github-issue-tower-defence-management 1.162.4 → 1.163.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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [1.163.0](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.162.5...v1.163.0) (2026-08-19)
2
+
3
+
4
+ ### Features
5
+
6
+ * **project:** add regular / inquiry story option below WORKFLOW BLOCKER on every TDPM project ([#1622](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/1622)) ([41275d3](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/commit/41275d34f6ff673c3f559439ca77c705fb91965c)), closes [#1621](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/1621)
7
+
8
+ ## [1.162.5](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.162.4...v1.162.5) (2026-08-19)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **use-case:** run SetNoStoryIssueToStoryUseCase regardless of cacheUsed ([#1618](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/1618)) ([3cee86d](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/commit/3cee86d7dde60afb9f0bab841672a127ffe249ce)), closes [#1614](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/1614)
14
+
1
15
  ## [1.162.4](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.162.3...v1.162.4) (2026-08-19)
2
16
 
3
17
 
@@ -5,6 +5,7 @@ const storyOption = (name, color) => ({ name, color, description: '' });
5
5
  const REQUIRED_STORY_OPTIONS = [
6
6
  storyOption('regular / NO STORY', 'RED'),
7
7
  storyOption('regular / WORKFLOW BLOCKER', 'RED'),
8
+ storyOption('regular / inquiry', 'RED'),
8
9
  storyOption('regular / high priority', 'RED'),
9
10
  storyOption('regular / workflow management', 'YELLOW'),
10
11
  storyOption('regular / routine management', 'YELLOW'),
@@ -1 +1 @@
1
- {"version":3,"file":"RequiredProjectField.js","sourceRoot":"","sources":["../../../src/domain/entities/RequiredProjectField.ts"],"names":[],"mappings":";;;AAUA,MAAM,WAAW,GAAG,CAClB,IAAY,EACZ,KAA2B,EACF,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;AAEjE,MAAM,sBAAsB,GAA8B;IACxD,WAAW,CAAC,oBAAoB,EAAE,KAAK,CAAC;IACxC,WAAW,CAAC,4BAA4B,EAAE,KAAK,CAAC;IAChD,WAAW,CAAC,yBAAyB,EAAE,KAAK,CAAC;IAC7C,WAAW,CAAC,+BAA+B,EAAE,QAAQ,CAAC;IACtD,WAAW,CAAC,8BAA8B,EAAE,QAAQ,CAAC;IACrD,WAAW,CAAC,sBAAsB,EAAE,MAAM,CAAC;IAC3C,WAAW,CAAC,qBAAqB,EAAE,MAAM,CAAC;IAC1C,WAAW,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACzC,WAAW,CAAC,mBAAmB,EAAE,MAAM,CAAC;CACzC,CAAC;AAEF,MAAM,iCAAiC,GAA8B,KAAK,CAAC,IAAI,CAC7E,EAAE,MAAM,EAAE,EAAE,EAAE,EACd,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;IACb,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC,EAAE;IACpB,KAAK,EAAE,MAAe;IACtB,WAAW,EAAE,EAAE;CAChB,CAAC,CACH,CAAC;AAEW,QAAA,gBAAgB,GAAG,OAAO,CAAC;AAC3B,QAAA,2BAA2B,GAAG,kBAAkB,CAAC;AACjD,QAAA,2BAA2B,GAAG,kBAAkB,CAAC;AACjD,QAAA,6BAA6B,GACxC,uCAAuC,CAAC;AAE7B,QAAA,uBAAuB,GAAqC;IACvE;QACE,IAAI,EAAE,wBAAgB;QACtB,QAAQ,EAAE,eAAe;QACzB,OAAO,EAAE,sBAAsB;KAChC;IACD;QACE,IAAI,EAAE,mCAA2B;QACjC,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,EAAE;KACZ;IACD;QACE,IAAI,EAAE,mCAA2B;QACjC,QAAQ,EAAE,eAAe;QACzB,OAAO,EAAE,iCAAiC;KAC3C;IACD;QACE,IAAI,EAAE,qCAA6B;QACnC,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,EAAE;KACZ;CACF,CAAC"}
1
+ {"version":3,"file":"RequiredProjectField.js","sourceRoot":"","sources":["../../../src/domain/entities/RequiredProjectField.ts"],"names":[],"mappings":";;;AAUA,MAAM,WAAW,GAAG,CAClB,IAAY,EACZ,KAA2B,EACF,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;AAEjE,MAAM,sBAAsB,GAA8B;IACxD,WAAW,CAAC,oBAAoB,EAAE,KAAK,CAAC;IACxC,WAAW,CAAC,4BAA4B,EAAE,KAAK,CAAC;IAChD,WAAW,CAAC,mBAAmB,EAAE,KAAK,CAAC;IACvC,WAAW,CAAC,yBAAyB,EAAE,KAAK,CAAC;IAC7C,WAAW,CAAC,+BAA+B,EAAE,QAAQ,CAAC;IACtD,WAAW,CAAC,8BAA8B,EAAE,QAAQ,CAAC;IACrD,WAAW,CAAC,sBAAsB,EAAE,MAAM,CAAC;IAC3C,WAAW,CAAC,qBAAqB,EAAE,MAAM,CAAC;IAC1C,WAAW,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACzC,WAAW,CAAC,mBAAmB,EAAE,MAAM,CAAC;CACzC,CAAC;AAEF,MAAM,iCAAiC,GAA8B,KAAK,CAAC,IAAI,CAC7E,EAAE,MAAM,EAAE,EAAE,EAAE,EACd,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;IACb,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC,EAAE;IACpB,KAAK,EAAE,MAAe;IACtB,WAAW,EAAE,EAAE;CAChB,CAAC,CACH,CAAC;AAEW,QAAA,gBAAgB,GAAG,OAAO,CAAC;AAC3B,QAAA,2BAA2B,GAAG,kBAAkB,CAAC;AACjD,QAAA,2BAA2B,GAAG,kBAAkB,CAAC;AACjD,QAAA,6BAA6B,GACxC,uCAAuC,CAAC;AAE7B,QAAA,uBAAuB,GAAqC;IACvE;QACE,IAAI,EAAE,wBAAgB;QACtB,QAAQ,EAAE,eAAe;QACzB,OAAO,EAAE,sBAAsB;KAChC;IACD;QACE,IAAI,EAAE,mCAA2B;QACjC,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,EAAE;KACZ;IACD;QACE,IAAI,EAAE,mCAA2B;QACjC,QAAQ,EAAE,eAAe;QACzB,OAAO,EAAE,iCAAiC;KAC3C;IACD;QACE,IAAI,EAAE,qCAA6B;QACnC,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,EAAE;KACZ;CACF,CAAC"}
@@ -9,6 +9,7 @@ class ProjectRequiredFieldCreateUseCase {
9
9
  this.run = async (params) => {
10
10
  const project = await this.projectRepository.getByUrl(params.projectUrl);
11
11
  await this.createMissingFields(project);
12
+ await this.reconcileStoryOptions(project);
12
13
  };
13
14
  this.createMissingFields = async (project) => {
14
15
  const existingFieldNames = (await this.projectRepository.listFieldNames(project)).map(ProjectFieldName_1.normalizeProjectFieldName);
@@ -19,6 +20,36 @@ class ProjectRequiredFieldCreateUseCase {
19
20
  await this.projectRepository.createField(project, required);
20
21
  }
21
22
  };
23
+ this.reconcileStoryOptions = async (project) => {
24
+ if (!project.story) {
25
+ return;
26
+ }
27
+ const storyFieldDefinition = RequiredProjectField_1.REQUIRED_PROJECT_FIELDS.find((f) => (0, ProjectFieldName_1.normalizeProjectFieldName)(f.name) ===
28
+ (0, ProjectFieldName_1.normalizeProjectFieldName)(RequiredProjectField_1.STORY_FIELD_NAME));
29
+ if (!storyFieldDefinition) {
30
+ return;
31
+ }
32
+ const requiredOptions = storyFieldDefinition.options;
33
+ const mergedOptions = project.story.stories.map((o) => ({ ...o }));
34
+ let addedCount = 0;
35
+ let previousRequiredIndex = -1;
36
+ for (const required of requiredOptions) {
37
+ const existingIndex = mergedOptions.findIndex((o) => this.optionNameSatisfies(o.name, required.name));
38
+ if (existingIndex >= 0) {
39
+ previousRequiredIndex = existingIndex;
40
+ continue;
41
+ }
42
+ const insertIndex = previousRequiredIndex + 1;
43
+ mergedOptions.splice(insertIndex, 0, { ...required, id: null });
44
+ previousRequiredIndex = insertIndex;
45
+ addedCount += 1;
46
+ }
47
+ if (addedCount === 0) {
48
+ return;
49
+ }
50
+ await this.projectRepository.updateStoryList(project, mergedOptions);
51
+ };
52
+ this.optionNameSatisfies = (currentName, requiredName) => (0, ProjectFieldName_1.normalizeProjectFieldName)(currentName).startsWith((0, ProjectFieldName_1.normalizeProjectFieldName)(requiredName));
22
53
  }
23
54
  }
24
55
  exports.ProjectRequiredFieldCreateUseCase = ProjectRequiredFieldCreateUseCase;
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectRequiredFieldCreateUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts"],"names":[],"mappings":";;;AACA,mEAAyE;AACzE,2EAA2E;AAG3E,MAAa,iCAAiC;IAC5C,YACmB,iBAGhB;QAHgB,sBAAiB,GAAjB,iBAAiB,CAGjC;QAGH,QAAG,GAAG,KAAK,EAAE,MAA8B,EAAiB,EAAE;YAC5D,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YACzE,MAAM,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC;QAC1C,CAAC,CAAC;QAEM,wBAAmB,GAAG,KAAK,EAAE,OAAgB,EAAiB,EAAE;YACtE,MAAM,kBAAkB,GAAG,CACzB,MAAM,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,OAAO,CAAC,CACrD,CAAC,GAAG,CAAC,4CAAyB,CAAC,CAAC;YACjC,KAAK,MAAM,QAAQ,IAAI,8CAAuB,EAAE,CAAC;gBAC/C,IACE,kBAAkB,CAAC,QAAQ,CAAC,IAAA,4CAAyB,EAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EACrE,CAAC;oBACD,SAAS;gBACX,CAAC;gBACD,MAAM,IAAI,CAAC,iBAAiB,CAAC,WAAW,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC,CAAC;IAnBC,CAAC;CAoBL;AA1BD,8EA0BC"}
1
+ {"version":3,"file":"ProjectRequiredFieldCreateUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts"],"names":[],"mappings":"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"}
@@ -6,7 +6,7 @@ class SetNoStoryIssueToStoryUseCase {
6
6
  this.issueRepository = issueRepository;
7
7
  this.run = async (input) => {
8
8
  const story = input.project.story;
9
- if (!story || input.cacheUsed) {
9
+ if (!story) {
10
10
  return;
11
11
  }
12
12
  const earliestTargetDate = input.targetDates[0] ?? null;
@@ -1 +1 @@
1
- {"version":3,"file":"SetNoStoryIssueToStoryUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/SetNoStoryIssueToStoryUseCase.ts"],"names":[],"mappings":";;;AAIA,MAAa,6BAA6B;IACxC,YAAqB,eAAqD;QAArD,oBAAe,GAAf,eAAe,CAAsC;QAE1E,QAAG,GAAG,KAAK,EAAE,KAKZ,EAAiB,EAAE;YAClB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAClC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;gBAC9B,OAAO;YACT,CAAC;YACD,MAAM,kBAAkB,GAAgB,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;YACrE,MAAM,aAAa,GAAG,CAAC,KAAY,EAAW,EAAE;gBAC9C,OAAO,CACL,KAAK,CAAC,KAAK,KAAK,IAAI;oBACpB,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAC3B,KAAK,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CACzC;oBACD,CAAC,KAAK,CAAC,cAAc,KAAK,IAAI;wBAC5B,CAAC,kBAAkB,KAAK,IAAI;4BAC1B,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,IAAI,kBAAkB,CAAC,OAAO,EAAE,CAAC,CAAC;oBACpE,KAAK,CAAC,cAAc,KAAK,IAAI;oBAC7B,KAAK,CAAC,KAAK,KAAK,MAAM,CACvB,CAAC;YACJ,CAAC,CAAC;YACF,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;YACnD,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,OAAO;YACT,CAAC;YACD,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;gBACjC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC1B,SAAS;gBACX,CAAC;gBACD,MAAM,IAAI,CAAC,eAAe,CAAC,WAAW,CACpC,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,EAC3B,KAAK,EACL,UAAU,CAAC,EAAE,CACd,CAAC;gBACF,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC,CAAC;IAzC2E,CAAC;CA0C/E;AA3CD,sEA2CC"}
1
+ {"version":3,"file":"SetNoStoryIssueToStoryUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/SetNoStoryIssueToStoryUseCase.ts"],"names":[],"mappings":";;;AAIA,MAAa,6BAA6B;IACxC,YAAqB,eAAqD;QAArD,oBAAe,GAAf,eAAe,CAAsC;QAE1E,QAAG,GAAG,KAAK,EAAE,KAKZ,EAAiB,EAAE;YAClB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAClC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO;YACT,CAAC;YACD,MAAM,kBAAkB,GAAgB,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;YACrE,MAAM,aAAa,GAAG,CAAC,KAAY,EAAW,EAAE;gBAC9C,OAAO,CACL,KAAK,CAAC,KAAK,KAAK,IAAI;oBACpB,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAC3B,KAAK,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CACzC;oBACD,CAAC,KAAK,CAAC,cAAc,KAAK,IAAI;wBAC5B,CAAC,kBAAkB,KAAK,IAAI;4BAC1B,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,IAAI,kBAAkB,CAAC,OAAO,EAAE,CAAC,CAAC;oBACpE,KAAK,CAAC,cAAc,KAAK,IAAI;oBAC7B,KAAK,CAAC,KAAK,KAAK,MAAM,CACvB,CAAC;YACJ,CAAC,CAAC;YACF,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;YACnD,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,OAAO;YACT,CAAC;YACD,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;gBACjC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC1B,SAAS;gBACX,CAAC;gBACD,MAAM,IAAI,CAAC,eAAe,CAAC,WAAW,CACpC,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,EAC3B,KAAK,EACL,UAAU,CAAC,EAAE,CACd,CAAC;gBACF,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC,CAAC;IAzC2E,CAAC;CA0C/E;AA3CD,sEA2CC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-issue-tower-defence-management",
3
- "version": "1.162.4",
3
+ "version": "1.163.0",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "scripts": {
@@ -16,6 +16,7 @@ const storyOption = (
16
16
  const REQUIRED_STORY_OPTIONS: Omit<FieldOption, 'id'>[] = [
17
17
  storyOption('regular / NO STORY', 'RED'),
18
18
  storyOption('regular / WORKFLOW BLOCKER', 'RED'),
19
+ storyOption('regular / inquiry', 'RED'),
19
20
  storyOption('regular / high priority', 'RED'),
20
21
  storyOption('regular / workflow management', 'YELLOW'),
21
22
  storyOption('regular / routine management', 'YELLOW'),
@@ -1,5 +1,5 @@
1
1
  import { mock } from 'jest-mock-extended';
2
- import { Project } from '../entities/Project';
2
+ import { FieldOption, Project } from '../entities/Project';
3
3
  import {
4
4
  DEPENDED_ISSUE_URL_FIELD_NAME,
5
5
  NEXT_ACTION_DATE_FIELD_NAME,
@@ -11,7 +11,7 @@ import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
11
11
 
12
12
  describe('ProjectRequiredFieldCreateUseCase', () => {
13
13
  const projectUrl = 'https://github.com/orgs/acme-labs/projects/18';
14
- const project: Project = {
14
+ const projectWithoutStory: Project = {
15
15
  id: 'PVT_kwDOAcmeLabs4AbCdEf',
16
16
  url: projectUrl,
17
17
  databaseId: 18,
@@ -29,14 +29,34 @@ describe('ProjectRequiredFieldCreateUseCase', () => {
29
29
  completionDate50PercentConfidence: null,
30
30
  };
31
31
 
32
- const createUseCase = (existingFieldNames: string[]) => {
32
+ const buildProjectWithStory = (stories: FieldOption[]): Project => ({
33
+ ...projectWithoutStory,
34
+ story: {
35
+ name: STORY_FIELD_NAME,
36
+ fieldId: 'PVTSSF_story',
37
+ databaseId: 42,
38
+ stories,
39
+ workflowManagementStory: {
40
+ id:
41
+ stories.find((s) => s.name === 'regular / workflow management')?.id ??
42
+ 'opt_wfm',
43
+ name: 'regular / workflow management',
44
+ },
45
+ },
46
+ });
47
+
48
+ const createUseCase = (existingFieldNames: string[], project: Project) => {
33
49
  const projectRepository =
34
50
  mock<
35
- Pick<ProjectRepository, 'getByUrl' | 'listFieldNames' | 'createField'>
51
+ Pick<
52
+ ProjectRepository,
53
+ 'getByUrl' | 'listFieldNames' | 'createField' | 'updateStoryList'
54
+ >
36
55
  >();
37
56
  projectRepository.getByUrl.mockResolvedValue(project);
38
57
  projectRepository.listFieldNames.mockResolvedValue(existingFieldNames);
39
58
  projectRepository.createField.mockResolvedValue(undefined);
59
+ projectRepository.updateStoryList.mockResolvedValue([]);
40
60
  return {
41
61
  projectRepository,
42
62
  useCase: new ProjectRequiredFieldCreateUseCase(projectRepository),
@@ -44,11 +64,10 @@ describe('ProjectRequiredFieldCreateUseCase', () => {
44
64
  };
45
65
 
46
66
  it('should create every required field when the project has none of them', async () => {
47
- const { projectRepository, useCase } = createUseCase([
48
- 'Title',
49
- 'Assignees',
50
- 'Status',
51
- ]);
67
+ const { projectRepository, useCase } = createUseCase(
68
+ ['Title', 'Assignees', 'Status'],
69
+ projectWithoutStory,
70
+ );
52
71
 
53
72
  await useCase.run({ projectUrl });
54
73
 
@@ -64,14 +83,17 @@ describe('ProjectRequiredFieldCreateUseCase', () => {
64
83
  });
65
84
 
66
85
  it('should create nothing when the project already has every required field', async () => {
67
- const { projectRepository, useCase } = createUseCase([
68
- 'Title',
69
- 'Status',
70
- 'story',
71
- 'nextactiondate',
72
- 'nextactionhour',
73
- 'Depended Issue URL separated by comma',
74
- ]);
86
+ const { projectRepository, useCase } = createUseCase(
87
+ [
88
+ 'Title',
89
+ 'Status',
90
+ 'story',
91
+ 'nextactiondate',
92
+ 'nextactionhour',
93
+ 'Depended Issue URL separated by comma',
94
+ ],
95
+ projectWithoutStory,
96
+ );
75
97
 
76
98
  await useCase.run({ projectUrl });
77
99
 
@@ -79,12 +101,10 @@ describe('ProjectRequiredFieldCreateUseCase', () => {
79
101
  });
80
102
 
81
103
  it('should create only the missing fields', async () => {
82
- const { projectRepository, useCase } = createUseCase([
83
- 'Title',
84
- 'Status',
85
- 'Story',
86
- 'Next Action Date',
87
- ]);
104
+ const { projectRepository, useCase } = createUseCase(
105
+ ['Title', 'Status', 'Story', 'Next Action Date'],
106
+ projectWithoutStory,
107
+ );
88
108
 
89
109
  await useCase.run({ projectUrl });
90
110
 
@@ -97,8 +117,11 @@ describe('ProjectRequiredFieldCreateUseCase', () => {
97
117
  ]);
98
118
  });
99
119
 
100
- it('should create the story field with the standard story options', async () => {
101
- const { projectRepository, useCase } = createUseCase(['Title', 'Status']);
120
+ it('should create the story field with the standard story options including regular / inquiry', async () => {
121
+ const { projectRepository, useCase } = createUseCase(
122
+ ['Title', 'Status'],
123
+ projectWithoutStory,
124
+ );
102
125
 
103
126
  await useCase.run({ projectUrl });
104
127
 
@@ -109,6 +132,7 @@ describe('ProjectRequiredFieldCreateUseCase', () => {
109
132
  expect(storyCall?.[1].options.map((option) => option.name)).toEqual([
110
133
  'regular / NO STORY',
111
134
  'regular / WORKFLOW BLOCKER',
135
+ 'regular / inquiry',
112
136
  'regular / high priority',
113
137
  'regular / workflow management',
114
138
  'regular / routine management',
@@ -120,7 +144,10 @@ describe('ProjectRequiredFieldCreateUseCase', () => {
120
144
  });
121
145
 
122
146
  it('should create the next action hour field with the hour options from 1 to 23', async () => {
123
- const { projectRepository, useCase } = createUseCase(['Title', 'Status']);
147
+ const { projectRepository, useCase } = createUseCase(
148
+ ['Title', 'Status'],
149
+ projectWithoutStory,
150
+ );
124
151
 
125
152
  await useCase.run({ projectUrl });
126
153
 
@@ -134,7 +161,10 @@ describe('ProjectRequiredFieldCreateUseCase', () => {
134
161
  });
135
162
 
136
163
  it('should create the depended issue url field as a text field', async () => {
137
- const { projectRepository, useCase } = createUseCase(['Title', 'Status']);
164
+ const { projectRepository, useCase } = createUseCase(
165
+ ['Title', 'Status'],
166
+ projectWithoutStory,
167
+ );
138
168
 
139
169
  await useCase.run({ projectUrl });
140
170
 
@@ -146,7 +176,10 @@ describe('ProjectRequiredFieldCreateUseCase', () => {
146
176
  });
147
177
 
148
178
  it('should create the next action date field as a date field', async () => {
149
- const { projectRepository, useCase } = createUseCase(['Title', 'Status']);
179
+ const { projectRepository, useCase } = createUseCase(
180
+ ['Title', 'Status'],
181
+ projectWithoutStory,
182
+ );
150
183
 
151
184
  await useCase.run({ projectUrl });
152
185
 
@@ -156,4 +189,407 @@ describe('ProjectRequiredFieldCreateUseCase', () => {
156
189
  expect(dateCall?.[1].dataType).toBe('DATE');
157
190
  expect(dateCall?.[1].options).toEqual([]);
158
191
  });
192
+
193
+ describe('reconcileStoryOptions', () => {
194
+ const existingStoriesWithoutInquiry: FieldOption[] = [
195
+ { id: 'opt1', name: 'regular / NO STORY', color: 'RED', description: '' },
196
+ {
197
+ id: 'opt2',
198
+ name: 'regular / WORKFLOW BLOCKER',
199
+ color: 'RED',
200
+ description: '',
201
+ },
202
+ {
203
+ id: 'opt3',
204
+ name: 'regular / high priority',
205
+ color: 'RED',
206
+ description: '',
207
+ },
208
+ {
209
+ id: 'opt4',
210
+ name: 'regular / workflow management',
211
+ color: 'YELLOW',
212
+ description: '',
213
+ },
214
+ {
215
+ id: 'opt5',
216
+ name: 'regular / routine management',
217
+ color: 'YELLOW',
218
+ description: '',
219
+ },
220
+ {
221
+ id: 'opt6',
222
+ name: 'regular / middle bug',
223
+ color: 'GRAY',
224
+ description: '',
225
+ },
226
+ {
227
+ id: 'opt7',
228
+ name: 'regular / minor bug',
229
+ color: 'GRAY',
230
+ description: '',
231
+ },
232
+ {
233
+ id: 'opt8',
234
+ name: 'regular / refactor',
235
+ color: 'GRAY',
236
+ description: '',
237
+ },
238
+ { id: 'opt9', name: 'regular / backlog', color: 'GRAY', description: '' },
239
+ ];
240
+
241
+ const existingStoriesComplete: FieldOption[] = [
242
+ { id: 'opt1', name: 'regular / NO STORY', color: 'RED', description: '' },
243
+ {
244
+ id: 'opt2',
245
+ name: 'regular / WORKFLOW BLOCKER',
246
+ color: 'RED',
247
+ description: '',
248
+ },
249
+ {
250
+ id: 'opt_inq',
251
+ name: 'regular / inquiry',
252
+ color: 'RED',
253
+ description: '',
254
+ },
255
+ {
256
+ id: 'opt3',
257
+ name: 'regular / high priority',
258
+ color: 'RED',
259
+ description: '',
260
+ },
261
+ {
262
+ id: 'opt4',
263
+ name: 'regular / workflow management',
264
+ color: 'YELLOW',
265
+ description: '',
266
+ },
267
+ {
268
+ id: 'opt5',
269
+ name: 'regular / routine management',
270
+ color: 'YELLOW',
271
+ description: '',
272
+ },
273
+ {
274
+ id: 'opt6',
275
+ name: 'regular / middle bug',
276
+ color: 'GRAY',
277
+ description: '',
278
+ },
279
+ {
280
+ id: 'opt7',
281
+ name: 'regular / minor bug',
282
+ color: 'GRAY',
283
+ description: '',
284
+ },
285
+ {
286
+ id: 'opt8',
287
+ name: 'regular / refactor',
288
+ color: 'GRAY',
289
+ description: '',
290
+ },
291
+ { id: 'opt9', name: 'regular / backlog', color: 'GRAY', description: '' },
292
+ ];
293
+
294
+ it('should not call updateStoryList when story field does not exist on the project', async () => {
295
+ const { projectRepository, useCase } = createUseCase(
296
+ [
297
+ 'Title',
298
+ 'Status',
299
+ 'Story',
300
+ 'Next Action Date',
301
+ 'Next Action Hour',
302
+ 'Depended Issue URL separated by comma',
303
+ ],
304
+ projectWithoutStory,
305
+ );
306
+
307
+ await useCase.run({ projectUrl });
308
+
309
+ expect(projectRepository.updateStoryList).not.toHaveBeenCalled();
310
+ });
311
+
312
+ it('should not call updateStoryList when all required story options are already present', async () => {
313
+ const project = buildProjectWithStory(existingStoriesComplete);
314
+ const { projectRepository, useCase } = createUseCase(
315
+ [
316
+ 'Title',
317
+ 'Status',
318
+ 'Story',
319
+ 'Next Action Date',
320
+ 'Next Action Hour',
321
+ 'Depended Issue URL separated by comma',
322
+ ],
323
+ project,
324
+ );
325
+
326
+ await useCase.run({ projectUrl });
327
+
328
+ expect(projectRepository.updateStoryList).not.toHaveBeenCalled();
329
+ });
330
+
331
+ it('should call updateStoryList when regular / inquiry is missing from the existing story field', async () => {
332
+ const project = buildProjectWithStory(existingStoriesWithoutInquiry);
333
+ const { projectRepository, useCase } = createUseCase(
334
+ [
335
+ 'Title',
336
+ 'Status',
337
+ 'Story',
338
+ 'Next Action Date',
339
+ 'Next Action Hour',
340
+ 'Depended Issue URL separated by comma',
341
+ ],
342
+ project,
343
+ );
344
+
345
+ await useCase.run({ projectUrl });
346
+
347
+ expect(projectRepository.updateStoryList).toHaveBeenCalledTimes(1);
348
+ });
349
+
350
+ it('should place regular / inquiry immediately after regular / WORKFLOW BLOCKER in the merged option list', async () => {
351
+ const project = buildProjectWithStory(existingStoriesWithoutInquiry);
352
+ const { projectRepository, useCase } = createUseCase(
353
+ [
354
+ 'Title',
355
+ 'Status',
356
+ 'Story',
357
+ 'Next Action Date',
358
+ 'Next Action Hour',
359
+ 'Depended Issue URL separated by comma',
360
+ ],
361
+ project,
362
+ );
363
+
364
+ await useCase.run({ projectUrl });
365
+
366
+ const submittedOptions =
367
+ projectRepository.updateStoryList.mock.calls[0][1];
368
+ const names = submittedOptions.map((o) => o.name);
369
+ const workflowBlockerIndex = names.indexOf('regular / WORKFLOW BLOCKER');
370
+ const inquiryIndex = names.indexOf('regular / inquiry');
371
+ expect(inquiryIndex).toBe(workflowBlockerIndex + 1);
372
+ });
373
+
374
+ it('should submit the new option without an id', async () => {
375
+ const project = buildProjectWithStory(existingStoriesWithoutInquiry);
376
+ const { projectRepository, useCase } = createUseCase(
377
+ [
378
+ 'Title',
379
+ 'Status',
380
+ 'Story',
381
+ 'Next Action Date',
382
+ 'Next Action Hour',
383
+ 'Depended Issue URL separated by comma',
384
+ ],
385
+ project,
386
+ );
387
+
388
+ await useCase.run({ projectUrl });
389
+
390
+ const submittedOptions =
391
+ projectRepository.updateStoryList.mock.calls[0][1];
392
+ const inquiryOption = submittedOptions.find(
393
+ (o) => o.name === 'regular / inquiry',
394
+ );
395
+ expect(inquiryOption?.id).toBeNull();
396
+ });
397
+
398
+ it('should preserve the existing option ids in the merged list', async () => {
399
+ const project = buildProjectWithStory(existingStoriesWithoutInquiry);
400
+ const { projectRepository, useCase } = createUseCase(
401
+ [
402
+ 'Title',
403
+ 'Status',
404
+ 'Story',
405
+ 'Next Action Date',
406
+ 'Next Action Hour',
407
+ 'Depended Issue URL separated by comma',
408
+ ],
409
+ project,
410
+ );
411
+
412
+ await useCase.run({ projectUrl });
413
+
414
+ const submittedOptions =
415
+ projectRepository.updateStoryList.mock.calls[0][1];
416
+ for (const original of existingStoriesWithoutInquiry) {
417
+ const submitted = submittedOptions.find(
418
+ (o) => o.name === original.name,
419
+ );
420
+ expect(submitted?.id).toBe(original.id);
421
+ }
422
+ });
423
+
424
+ it('should include extra options that are not in the required list and preserve their ids', async () => {
425
+ const extraOption: FieldOption = {
426
+ id: 'opt_extra',
427
+ name: 'regular / special project',
428
+ color: 'BLUE',
429
+ description: '',
430
+ };
431
+ const storiesWithExtra = [...existingStoriesWithoutInquiry, extraOption];
432
+ const project = buildProjectWithStory(storiesWithExtra);
433
+ const { projectRepository, useCase } = createUseCase(
434
+ [
435
+ 'Title',
436
+ 'Status',
437
+ 'Story',
438
+ 'Next Action Date',
439
+ 'Next Action Hour',
440
+ 'Depended Issue URL separated by comma',
441
+ ],
442
+ project,
443
+ );
444
+
445
+ await useCase.run({ projectUrl });
446
+
447
+ const submittedOptions =
448
+ projectRepository.updateStoryList.mock.calls[0][1];
449
+ const submittedExtra = submittedOptions.find(
450
+ (o) => o.name === 'regular / special project',
451
+ );
452
+ expect(submittedExtra?.id).toBe('opt_extra');
453
+ });
454
+
455
+ it('should submit regular / inquiry with color RED', async () => {
456
+ const project = buildProjectWithStory(existingStoriesWithoutInquiry);
457
+ const { projectRepository, useCase } = createUseCase(
458
+ [
459
+ 'Title',
460
+ 'Status',
461
+ 'Story',
462
+ 'Next Action Date',
463
+ 'Next Action Hour',
464
+ 'Depended Issue URL separated by comma',
465
+ ],
466
+ project,
467
+ );
468
+
469
+ await useCase.run({ projectUrl });
470
+
471
+ const submittedOptions =
472
+ projectRepository.updateStoryList.mock.calls[0][1];
473
+ const inquiryOption = submittedOptions.find(
474
+ (o) => o.name === 'regular / inquiry',
475
+ );
476
+ expect(inquiryOption?.color).toBe('RED');
477
+ });
478
+
479
+ const storiesWithDescriptiveNoStoryAndInterleavedExtras: FieldOption[] = [
480
+ {
481
+ id: 'opt1',
482
+ name: "regular / NO STORY; DON'T WORK ON THIS STORY, NEED TO SET STORY FIELD",
483
+ color: 'RED',
484
+ description: '',
485
+ },
486
+ {
487
+ id: 'opt_extra_a',
488
+ name: 'acme / rebuild the billing screen',
489
+ color: 'BLUE',
490
+ description: '',
491
+ },
492
+ {
493
+ id: 'opt2',
494
+ name: 'regular / WORKFLOW BLOCKER',
495
+ color: 'RED',
496
+ description: '',
497
+ },
498
+ {
499
+ id: 'opt_extra_b',
500
+ name: 'acme / migrate the search index',
501
+ color: 'GREEN',
502
+ description: '',
503
+ },
504
+ {
505
+ id: 'opt4',
506
+ name: 'regular / workflow management',
507
+ color: 'YELLOW',
508
+ description: '',
509
+ },
510
+ {
511
+ id: 'opt5',
512
+ name: 'regular / routine management',
513
+ color: 'YELLOW',
514
+ description: '',
515
+ },
516
+ { id: 'opt9', name: 'regular / backlog', color: 'GRAY', description: '' },
517
+ ];
518
+
519
+ const runWithStories = async (stories: FieldOption[]) => {
520
+ const project = buildProjectWithStory(stories);
521
+ const { projectRepository, useCase } = createUseCase(
522
+ [
523
+ 'Title',
524
+ 'Status',
525
+ 'Story',
526
+ 'Next Action Date',
527
+ 'Next Action Hour',
528
+ 'Depended Issue URL separated by comma',
529
+ ],
530
+ project,
531
+ );
532
+
533
+ await useCase.run({ projectUrl });
534
+
535
+ return projectRepository.updateStoryList.mock.calls[0]?.[1];
536
+ };
537
+
538
+ it('should treat an existing option whose name extends a required name as that required option', async () => {
539
+ const submittedOptions = await runWithStories(
540
+ storiesWithDescriptiveNoStoryAndInterleavedExtras,
541
+ );
542
+
543
+ const noStoryNames = (submittedOptions ?? [])
544
+ .map((o) => o.name)
545
+ .filter((name) => name.toLowerCase().includes('no story'));
546
+ expect(noStoryNames).toEqual([
547
+ "regular / NO STORY; DON'T WORK ON THIS STORY, NEED TO SET STORY FIELD",
548
+ ]);
549
+ });
550
+
551
+ it('should add only the options that are genuinely absent', async () => {
552
+ const submittedOptions = await runWithStories(
553
+ storiesWithDescriptiveNoStoryAndInterleavedExtras,
554
+ );
555
+
556
+ const addedNames = (submittedOptions ?? [])
557
+ .filter((o) => o.id === null)
558
+ .map((o) => o.name);
559
+ expect(addedNames).toEqual([
560
+ 'regular / inquiry',
561
+ 'regular / high priority',
562
+ 'regular / middle bug',
563
+ 'regular / minor bug',
564
+ 'regular / refactor',
565
+ ]);
566
+ });
567
+
568
+ it('should keep the relative order of every option that already exists', async () => {
569
+ const submittedOptions = await runWithStories(
570
+ storiesWithDescriptiveNoStoryAndInterleavedExtras,
571
+ );
572
+
573
+ const submittedNames = (submittedOptions ?? []).map((o) => o.name);
574
+ const existingNamesInSubmittedOrder = submittedNames.filter((name) =>
575
+ storiesWithDescriptiveNoStoryAndInterleavedExtras.some(
576
+ (existing) => existing.name === name,
577
+ ),
578
+ );
579
+ expect(existingNamesInSubmittedOrder).toEqual(
580
+ storiesWithDescriptiveNoStoryAndInterleavedExtras.map((o) => o.name),
581
+ );
582
+ });
583
+
584
+ it('should insert regular / inquiry directly below regular / WORKFLOW BLOCKER when extras are interleaved', async () => {
585
+ const submittedOptions = await runWithStories(
586
+ storiesWithDescriptiveNoStoryAndInterleavedExtras,
587
+ );
588
+
589
+ const submittedNames = (submittedOptions ?? []).map((o) => o.name);
590
+ expect(submittedNames.indexOf('regular / inquiry')).toBe(
591
+ submittedNames.indexOf('regular / WORKFLOW BLOCKER') + 1,
592
+ );
593
+ });
594
+ });
159
595
  });
@@ -1,19 +1,27 @@
1
- import { Project } from '../entities/Project';
1
+ import { FieldOption, Project } from '../entities/Project';
2
2
  import { normalizeProjectFieldName } from '../entities/ProjectFieldName';
3
- import { REQUIRED_PROJECT_FIELDS } from '../entities/RequiredProjectField';
3
+ import {
4
+ REQUIRED_PROJECT_FIELDS,
5
+ STORY_FIELD_NAME,
6
+ } from '../entities/RequiredProjectField';
4
7
  import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
5
8
 
9
+ type StoryOptionToSubmit = Omit<FieldOption, 'id'> & {
10
+ id: FieldOption['id'] | null;
11
+ };
12
+
6
13
  export class ProjectRequiredFieldCreateUseCase {
7
14
  constructor(
8
15
  private readonly projectRepository: Pick<
9
16
  ProjectRepository,
10
- 'getByUrl' | 'listFieldNames' | 'createField'
17
+ 'getByUrl' | 'listFieldNames' | 'createField' | 'updateStoryList'
11
18
  >,
12
19
  ) {}
13
20
 
14
21
  run = async (params: { projectUrl: string }): Promise<void> => {
15
22
  const project = await this.projectRepository.getByUrl(params.projectUrl);
16
23
  await this.createMissingFields(project);
24
+ await this.reconcileStoryOptions(project);
17
25
  };
18
26
 
19
27
  private createMissingFields = async (project: Project): Promise<void> => {
@@ -29,4 +37,49 @@ export class ProjectRequiredFieldCreateUseCase {
29
37
  await this.projectRepository.createField(project, required);
30
38
  }
31
39
  };
40
+
41
+ private reconcileStoryOptions = async (project: Project): Promise<void> => {
42
+ if (!project.story) {
43
+ return;
44
+ }
45
+ const storyFieldDefinition = REQUIRED_PROJECT_FIELDS.find(
46
+ (f) =>
47
+ normalizeProjectFieldName(f.name) ===
48
+ normalizeProjectFieldName(STORY_FIELD_NAME),
49
+ );
50
+ if (!storyFieldDefinition) {
51
+ return;
52
+ }
53
+ const requiredOptions = storyFieldDefinition.options;
54
+ const mergedOptions: StoryOptionToSubmit[] = project.story.stories.map(
55
+ (o) => ({ ...o }),
56
+ );
57
+ let addedCount = 0;
58
+ let previousRequiredIndex = -1;
59
+ for (const required of requiredOptions) {
60
+ const existingIndex = mergedOptions.findIndex((o) =>
61
+ this.optionNameSatisfies(o.name, required.name),
62
+ );
63
+ if (existingIndex >= 0) {
64
+ previousRequiredIndex = existingIndex;
65
+ continue;
66
+ }
67
+ const insertIndex = previousRequiredIndex + 1;
68
+ mergedOptions.splice(insertIndex, 0, { ...required, id: null });
69
+ previousRequiredIndex = insertIndex;
70
+ addedCount += 1;
71
+ }
72
+ if (addedCount === 0) {
73
+ return;
74
+ }
75
+ await this.projectRepository.updateStoryList(project, mergedOptions);
76
+ };
77
+
78
+ private optionNameSatisfies = (
79
+ currentName: string,
80
+ requiredName: string,
81
+ ): boolean =>
82
+ normalizeProjectFieldName(currentName).startsWith(
83
+ normalizeProjectFieldName(requiredName),
84
+ );
32
85
  }
@@ -94,24 +94,28 @@ describe('SetNoStoryIssueToStoryUseCase', () => {
94
94
  expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
95
95
  });
96
96
 
97
- it('should do nothing when cacheUsed is true', async () => {
98
- await useCase.run({
97
+ it('should assign story to eligible issue even when cacheUsed is true', async () => {
98
+ const issue: Issue = {
99
+ ...mock<Issue>(),
100
+ labels: [],
101
+ story: null,
102
+ state: 'OPEN',
103
+ nextActionDate: null,
104
+ nextActionHour: null,
105
+ };
106
+
107
+ const promise = useCase.run({
99
108
  targetDates: [targetDate],
100
109
  project: basicProject,
101
- issues: [
102
- {
103
- ...mock<Issue>(),
104
- labels: [],
105
- story: null,
106
- state: 'OPEN',
107
- nextActionDate: null,
108
- nextActionHour: null,
109
- },
110
- ],
110
+ issues: [issue],
111
111
  cacheUsed: true,
112
112
  });
113
+ await jest.runAllTimersAsync();
114
+ await promise;
113
115
 
114
- expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
116
+ expect(mockIssueRepository.updateStory.mock.calls).toEqual([
117
+ [{ ...basicProject, story: basicProject.story }, issue, 'noStoryId'],
118
+ ]);
115
119
  });
116
120
 
117
121
  it('should assign first story to eligible issue on non-minute-0 target date', async () => {
@@ -12,7 +12,7 @@ export class SetNoStoryIssueToStoryUseCase {
12
12
  cacheUsed: boolean;
13
13
  }): Promise<void> => {
14
14
  const story = input.project.story;
15
- if (!story || input.cacheUsed) {
15
+ if (!story) {
16
16
  return;
17
17
  }
18
18
  const earliestTargetDate: Date | null = input.targetDates[0] ?? null;
@@ -1 +1 @@
1
- {"version":3,"file":"RequiredProjectField.d.ts","sourceRoot":"","sources":["../../../src/domain/entities/RequiredProjectField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,MAAM,MAAM,4BAA4B,GAAG,MAAM,GAAG,MAAM,GAAG,eAAe,CAAC;AAE7E,MAAM,MAAM,8BAA8B,GAAG;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,4BAA4B,CAAC;IACvC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,CAAC;CACpC,CAAC;AA4BF,eAAO,MAAM,gBAAgB,UAAU,CAAC;AACxC,eAAO,MAAM,2BAA2B,qBAAqB,CAAC;AAC9D,eAAO,MAAM,2BAA2B,qBAAqB,CAAC;AAC9D,eAAO,MAAM,6BAA6B,0CACD,CAAC;AAE1C,eAAO,MAAM,uBAAuB,EAAE,8BAA8B,EAqBnE,CAAC"}
1
+ {"version":3,"file":"RequiredProjectField.d.ts","sourceRoot":"","sources":["../../../src/domain/entities/RequiredProjectField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,MAAM,MAAM,4BAA4B,GAAG,MAAM,GAAG,MAAM,GAAG,eAAe,CAAC;AAE7E,MAAM,MAAM,8BAA8B,GAAG;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,4BAA4B,CAAC;IACvC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,CAAC;CACpC,CAAC;AA6BF,eAAO,MAAM,gBAAgB,UAAU,CAAC;AACxC,eAAO,MAAM,2BAA2B,qBAAqB,CAAC;AAC9D,eAAO,MAAM,2BAA2B,qBAAqB,CAAC;AAC9D,eAAO,MAAM,6BAA6B,0CACD,CAAC;AAE1C,eAAO,MAAM,uBAAuB,EAAE,8BAA8B,EAqBnE,CAAC"}
@@ -1,10 +1,12 @@
1
1
  import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
2
2
  export declare class ProjectRequiredFieldCreateUseCase {
3
3
  private readonly projectRepository;
4
- constructor(projectRepository: Pick<ProjectRepository, 'getByUrl' | 'listFieldNames' | 'createField'>);
4
+ constructor(projectRepository: Pick<ProjectRepository, 'getByUrl' | 'listFieldNames' | 'createField' | 'updateStoryList'>);
5
5
  run: (params: {
6
6
  projectUrl: string;
7
7
  }) => Promise<void>;
8
8
  private createMissingFields;
9
+ private reconcileStoryOptions;
10
+ private optionNameSatisfies;
9
11
  }
10
12
  //# sourceMappingURL=ProjectRequiredFieldCreateUseCase.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectRequiredFieldCreateUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAE3E,qBAAa,iCAAiC;IAE1C,OAAO,CAAC,QAAQ,CAAC,iBAAiB;gBAAjB,iBAAiB,EAAE,IAAI,CACtC,iBAAiB,EACjB,UAAU,GAAG,gBAAgB,GAAG,aAAa,CAC9C;IAGH,GAAG,GAAU,QAAQ;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,KAAG,OAAO,CAAC,IAAI,CAAC,CAGzD;IAEF,OAAO,CAAC,mBAAmB,CAYzB;CACH"}
1
+ {"version":3,"file":"ProjectRequiredFieldCreateUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAM3E,qBAAa,iCAAiC;IAE1C,OAAO,CAAC,QAAQ,CAAC,iBAAiB;gBAAjB,iBAAiB,EAAE,IAAI,CACtC,iBAAiB,EACjB,UAAU,GAAG,gBAAgB,GAAG,aAAa,GAAG,iBAAiB,CAClE;IAGH,GAAG,GAAU,QAAQ;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,KAAG,OAAO,CAAC,IAAI,CAAC,CAIzD;IAEF,OAAO,CAAC,mBAAmB,CAYzB;IAEF,OAAO,CAAC,qBAAqB,CAmC3B;IAEF,OAAO,CAAC,mBAAmB,CAMvB;CACL"}