phasegate 0.91.0 → 0.108.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 +255 -0
- package/README.ja.md +17 -7
- package/README.md +26 -4
- package/docs/ADR/ADR-015-architecture-preset.md +183 -0
- package/docs/guide/cli-reference.md +108 -0
- package/docs/guide/codex-integration.md +7 -2
- package/docs/guide/installation.md +10 -2
- package/docs/guide/preset-selection.md +170 -0
- package/docs/guide/quick-vs-full-mode.md +3 -3
- package/docs/guide/retrofit-adoption.md +19 -2
- package/docs/guide/skills-overview.md +1 -1
- package/package.json +7 -1
- package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +108 -100
- package/scripts/harness/agent-integration/domain/ports/phase-gate-query-port.ts +3 -3
- package/scripts/harness/agent-integration/domain/value-objects/write-target-scope.ts +26 -30
- package/scripts/harness/agent-integration/infrastructure/adapters/file-system-story-reflection-query-adapter.ts +6 -2
- package/scripts/harness/biome-ast-engine/application/dto/analyze-import-graph-input.ts +3 -0
- package/scripts/harness/biome-ast-engine/application/dto/resolve-enabled-rules-output.ts +2 -0
- package/scripts/harness/biome-ast-engine/application/mappers/resolve-enabled-rules-output-mapper.ts +4 -1
- package/scripts/harness/biome-ast-engine/application/usecases/analyze-import-graph-usecase.ts +4 -1
- package/scripts/harness/biome-ast-engine/application/usecases/execute-lint-usecase.ts +2 -0
- package/scripts/harness/biome-ast-engine/application/usecases/resolve-enabled-rules-usecase.ts +31 -3
- package/scripts/harness/biome-ast-engine/composition-root.ts +10 -2
- package/scripts/harness/biome-ast-engine/domain/ports/rule-config-provider-port.ts +16 -0
- package/scripts/harness/biome-ast-engine/domain/ports/source-module-analyzer-port.ts +5 -1
- package/scripts/harness/biome-ast-engine/domain/services/lint-runner.ts +5 -1
- package/scripts/harness/biome-ast-engine/domain/value-objects/architecture-spec.ts +38 -0
- package/scripts/harness/biome-ast-engine/domain/value-objects/layer-boundary.ts +7 -8
- package/scripts/harness/biome-ast-engine/domain/value-objects/layer-name.ts +15 -23
- package/scripts/harness/biome-ast-engine/domain/value-objects/source-module-snapshot.ts +11 -4
- package/scripts/harness/biome-ast-engine/infrastructure/adapters/harness-config-provider-adapter.ts +29 -3
- package/scripts/harness/biome-ast-engine/infrastructure/adapters/typescript-source-module-analyzer-adapter.ts +22 -15
- package/scripts/harness/biome-ast-engine/infrastructure/mappers/source-module-snapshot-mapper.ts +26 -17
- package/scripts/harness/config-foundation/application/dto/resolved-config-output.ts +1 -0
- package/scripts/harness/config-foundation/application/usecases/load-resolved-config-use-case.ts +34 -2
- package/scripts/harness/config-foundation/application/usecases/migrate-schema-use-case.ts +89 -0
- package/scripts/harness/config-foundation/composition-root.ts +8 -0
- package/scripts/harness/config-foundation/domain/harness-config.ts +6 -0
- package/scripts/harness/config-foundation/domain/services/architecture-resolution-service.ts +257 -0
- package/scripts/harness/config-foundation/domain/value-objects/architecture-config.ts +66 -0
- package/scripts/harness/config-foundation/domain/value-objects/architecture-preset-catalog.ts +75 -0
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +546 -0
- package/scripts/harness/config-foundation/infrastructure/validators/ajv-config-schema-validator.ts +18 -6
- package/scripts/harness/config-foundation/presentation/cli/migrate-schema-command-handler.ts +83 -0
- package/scripts/harness/integrations/pre-commit.ts +211 -52
- package/scripts/harness/main.ts +460 -340
- package/scripts/harness/phase-dependency-model/domain/ports/story-reflection-file-system-port.ts +2 -4
- package/scripts/harness/phase-dependency-model/domain/services/story-reflection-checker.ts +54 -17
- package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.ts +154 -36
- package/scripts/harness/setup/skill-deployer.ts +140 -102
- package/scripts/harness/skill-quality/domain/errors/skill-quality-error.ts +24 -23
- package/scripts/harness/skill-quality/domain/value-objects/commit-message.ts +23 -7
- package/scripts/harness/traceability-model/application/usecases/apply-work-item-migration-usecase.ts +52 -0
- package/scripts/harness/traceability-model/application/usecases/plan-work-item-migration-usecase.ts +29 -0
- package/scripts/harness/traceability-model/application/usecases/validate-design-story-annotations-usecase.ts +83 -18
- package/scripts/harness/traceability-model/composition-root.ts +48 -30
- package/scripts/harness/traceability-model/domain/ports/design-document-port.ts +9 -15
- package/scripts/harness/traceability-model/domain/ports/work-item-migration-apply-port.ts +11 -0
- package/scripts/harness/traceability-model/domain/ports/work-item-migration-source-port.ts +9 -0
- package/scripts/harness/traceability-model/domain/services/work-item-migration-planner.ts +162 -0
- package/scripts/harness/traceability-model/domain/value-objects/work-item-frontmatter.ts +57 -0
- package/scripts/harness/traceability-model/domain/value-objects/work-item-migration-candidate.ts +47 -0
- package/scripts/harness/traceability-model/infrastructure/gateways/file-system-work-item-migration-apply-gateway.ts +110 -0
- package/scripts/harness/traceability-model/infrastructure/gateways/file-system-work-item-migration-source-gateway.ts +182 -0
- package/scripts/harness/traceability-model/infrastructure/gateways/markdown-design-document-gateway.ts +29 -43
- package/scripts/harness/traceability-model/infrastructure/parsers/work-item-frontmatter-parser.ts +136 -0
- package/scripts/harness/traceability-model/presentation/cli/migrate-work-items-command-handler.ts +186 -0
- package/skills/quick-implementor/SKILL.md +17 -1
- package/templates/.husky/commit-msg +1 -0
|
@@ -3,16 +3,18 @@
|
|
|
3
3
|
* @unit traceability-model
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type {
|
|
7
|
-
import type {
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
import type { DesignDocumentPort } from "../../domain/ports/design-document-port.js";
|
|
7
|
+
import type { MetadataValidator } from "../../domain/services/metadata-validator.js";
|
|
8
|
+
import {
|
|
9
|
+
MetadataValidationResult,
|
|
10
|
+
type TraceabilityHarnessError,
|
|
11
|
+
} from "../../domain/value-objects/metadata-validation-result.js";
|
|
12
|
+
import type { ProjectRelativePath } from "../../domain/value-objects/project-relative-path.js";
|
|
13
|
+
import type { WorkItemFrontmatter } from "../../domain/value-objects/work-item-frontmatter.js";
|
|
14
|
+
import { WorkItemFrontmatterValidationError } from "../../domain/value-objects/work-item-frontmatter.js";
|
|
15
|
+
import type { MetadataValidationOutput } from "../dto/metadata-validation-output.js";
|
|
11
16
|
|
|
12
|
-
type DesignStoryAnnotationsValidator = Pick<
|
|
13
|
-
MetadataValidator,
|
|
14
|
-
'validateDesignDocument'
|
|
15
|
-
>;
|
|
17
|
+
type DesignStoryAnnotationsValidator = Pick<MetadataValidator, "validateDesignDocument">;
|
|
16
18
|
|
|
17
19
|
const toOutput = (
|
|
18
20
|
filePath: ProjectRelativePath,
|
|
@@ -25,13 +27,63 @@ const toOutput = (
|
|
|
25
27
|
warnings: result.warnings,
|
|
26
28
|
});
|
|
27
29
|
|
|
30
|
+
const toWorkItemFrontmatterError = (error: WorkItemFrontmatterValidationError): TraceabilityHarnessError =>
|
|
31
|
+
Object.freeze({
|
|
32
|
+
code: "L2-002",
|
|
33
|
+
severity: "error",
|
|
34
|
+
message: error.message,
|
|
35
|
+
suggestion: "WI frontmatter の id/type/severity/status 形式を確認してください",
|
|
36
|
+
fix_example: `---
|
|
37
|
+
id: WI-001
|
|
38
|
+
type: story
|
|
39
|
+
severity: normal
|
|
40
|
+
status: drafted
|
|
41
|
+
---`,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const mergeValidationResult = (
|
|
45
|
+
additionalErrors: readonly TraceabilityHarnessError[],
|
|
46
|
+
result: MetadataValidationResult,
|
|
47
|
+
): MetadataValidationResult => {
|
|
48
|
+
if (additionalErrors.length === 0) {
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return MetadataValidationResult.failure({
|
|
53
|
+
errors: Object.freeze([...additionalErrors, ...result.errors]),
|
|
54
|
+
warnings: result.warnings,
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const STORY_ID_REQUIRED_MESSAGE = "@story-id は必須です";
|
|
59
|
+
|
|
60
|
+
const acceptWorkItemFrontmatterAsStoryTrace = (
|
|
61
|
+
filePath: ProjectRelativePath,
|
|
62
|
+
frontmatter: WorkItemFrontmatter | null,
|
|
63
|
+
result: MetadataValidationResult,
|
|
64
|
+
): MetadataValidationResult => {
|
|
65
|
+
const isWorkItemPath = /(?:^|\/)_cross\/WI-\d+(?:\/|$)/.test(filePath.toString());
|
|
66
|
+
if (frontmatter === null && !isWorkItemPath) {
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const errors = result.errors.filter((error) => error.message !== STORY_ID_REQUIRED_MESSAGE);
|
|
71
|
+
if (errors.length === result.errors.length) {
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
if (errors.length === 0) {
|
|
75
|
+
return MetadataValidationResult.success({ warnings: result.warnings });
|
|
76
|
+
}
|
|
77
|
+
return MetadataValidationResult.failure({ errors, warnings: result.warnings });
|
|
78
|
+
};
|
|
79
|
+
|
|
28
80
|
export class DesignDocumentReadApplicationError extends Error {
|
|
29
81
|
readonly filePath: string;
|
|
30
82
|
readonly cause: unknown;
|
|
31
83
|
|
|
32
84
|
constructor(filePath: string, cause: unknown) {
|
|
33
85
|
super(`design document read failed: ${filePath}`);
|
|
34
|
-
this.name =
|
|
86
|
+
this.name = "DesignDocumentReadApplicationError";
|
|
35
87
|
this.filePath = filePath;
|
|
36
88
|
this.cause = cause;
|
|
37
89
|
}
|
|
@@ -51,18 +103,30 @@ export class ValidateDesignStoryAnnotationsUseCase {
|
|
|
51
103
|
this.validator = deps.validator;
|
|
52
104
|
}
|
|
53
105
|
|
|
54
|
-
async execute(
|
|
55
|
-
filePaths: readonly ProjectRelativePath[],
|
|
56
|
-
): Promise<readonly Readonly<MetadataValidationOutput>[]> {
|
|
106
|
+
async execute(filePaths: readonly ProjectRelativePath[]): Promise<readonly Readonly<MetadataValidationOutput>[]> {
|
|
57
107
|
const results: Readonly<MetadataValidationOutput>[] = [];
|
|
58
108
|
|
|
59
109
|
for (const filePath of filePaths) {
|
|
60
110
|
try {
|
|
61
|
-
if (typeof this.designDocumentPort.readFrontmatterFlags !==
|
|
62
|
-
throw new Error(
|
|
111
|
+
if (typeof this.designDocumentPort.readFrontmatterFlags !== "function") {
|
|
112
|
+
throw new Error("readFrontmatterFlags is not implemented");
|
|
63
113
|
}
|
|
64
|
-
if (typeof this.designDocumentPort.readStoryAnnotations !==
|
|
65
|
-
throw new Error(
|
|
114
|
+
if (typeof this.designDocumentPort.readStoryAnnotations !== "function") {
|
|
115
|
+
throw new Error("readStoryAnnotations is not implemented");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const workItemFrontmatterErrors: TraceabilityHarnessError[] = [];
|
|
119
|
+
let workItemFrontmatter: WorkItemFrontmatter | null = null;
|
|
120
|
+
if (typeof this.designDocumentPort.readWorkItemFrontmatter === "function") {
|
|
121
|
+
try {
|
|
122
|
+
workItemFrontmatter = await this.designDocumentPort.readWorkItemFrontmatter(filePath);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (error instanceof WorkItemFrontmatterValidationError) {
|
|
125
|
+
workItemFrontmatterErrors.push(toWorkItemFrontmatterError(error));
|
|
126
|
+
} else {
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
66
130
|
}
|
|
67
131
|
|
|
68
132
|
const flags = await this.designDocumentPort.readFrontmatterFlags(filePath);
|
|
@@ -72,7 +136,8 @@ export class ValidateDesignStoryAnnotationsUseCase {
|
|
|
72
136
|
annotations,
|
|
73
137
|
flags,
|
|
74
138
|
});
|
|
75
|
-
|
|
139
|
+
const resultWithWorkItemTrace = acceptWorkItemFrontmatterAsStoryTrace(filePath, workItemFrontmatter, result);
|
|
140
|
+
results.push(toOutput(filePath, mergeValidationResult(workItemFrontmatterErrors, resultWithWorkItemTrace)));
|
|
76
141
|
} catch (error) {
|
|
77
142
|
throw new DesignDocumentReadApplicationError(filePath.toString(), error);
|
|
78
143
|
}
|
|
@@ -5,19 +5,25 @@
|
|
|
5
5
|
* traceability-model ユニットの Composition Root。
|
|
6
6
|
* 全コンポーネントを生成・配線し、外部に公開するハンドラー群を返す。
|
|
7
7
|
*/
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { StoryIdAliasResolver } from
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
8
|
+
|
|
9
|
+
import { ApplyWorkItemMigrationUseCase } from "./application/usecases/apply-work-item-migration-usecase.js";
|
|
10
|
+
import { PlanWorkItemMigrationUseCase } from "./application/usecases/plan-work-item-migration-usecase.js";
|
|
11
|
+
import { ValidateDesignStoryAnnotationsUseCase } from "./application/usecases/validate-design-story-annotations-usecase.js";
|
|
12
|
+
import { ValidateImplementationMetadataUseCase } from "./application/usecases/validate-implementation-metadata-usecase.js";
|
|
13
|
+
import { ValidateTestStoryMetadataUseCase } from "./application/usecases/validate-test-story-metadata-usecase.js";
|
|
14
|
+
import { MetadataValidator } from "./domain/services/metadata-validator.js";
|
|
15
|
+
import { StoryIdAliasResolver } from "./domain/services/story-id-alias-resolver.js";
|
|
16
|
+
import { TraceabilityChainBuilder } from "./domain/services/traceability-chain-builder.js";
|
|
17
|
+
import { ProjectRelativePath } from "./domain/value-objects/project-relative-path.js";
|
|
18
|
+
import { FileSystemInceptionPlanGateway } from "./infrastructure/gateways/file-system-inception-plan-gateway.js";
|
|
19
|
+
import { FileSystemMetadataReader } from "./infrastructure/gateways/file-system-metadata-reader.js";
|
|
20
|
+
import { FileSystemWorkItemMigrationApplyGateway } from "./infrastructure/gateways/file-system-work-item-migration-apply-gateway.js";
|
|
21
|
+
import { FileSystemWorkItemMigrationSourceGateway } from "./infrastructure/gateways/file-system-work-item-migration-source-gateway.js";
|
|
22
|
+
import { MarkdownDesignDocumentGateway } from "./infrastructure/gateways/markdown-design-document-gateway.js";
|
|
23
|
+
import { MarkdownStoryCatalogGateway } from "./infrastructure/gateways/markdown-story-catalog-gateway.js";
|
|
24
|
+
import { MarkdownUnitDefinitionGateway } from "./infrastructure/gateways/markdown-unit-definition-gateway.js";
|
|
25
|
+
import { MigrateWorkItemsCommandHandler } from "./presentation/cli/migrate-work-items-command-handler.js";
|
|
26
|
+
import { ValidateMetadataCommandHandler } from "./presentation/cli/validate-metadata-command-handler.js";
|
|
21
27
|
|
|
22
28
|
export function createTraceabilityModelModule(rootDir: string) {
|
|
23
29
|
// Infrastructure gateways
|
|
@@ -26,6 +32,8 @@ export function createTraceabilityModelModule(rootDir: string) {
|
|
|
26
32
|
const metadataReader = new FileSystemMetadataReader({ rootDir });
|
|
27
33
|
const designDocument = new MarkdownDesignDocumentGateway({ rootDir });
|
|
28
34
|
const inceptionPlan = new FileSystemInceptionPlanGateway({ rootDir });
|
|
35
|
+
const workItemMigrationSource = new FileSystemWorkItemMigrationSourceGateway({ rootDir });
|
|
36
|
+
const workItemMigrationApply = new FileSystemWorkItemMigrationApplyGateway({ rootDir });
|
|
29
37
|
|
|
30
38
|
// Domain services
|
|
31
39
|
const metadataValidator = new MetadataValidator({
|
|
@@ -42,34 +50,44 @@ export function createTraceabilityModelModule(rootDir: string) {
|
|
|
42
50
|
const storyIdAliasResolver = new StoryIdAliasResolver(storyCatalog);
|
|
43
51
|
|
|
44
52
|
// Usecases
|
|
45
|
-
const validateImplementationMetadataUseCase =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
const validateImplementationMetadataUseCase = new ValidateImplementationMetadataUseCase({
|
|
54
|
+
metadataReaderPort: metadataReader,
|
|
55
|
+
validator: metadataValidator,
|
|
56
|
+
});
|
|
57
|
+
const validateDesignStoryAnnotationsUseCase = new ValidateDesignStoryAnnotationsUseCase({
|
|
58
|
+
designDocumentPort: designDocument,
|
|
59
|
+
validator: metadataValidator,
|
|
60
|
+
});
|
|
61
|
+
const validateTestStoryMetadataUseCase = new ValidateTestStoryMetadataUseCase({
|
|
62
|
+
metadataReaderPort: metadataReader,
|
|
63
|
+
validator: metadataValidator,
|
|
64
|
+
});
|
|
65
|
+
const planWorkItemMigrationUseCase = new PlanWorkItemMigrationUseCase({
|
|
66
|
+
sourcePort: workItemMigrationSource,
|
|
67
|
+
});
|
|
68
|
+
const applyWorkItemMigrationUseCase = new ApplyWorkItemMigrationUseCase({
|
|
69
|
+
planWorkItemMigrationUseCase,
|
|
70
|
+
applyPort: workItemMigrationApply,
|
|
71
|
+
});
|
|
60
72
|
|
|
61
73
|
// Presentation handlers
|
|
62
74
|
const validateMetadataCommandHandler = new ValidateMetadataCommandHandler({
|
|
63
75
|
validateImplementationMetadataUseCase,
|
|
64
76
|
validateDesignStoryAnnotationsUseCase,
|
|
65
77
|
validateTestStoryMetadataUseCase,
|
|
66
|
-
createProjectRelativePath: (value: string) =>
|
|
67
|
-
|
|
78
|
+
createProjectRelativePath: (value: string) => ProjectRelativePath.create(value),
|
|
79
|
+
});
|
|
80
|
+
const migrateWorkItemsCommandHandler = new MigrateWorkItemsCommandHandler({
|
|
81
|
+
planWorkItemMigrationUseCase,
|
|
82
|
+
applyWorkItemMigrationUseCase,
|
|
68
83
|
});
|
|
69
84
|
|
|
70
85
|
return {
|
|
71
86
|
validateMetadataCommandHandler,
|
|
87
|
+
migrateWorkItemsCommandHandler,
|
|
72
88
|
// expose key services for cross-unit use
|
|
73
89
|
storyCatalog,
|
|
90
|
+
traceabilityChainBuilder,
|
|
91
|
+
storyIdAliasResolver,
|
|
74
92
|
} as const;
|
|
75
93
|
}
|
|
@@ -3,22 +3,16 @@
|
|
|
3
3
|
* @unit traceability-model
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { ProjectRelativePathLike } from
|
|
7
|
-
import type { DesignDocumentFlags } from
|
|
8
|
-
import type { StoryIdAnnotation } from
|
|
6
|
+
import type { ProjectRelativePathLike } from "../value-objects/chain-link.js";
|
|
7
|
+
import type { DesignDocumentFlags } from "../value-objects/design-document-flags.js";
|
|
8
|
+
import type { StoryIdAnnotation } from "../value-objects/story-id-annotation.js";
|
|
9
|
+
import type { WorkItemFrontmatter } from "../value-objects/work-item-frontmatter.js";
|
|
9
10
|
|
|
10
11
|
export interface DesignDocumentPort {
|
|
11
12
|
listByUnit?(unitName: string): Promise<readonly ProjectRelativePathLike[]>;
|
|
12
|
-
findConstructionDocuments?(
|
|
13
|
-
|
|
14
|
-
): Promise<readonly
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
): Promise<readonly StoryIdAnnotation[]>;
|
|
18
|
-
readStoryIdAnnotations?(
|
|
19
|
-
filePath: ProjectRelativePathLike,
|
|
20
|
-
): Promise<readonly StoryIdAnnotation[]>;
|
|
21
|
-
readFrontmatterFlags?(
|
|
22
|
-
filePath: ProjectRelativePathLike,
|
|
23
|
-
): Promise<DesignDocumentFlags>;
|
|
13
|
+
findConstructionDocuments?(unitName: string): Promise<readonly ProjectRelativePathLike[]>;
|
|
14
|
+
readStoryAnnotations?(filePath: ProjectRelativePathLike): Promise<readonly StoryIdAnnotation[]>;
|
|
15
|
+
readStoryIdAnnotations?(filePath: ProjectRelativePathLike): Promise<readonly StoryIdAnnotation[]>;
|
|
16
|
+
readFrontmatterFlags?(filePath: ProjectRelativePathLike): Promise<DesignDocumentFlags>;
|
|
17
|
+
readWorkItemFrontmatter?(filePath: ProjectRelativePathLike): Promise<WorkItemFrontmatter | null>;
|
|
24
18
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// @unit traceability-model
|
|
2
|
+
// @layer domain
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
WorkItemMigrationAppliedCandidate,
|
|
6
|
+
WorkItemMigrationCandidate,
|
|
7
|
+
} from "../value-objects/work-item-migration-candidate.js";
|
|
8
|
+
|
|
9
|
+
export interface WorkItemMigrationApplyPort {
|
|
10
|
+
apply(candidate: WorkItemMigrationCandidate): Promise<WorkItemMigrationAppliedCandidate>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @unit traceability-model
|
|
2
|
+
// @layer domain
|
|
3
|
+
|
|
4
|
+
import type { LegacyIssueDirectory } from "../value-objects/work-item-migration-candidate.js";
|
|
5
|
+
|
|
6
|
+
export interface WorkItemMigrationSourcePort {
|
|
7
|
+
listLegacyIssueDirectories(): Promise<readonly LegacyIssueDirectory[]>;
|
|
8
|
+
listExistingWorkItemIds(): Promise<readonly string[]>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// @unit traceability-model
|
|
2
|
+
// @layer domain
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
LegacyIssueDirectory,
|
|
6
|
+
WorkItemMigrationCandidate,
|
|
7
|
+
WorkItemMigrationPlan,
|
|
8
|
+
} from "../value-objects/work-item-migration-candidate.js";
|
|
9
|
+
|
|
10
|
+
const ISSUE_ID_PATTERN = /^ISSUE-(\d+)$/;
|
|
11
|
+
const WI_ID_PATTERN = /^WI-(\d+)$/;
|
|
12
|
+
const H_ID_PATTERN = /^H\d{2}-\d{2}$/;
|
|
13
|
+
|
|
14
|
+
type LegacyIdKind = "wi" | "issue" | "h" | "unknown";
|
|
15
|
+
type WorkItemKind = "issue" | "story";
|
|
16
|
+
|
|
17
|
+
export class WorkItemMigrationPlanner {
|
|
18
|
+
plan(
|
|
19
|
+
entries: readonly LegacyIssueDirectory[],
|
|
20
|
+
existingWorkItemIds: readonly string[] = [],
|
|
21
|
+
): WorkItemMigrationPlan {
|
|
22
|
+
const candidates: WorkItemMigrationCandidate[] = [];
|
|
23
|
+
const warnings: string[] = [];
|
|
24
|
+
|
|
25
|
+
const usedNumbers = new Set<number>();
|
|
26
|
+
for (const id of existingWorkItemIds) {
|
|
27
|
+
const num = parseWorkItemNumber(id);
|
|
28
|
+
if (num !== null) usedNumbers.add(num);
|
|
29
|
+
}
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
const fixed = toFixedWorkItemId(entry.legacyId);
|
|
32
|
+
if (fixed === null) continue;
|
|
33
|
+
const num = parseWorkItemNumber(fixed);
|
|
34
|
+
if (num !== null) usedNumbers.add(num);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let cursor = 1;
|
|
38
|
+
|
|
39
|
+
for (const entry of entries) {
|
|
40
|
+
const kind = classifyLegacyId(entry.legacyId);
|
|
41
|
+
let nextId: string;
|
|
42
|
+
let workItemKind: WorkItemKind;
|
|
43
|
+
|
|
44
|
+
if (kind === "wi" || kind === "issue") {
|
|
45
|
+
const fixed = toFixedWorkItemId(entry.legacyId);
|
|
46
|
+
if (fixed === null) {
|
|
47
|
+
warnings.push(`unsupported legacy id: ${entry.legacyId}`);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
nextId = fixed;
|
|
51
|
+
workItemKind = "issue";
|
|
52
|
+
} else if (kind === "h") {
|
|
53
|
+
while (usedNumbers.has(cursor)) cursor++;
|
|
54
|
+
nextId = `WI-${String(cursor).padStart(3, "0")}`;
|
|
55
|
+
usedNumbers.add(cursor);
|
|
56
|
+
cursor++;
|
|
57
|
+
workItemKind = "story";
|
|
58
|
+
} else {
|
|
59
|
+
warnings.push(`unsupported legacy id: ${entry.legacyId}`);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const affects = extractAffects(entry.content);
|
|
64
|
+
if (entry.scope === "cross" && affects.length === 0) {
|
|
65
|
+
warnings.push(`${entry.legacyId}: cross-unit WI requires affects`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const targetPath =
|
|
69
|
+
entry.scope === "cross" ? `docs/inception/_cross/${nextId}` : `docs/inception/${entry.unitName}/${nextId}`;
|
|
70
|
+
|
|
71
|
+
candidates.push({
|
|
72
|
+
legacyId: entry.legacyId,
|
|
73
|
+
nextId,
|
|
74
|
+
sourcePath: entry.sourcePath,
|
|
75
|
+
targetPath,
|
|
76
|
+
scope: entry.scope,
|
|
77
|
+
...(entry.unitName !== undefined ? { unitName: entry.unitName } : {}),
|
|
78
|
+
descriptionFileName: entry.descriptionFileName,
|
|
79
|
+
conflict: entry.targetExists,
|
|
80
|
+
frontmatterPreview: buildFrontmatterPreview({
|
|
81
|
+
nextId,
|
|
82
|
+
legacyId: entry.legacyId,
|
|
83
|
+
severity: extractSeverity(entry.content),
|
|
84
|
+
affects,
|
|
85
|
+
includeAffects: entry.scope === "cross",
|
|
86
|
+
workItemKind,
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return Object.freeze({
|
|
92
|
+
candidates: Object.freeze(candidates),
|
|
93
|
+
warnings: Object.freeze(warnings),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function classifyLegacyId(legacyId: string): LegacyIdKind {
|
|
99
|
+
if (WI_ID_PATTERN.test(legacyId)) return "wi";
|
|
100
|
+
if (ISSUE_ID_PATTERN.test(legacyId)) return "issue";
|
|
101
|
+
if (H_ID_PATTERN.test(legacyId)) return "h";
|
|
102
|
+
return "unknown";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function toFixedWorkItemId(legacyId: string): string | null {
|
|
106
|
+
if (WI_ID_PATTERN.test(legacyId)) return legacyId;
|
|
107
|
+
|
|
108
|
+
const match = ISSUE_ID_PATTERN.exec(legacyId);
|
|
109
|
+
if (!match) return null;
|
|
110
|
+
|
|
111
|
+
return `WI-${match[1].padStart(3, "0")}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function parseWorkItemNumber(id: string): number | null {
|
|
115
|
+
const match = WI_ID_PATTERN.exec(id);
|
|
116
|
+
if (!match) return null;
|
|
117
|
+
const num = Number.parseInt(match[1], 10);
|
|
118
|
+
return Number.isFinite(num) ? num : null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function extractSeverity(content: string): "trivial" | "normal" | "high" {
|
|
122
|
+
if (/深刻度\*\*:\s*High/i.test(content)) return "high";
|
|
123
|
+
if (/深刻度\*\*:\s*Trivial/i.test(content)) return "trivial";
|
|
124
|
+
return "normal";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function extractAffects(content: string): readonly string[] {
|
|
128
|
+
const match = /\*\*影響Unit\*\*:\s*(.+)/.exec(content);
|
|
129
|
+
if (!match) return Object.freeze([]);
|
|
130
|
+
|
|
131
|
+
return Object.freeze(
|
|
132
|
+
match[1]
|
|
133
|
+
.split(",")
|
|
134
|
+
.map((value) => value.trim())
|
|
135
|
+
.filter((value) => value.length > 0),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function buildFrontmatterPreview(args: {
|
|
140
|
+
readonly nextId: string;
|
|
141
|
+
readonly legacyId: string;
|
|
142
|
+
readonly severity: "trivial" | "normal" | "high";
|
|
143
|
+
readonly affects: readonly string[];
|
|
144
|
+
readonly includeAffects: boolean;
|
|
145
|
+
readonly workItemKind: WorkItemKind;
|
|
146
|
+
}): string {
|
|
147
|
+
const lines = [
|
|
148
|
+
"---",
|
|
149
|
+
`id: ${args.nextId}`,
|
|
150
|
+
`type: ${args.workItemKind}`,
|
|
151
|
+
`severity: ${args.severity}`,
|
|
152
|
+
"status: drafted",
|
|
153
|
+
`legacy_id: ${args.legacyId}`,
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
if (args.includeAffects && args.affects.length > 0) {
|
|
157
|
+
lines.push(`affects: [${args.affects.join(", ")}]`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
lines.push("---");
|
|
161
|
+
return lines.join("\n");
|
|
162
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// @unit traceability-model
|
|
2
|
+
// @layer domain
|
|
3
|
+
/**
|
|
4
|
+
* WorkItemFrontmatter — 設計文書 frontmatter から抽出した WI メタデータ(H03-04 / ISSUE-026 Phase A-2)。
|
|
5
|
+
*
|
|
6
|
+
* 既存 `parseFrontmatterFlags` (initial_creation) とは独立した型系統で、
|
|
7
|
+
* WI 一本化の識別と type 別振る舞いの基盤を提供する。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type WorkItemType = 'story' | 'issue' | 'fix' | 'refactor' | 'chore';
|
|
11
|
+
export type WorkItemSeverity = 'trivial' | 'normal' | 'high';
|
|
12
|
+
export type WorkItemStatus =
|
|
13
|
+
| 'drafted'
|
|
14
|
+
| 'reflected'
|
|
15
|
+
| 'implemented'
|
|
16
|
+
| 'tested';
|
|
17
|
+
|
|
18
|
+
export interface WorkItemFrontmatter {
|
|
19
|
+
readonly id: string;
|
|
20
|
+
readonly type: WorkItemType;
|
|
21
|
+
readonly affects?: readonly string[];
|
|
22
|
+
readonly severity?: WorkItemSeverity;
|
|
23
|
+
readonly status?: WorkItemStatus;
|
|
24
|
+
readonly source?: string;
|
|
25
|
+
readonly legacyId?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class WorkItemFrontmatterValidationError extends Error {
|
|
29
|
+
constructor(reason: string) {
|
|
30
|
+
super(`WorkItem frontmatter が不正です: ${reason}`);
|
|
31
|
+
this.name = 'WorkItemFrontmatterValidationError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const WORK_ITEM_ID_PATTERN =
|
|
36
|
+
/^(?:WI-\d+|H(?:\d{2}|F\d+)-\d{2}|ISSUE-\d+)$/;
|
|
37
|
+
|
|
38
|
+
export const WORK_ITEM_TYPES: ReadonlySet<WorkItemType> = new Set([
|
|
39
|
+
'story',
|
|
40
|
+
'issue',
|
|
41
|
+
'fix',
|
|
42
|
+
'refactor',
|
|
43
|
+
'chore',
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
export const WORK_ITEM_SEVERITIES: ReadonlySet<WorkItemSeverity> = new Set([
|
|
47
|
+
'trivial',
|
|
48
|
+
'normal',
|
|
49
|
+
'high',
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
export const WORK_ITEM_STATUSES: ReadonlySet<WorkItemStatus> = new Set([
|
|
53
|
+
'drafted',
|
|
54
|
+
'reflected',
|
|
55
|
+
'implemented',
|
|
56
|
+
'tested',
|
|
57
|
+
]);
|
package/scripts/harness/traceability-model/domain/value-objects/work-item-migration-candidate.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// @unit traceability-model
|
|
2
|
+
// @layer domain
|
|
3
|
+
|
|
4
|
+
export type WorkItemMigrationScope = "cross" | "unit";
|
|
5
|
+
export type WorkItemDescriptionFileName = "description.md" | "issue_description.md";
|
|
6
|
+
|
|
7
|
+
export interface LegacyIssueDirectory {
|
|
8
|
+
readonly legacyId: string;
|
|
9
|
+
readonly sourcePath: string;
|
|
10
|
+
readonly scope: WorkItemMigrationScope;
|
|
11
|
+
readonly unitName?: string;
|
|
12
|
+
readonly descriptionFileName: WorkItemDescriptionFileName | null;
|
|
13
|
+
readonly content: string;
|
|
14
|
+
readonly targetExists: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface WorkItemMigrationCandidate {
|
|
18
|
+
readonly legacyId: string;
|
|
19
|
+
readonly nextId: string;
|
|
20
|
+
readonly sourcePath: string;
|
|
21
|
+
readonly targetPath: string;
|
|
22
|
+
readonly scope: WorkItemMigrationScope;
|
|
23
|
+
readonly unitName?: string;
|
|
24
|
+
readonly descriptionFileName: WorkItemDescriptionFileName | null;
|
|
25
|
+
readonly conflict: boolean;
|
|
26
|
+
readonly frontmatterPreview: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface WorkItemMigrationPlan {
|
|
30
|
+
readonly candidates: readonly WorkItemMigrationCandidate[];
|
|
31
|
+
readonly warnings: readonly string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface WorkItemMigrationAppliedCandidate {
|
|
35
|
+
readonly legacyId: string;
|
|
36
|
+
readonly nextId: string;
|
|
37
|
+
readonly sourcePath: string;
|
|
38
|
+
readonly targetPath: string;
|
|
39
|
+
readonly descriptionPath: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface WorkItemMigrationApplyResult {
|
|
43
|
+
readonly applied: readonly WorkItemMigrationAppliedCandidate[];
|
|
44
|
+
readonly skipped: readonly WorkItemMigrationCandidate[];
|
|
45
|
+
readonly warnings: readonly string[];
|
|
46
|
+
readonly blocked: boolean;
|
|
47
|
+
}
|