phasegate 0.16.0 → 0.31.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/README.ja.md +71 -39
- package/README.md +69 -9
- package/docs/ADR/ADR-013-story-reflection-gate.md +100 -0
- package/docs/guide/configuration.md +244 -7
- package/package.json +4 -2
- package/scripts/harness/adr-foundation/domain/ports/adr-document-parser-port.ts +1 -0
- package/scripts/harness/adr-foundation/domain/ports/adr-frontmatter-parser-port.ts +1 -0
- package/scripts/harness/adr-foundation/domain/ports/adr-repository-port.ts +1 -0
- package/scripts/harness/adr-foundation/domain/value-objects/adr-status.ts +6 -6
- package/scripts/harness/agent-integration/application/dto/handle-pre-tool-use-dto.ts +3 -1
- package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +108 -2
- package/scripts/harness/agent-integration/domain/ports/config-query-port.ts +2 -0
- package/scripts/harness/agent-integration/domain/ports/phase-gate-query-port.ts +1 -1
- package/scripts/harness/agent-integration/domain/ports/story-reflection-query-port.ts +8 -0
- package/scripts/harness/agent-integration/domain/services/bash-write-target-extractor.ts +258 -0
- package/scripts/harness/agent-integration/domain/services/hook-to-cli-translator.ts +21 -4
- package/scripts/harness/agent-integration/domain/value-objects/protected-file-list.ts +20 -0
- package/scripts/harness/agent-integration/domain/value-objects/story-reflection-query-result.ts +63 -0
- package/scripts/harness/agent-integration/infrastructure/adapters/file-system-story-reflection-query-adapter.ts +122 -0
- package/scripts/harness/agent-integration/infrastructure/adapters/harness-config-config-query-adapter.ts +20 -0
- package/scripts/harness/agent-integration/infrastructure/adapters/phase-gate-query-adapter.ts +17 -3
- package/scripts/harness/agent-integration/presentation/pre-tool-use-hook.ts +29 -2
- package/scripts/harness/biome-ast-engine/domain/services/lint-runner.ts +24 -0
- package/scripts/harness/biome-ast-engine/domain/services/rule-definition-registry.ts +1 -1
- package/scripts/harness/biome-ast-engine/domain/value-objects/source-module-snapshot.ts +5 -2
- package/scripts/harness/biome-ast-engine/infrastructure/adapters/node-workspace-file-adapter.ts +1 -1
- package/scripts/harness/biome-ast-engine/infrastructure/adapters/typescript-source-module-analyzer-adapter.ts +1 -1
- package/scripts/harness/biome-ast-engine/infrastructure/parsers/unit-comment-parser.ts +12 -7
- package/scripts/harness/ci-governance/composition-root.ts +1 -0
- package/scripts/harness/config-foundation/application/mappers/phase-config-section-mapper.ts +44 -0
- package/scripts/harness/config-foundation/application/usecases/load-resolved-config-use-case.ts +20 -1
- package/scripts/harness/config-foundation/domain/harness-config.ts +2 -1
- package/scripts/harness/config-foundation/domain/value-objects/phase-dependencies-config.ts +18 -4
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +185 -10
- package/scripts/harness/config-foundation/infrastructure/validators/ajv-config-schema-validator.ts +2 -1
- package/scripts/harness/harness-api/application/dto/command-dispatch-input.ts +1 -0
- package/scripts/harness/harness-api/application/dto/command-dispatch-output.ts +1 -0
- package/scripts/harness/harness-api/application/dto/exit-code-decision-input.ts +1 -0
- package/scripts/harness/harness-api/application/dto/exit-code-decision-output.ts +1 -0
- package/scripts/harness/harness-api/application/dto/harness-api-response-contract.ts +1 -0
- package/scripts/harness/harness-api/application/dto/initialize-registry-input.ts +1 -0
- package/scripts/harness/harness-api/application/dto/registry-summary-output.ts +1 -0
- package/scripts/harness/harness-api/application/dto/status-derivation-input.ts +1 -0
- package/scripts/harness/harness-api/application/mappers/harness-api-response-mapper.ts +1 -0
- package/scripts/harness/harness-api/application/usecases/decide-exit-code-usecase.ts +1 -0
- package/scripts/harness/harness-api/application/usecases/derive-harness-status-usecase.ts +1 -0
- package/scripts/harness/harness-api/application/usecases/dispatch-command-usecase.ts +1 -0
- package/scripts/harness/harness-api/application/usecases/initialize-command-registry-usecase.ts +1 -0
- package/scripts/harness/harness-api/composition-root.ts +1 -0
- package/scripts/harness/harness-api/domain/ports/artifact-scanner-port.ts +1 -0
- package/scripts/harness/harness-api/domain/ports/biome-lint-port.ts +1 -0
- package/scripts/harness/harness-api/domain/ports/config-query-port.ts +1 -0
- package/scripts/harness/harness-api/domain/ports/impact-analysis-port.ts +1 -0
- package/scripts/harness/harness-api/domain/ports/phase-gate-query-port.ts +1 -0
- package/scripts/harness/harness-api/domain/ports/validator-execution-port.ts +1 -0
- package/scripts/harness/harness-api/domain/services/command-dispatch-service.ts +1 -0
- package/scripts/harness/harness-api/domain/services/command-registry.ts +1 -0
- package/scripts/harness/harness-api/domain/services/status-derivation-service.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/artifact-scan-result.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/check-ready-result.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/ci-check-result.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/cli-command-definition.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/command-input-spec.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/drift-report-summary.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/exit-code-spec.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/harness-api-response.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/harness-status-summary.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/layer-health.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/phase-info.ts +1 -0
- package/scripts/harness/harness-api/index.ts +1 -0
- package/scripts/harness/harness-api/infrastructure/adapters/biome-ast-engine-lint-adapter.ts +1 -0
- package/scripts/harness/harness-api/infrastructure/adapters/file-system-artifact-scanner-adapter.ts +1 -0
- package/scripts/harness/harness-api/infrastructure/adapters/harness-config-query-adapter.ts +1 -0
- package/scripts/harness/harness-api/infrastructure/adapters/nyquist-validation-impact-analysis-adapter.ts +1 -0
- package/scripts/harness/harness-api/infrastructure/adapters/phase-dependency-model-query-adapter.ts +1 -0
- package/scripts/harness/harness-api/infrastructure/adapters/validator-system-execution-adapter.ts +1 -0
- package/scripts/harness/harness-api/presentation/dto/cli-output-options.ts +1 -0
- package/scripts/harness/harness-api/presentation/formatters/harness-api-json-formatter.ts +1 -0
- package/scripts/harness/harness-api/presentation/handlers/check-phase-handler.ts +1 -0
- package/scripts/harness/harness-api/presentation/handlers/check-ready-handler.ts +1 -0
- package/scripts/harness/harness-api/presentation/handlers/ci-check-handler.ts +1 -0
- package/scripts/harness/harness-api/presentation/handlers/complete-check-handler.ts +1 -0
- package/scripts/harness/harness-api/presentation/handlers/detect-drift-handler.ts +1 -0
- package/scripts/harness/harness-api/presentation/handlers/impact-analysis-handler.ts +1 -0
- package/scripts/harness/harness-api/presentation/handlers/lint-handler.ts +1 -0
- package/scripts/harness/harness-api/presentation/handlers/status-handler.ts +1 -0
- package/scripts/harness/harness-error/infrastructure/adapters/legacy-error-reporter-adapter.ts +7 -1
- package/scripts/harness/integrations/pre-commit.ts +1 -0
- package/scripts/harness/main.ts +154 -34
- package/scripts/harness/phase-dependency-model/application/dto/resolve-gate-result-dto.ts +14 -0
- package/scripts/harness/phase-dependency-model/application/services/phase-gate-result-mapper.ts +20 -0
- package/scripts/harness/phase-dependency-model/application/usecases/check-story-reflection-usecase.ts +42 -0
- package/scripts/harness/phase-dependency-model/application/usecases/resolve-gate-usecase.ts +161 -0
- package/scripts/harness/phase-dependency-model/composition-root.ts +57 -1
- package/scripts/harness/phase-dependency-model/domain/definitions/{default-phase-dependencies.ts → full-phase-dependencies.ts} +3 -3
- package/scripts/harness/phase-dependency-model/domain/definitions/{default-phase-nodes.ts → full-phase-nodes.ts} +1 -1
- package/scripts/harness/phase-dependency-model/domain/definitions/full-story-reflection-defaults.ts +28 -0
- package/scripts/harness/phase-dependency-model/domain/definitions/minimal-phase-dependencies.ts +32 -0
- package/scripts/harness/phase-dependency-model/domain/definitions/minimal-phase-nodes.ts +50 -0
- package/scripts/harness/phase-dependency-model/domain/definitions/minimal-story-reflection-defaults.ts +8 -0
- package/scripts/harness/phase-dependency-model/domain/definitions/standard-phase-dependencies.ts +39 -0
- package/scripts/harness/phase-dependency-model/domain/definitions/standard-phase-nodes.ts +118 -0
- package/scripts/harness/phase-dependency-model/domain/definitions/standard-story-reflection-defaults.ts +23 -0
- package/scripts/harness/phase-dependency-model/domain/models/phase-structure.ts +95 -23
- package/scripts/harness/phase-dependency-model/domain/ports/glob-matcher-port.ts +8 -0
- package/scripts/harness/phase-dependency-model/domain/ports/phase-config-provider-port.ts +2 -0
- package/scripts/harness/phase-dependency-model/domain/ports/story-annotation-verifier-port.ts +14 -0
- package/scripts/harness/phase-dependency-model/domain/ports/story-reflection-file-system-port.ts +16 -0
- package/scripts/harness/phase-dependency-model/domain/services/gate-graph.ts +223 -0
- package/scripts/harness/phase-dependency-model/domain/services/story-reflection-checker.ts +66 -0
- package/scripts/harness/phase-dependency-model/domain/values/gate-definition.ts +127 -0
- package/scripts/harness/phase-dependency-model/domain/values/gate-name.ts +40 -0
- package/scripts/harness/phase-dependency-model/domain/values/gate-story-annotation.ts +39 -0
- package/scripts/harness/phase-dependency-model/domain/values/phase-customization-policy.ts +16 -4
- package/scripts/harness/phase-dependency-model/domain/values/story-reflection-config.ts +44 -0
- package/scripts/harness/phase-dependency-model/domain/values/story-reflection-mapping.ts +91 -0
- package/scripts/harness/phase-dependency-model/domain/values/story-reflection-result.ts +55 -0
- package/scripts/harness/phase-dependency-model/infrastructure/adapters/picomatch-glob-matcher.ts +17 -0
- package/scripts/harness/phase-dependency-model/infrastructure/adapters/story-annotation-verifier-adapter.ts +46 -0
- package/scripts/harness/phase-dependency-model/infrastructure/config/custom-gates-config-parser.ts +22 -0
- package/scripts/harness/phase-dependency-model/infrastructure/config/harness-config-phase-config-provider.ts +78 -2
- package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.ts +94 -0
- package/scripts/harness/phase-dependency-model/presentation/cli/check-phase-gate-command-handler.ts +12 -4
- package/scripts/harness/phase-dependency-model/presentation/cli/story-reflection-status-presenter.ts +87 -0
- package/scripts/harness/regression-suite/application/dto/analyze-migration-input.ts +1 -0
- package/scripts/harness/regression-suite/application/dto/analyze-migration-output.ts +1 -0
- package/scripts/harness/regression-suite/application/dto/configure-ci-gate-input.ts +1 -0
- package/scripts/harness/regression-suite/application/dto/configure-ci-gate-output.ts +1 -0
- package/scripts/harness/regression-suite/application/dto/migrate-v0-tests-input.ts +1 -0
- package/scripts/harness/regression-suite/application/dto/migrate-v0-tests-output.ts +1 -0
- package/scripts/harness/regression-suite/application/dto/run-regression-suite-input.ts +1 -0
- package/scripts/harness/regression-suite/application/dto/run-regression-suite-output.ts +1 -0
- package/scripts/harness/regression-suite/application/mappers/migration-mapping-mapper.ts +1 -0
- package/scripts/harness/regression-suite/application/mappers/test-execution-summary-mapper.ts +1 -0
- package/scripts/harness/regression-suite/application/usecases/analyze-v0-migration-usecase.ts +1 -0
- package/scripts/harness/regression-suite/application/usecases/configure-ci-gate-usecase.ts +1 -0
- package/scripts/harness/regression-suite/application/usecases/migrate-v0-tests-usecase.ts +1 -0
- package/scripts/harness/regression-suite/application/usecases/run-agent-independence-guard-usecase.ts +1 -0
- package/scripts/harness/regression-suite/application/usecases/run-gng-gate-regression-usecase.ts +1 -0
- package/scripts/harness/regression-suite/application/usecases/run-k-requirements-regression-usecase.ts +1 -0
- package/scripts/harness/regression-suite/application/usecases/run-k14-k15-regression-usecase.ts +1 -0
- package/scripts/harness/regression-suite/composition-root.ts +1 -0
- package/scripts/harness/regression-suite/domain/aggregates/v0-test-migration.ts +1 -0
- package/scripts/harness/regression-suite/domain/ports/ci-gate-result-writer-port.ts +1 -0
- package/scripts/harness/regression-suite/domain/ports/config-query-port.ts +1 -0
- package/scripts/harness/regression-suite/domain/ports/import-analyzer-port.ts +1 -0
- package/scripts/harness/regression-suite/domain/ports/migration-mapping-repository-port.ts +1 -0
- package/scripts/harness/regression-suite/domain/ports/suite-registry-port.ts +1 -0
- package/scripts/harness/regression-suite/domain/ports/test-runner-port.ts +1 -0
- package/scripts/harness/regression-suite/domain/ports/v0-spec-reader-port.ts +1 -0
- package/scripts/harness/regression-suite/domain/services/import-guard-service.ts +1 -0
- package/scripts/harness/regression-suite/domain/services/migration-analyzer.ts +1 -0
- package/scripts/harness/regression-suite/domain/services/regression-runner.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/agent-independence-test.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/biome-modification-spec.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/ci-gate-config.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/coverage-rate.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/gng-condition-test.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/import-violation.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/k-requirement-test.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/migration-mapping.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/regression-suite-definition.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/suite-id.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/test-execution-summary.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/test-failure-detail.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/v0-test-id.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/v1-test-path.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/adapters/biome-ast-import-analyzer-adapter.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/adapters/file-system-v0-spec-reader-adapter.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/adapters/harness-config-query-adapter.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/adapters/json-ci-gate-result-writer-adapter.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/adapters/markdown-migration-mapping-repository-adapter.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/adapters/static-suite-registry-adapter.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/adapters/vitest-test-runner-adapter.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/registry/agent-independence-suite-definition.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/registry/gng-gate-suite-definition.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/registry/k-requirements-suite-definition.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/registry/v0-migration-suite-definition.ts +1 -0
- package/scripts/harness/setup/skill-deployer.ts +82 -5
- package/scripts/harness/shared-kernel/harness-api.ts +1 -0
- package/scripts/harness/skill-quality/composition-root.ts +1 -0
- package/scripts/harness/skill-quality/infrastructure/adapters/ajv-lesson-artifact-schema-adapter.ts +5 -4
- package/scripts/harness/traceability-model/domain/services/metadata-validator.ts +12 -10
- package/scripts/harness/traceability-model/infrastructure/parsers/source-metadata-parser.ts +16 -2
- package/skills/cascade-updater/SKILL.md +16 -3
- package/skills/codebase-mapper/SKILL.md +9 -9
- package/skills/codex-delegator/SKILL.md +5 -56
- package/skills/consistency-checker/SKILL.md +5 -9
- package/skills/consistency-checker/references//343/203/201/343/202/247/343/203/203/343/202/257/343/203/252/343/202/271/343/203/210.md +6 -3
- package/skills/doc-freshness-checker/SKILL.md +6 -6
- package/skills/engineering-perspective/SKILL.md +2 -58
- package/skills/engineering-perspective/references/domain-model.md +4 -135
- package/skills/implementation-readiness-checker/SKILL.md +18 -72
- package/skills/it-test-designer/SKILL.md +25 -161
- package/skills/it-test-designer/references/it-test-design-template.md +131 -0
- package/skills/it-test-logic-designer/SKILL.md +13 -388
- package/skills/it-test-logic-designer/references/controller-test-patterns.md +100 -0
- package/skills/it-test-logic-designer/references/repository-test-patterns.md +206 -0
- package/skills/it-test-logic-designer/references/usecase-test-patterns.md +87 -0
- package/skills/pointer-validator/SKILL.md +4 -4
- package/skills/scenario-test-designer/SKILL.md +4 -68
- package/skills/scenario-test-designer/references/scenario-test-design-template.md +41 -0
- package/skills/scenario-test-designer/references/scenario-test-plan-template.md +35 -0
- package/skills/scenario-test-logic-designer/SKILL.md +21 -304
- package/skills/scenario-test-logic-designer/references/msw-patterns.md +36 -0
- package/skills/scenario-test-logic-designer/references/playwright-patterns.md +237 -0
- package/skills/test-coverage-checker/SKILL.md +39 -121
- package/skills/test-coverage-checker/references/coverage-report-template.md +99 -0
- package/skills/uiux-designer/SKILL.md +27 -92
- package/skills/uiux-designer/references/data-testid-convention.md +38 -0
- package/skills/uiux-designer/references/uiux-design-template.md +105 -0
- package/skills/unit-test-designer/SKILL.md +5 -106
- package/skills/unit-test-designer/references/unit-test-design-template.md +127 -0
- package/skills/unit-test-logic-designer/SKILL.md +28 -250
- package/skills/unit-test-logic-designer/references/test-patterns.md +207 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @layer domain
|
|
3
|
+
* @unit phase-dependency-model
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { GateDefinition } from '../values/gate-definition.js';
|
|
7
|
+
import { GateName } from '../values/gate-name.js';
|
|
8
|
+
|
|
9
|
+
export interface GateGraphViolation {
|
|
10
|
+
readonly code:
|
|
11
|
+
| 'DUPLICATE_GATE_NAME'
|
|
12
|
+
| 'UNKNOWN_DEPENDENCY'
|
|
13
|
+
| 'LEVEL_ORDER_VIOLATION'
|
|
14
|
+
| 'CYCLIC_DEPENDENCY';
|
|
15
|
+
readonly message: string;
|
|
16
|
+
readonly gateNames: readonly string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class GateGraphValidationError extends Error {
|
|
20
|
+
readonly violations: readonly GateGraphViolation[];
|
|
21
|
+
|
|
22
|
+
constructor(violations: readonly GateGraphViolation[]) {
|
|
23
|
+
super(violations.map((violation) => violation.message).join('\n'));
|
|
24
|
+
this.name = 'GateGraphValidationError';
|
|
25
|
+
this.violations = Object.freeze([...violations]);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const toViolation = (
|
|
30
|
+
code: GateGraphViolation['code'],
|
|
31
|
+
message: string,
|
|
32
|
+
gateNames: readonly string[],
|
|
33
|
+
): GateGraphViolation =>
|
|
34
|
+
Object.freeze({
|
|
35
|
+
code,
|
|
36
|
+
message,
|
|
37
|
+
gateNames: Object.freeze([...gateNames]),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export class GateGraph {
|
|
41
|
+
readonly gates: ReadonlyMap<string, GateDefinition>;
|
|
42
|
+
|
|
43
|
+
private constructor(gates: ReadonlyMap<string, GateDefinition>) {
|
|
44
|
+
this.gates = gates;
|
|
45
|
+
Object.freeze(this);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static build(gates: GateDefinition[]): GateGraph {
|
|
49
|
+
const violations: GateGraphViolation[] = [];
|
|
50
|
+
const duplicateNames = new Set<string>();
|
|
51
|
+
const gateMap = new Map<string, GateDefinition>();
|
|
52
|
+
|
|
53
|
+
for (const gate of gates) {
|
|
54
|
+
const name = gate.name.value;
|
|
55
|
+
if (gateMap.has(name)) {
|
|
56
|
+
duplicateNames.add(name);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
gateMap.set(name, gate);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const duplicateName of duplicateNames) {
|
|
63
|
+
violations.push(
|
|
64
|
+
toViolation(
|
|
65
|
+
'DUPLICATE_GATE_NAME',
|
|
66
|
+
`重複する GateName です: ${duplicateName}`,
|
|
67
|
+
[duplicateName],
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for (const gate of gateMap.values()) {
|
|
73
|
+
for (const dependencyName of gate.dependsOn) {
|
|
74
|
+
const dependency = gateMap.get(dependencyName.value);
|
|
75
|
+
if (!dependency) {
|
|
76
|
+
violations.push(
|
|
77
|
+
toViolation(
|
|
78
|
+
'UNKNOWN_DEPENDENCY',
|
|
79
|
+
`未知の dependsOn 参照です: ${gate.name.value} -> ${dependencyName.value}`,
|
|
80
|
+
[gate.name.value, dependencyName.value],
|
|
81
|
+
),
|
|
82
|
+
);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (dependency.level.value > gate.level.value) {
|
|
87
|
+
violations.push(
|
|
88
|
+
toViolation(
|
|
89
|
+
'LEVEL_ORDER_VIOLATION',
|
|
90
|
+
`レベル逆行です: ${gate.name.value} -> ${dependencyName.value}`,
|
|
91
|
+
[gate.name.value, dependencyName.value],
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const graph = new Map<string, string[]>();
|
|
99
|
+
for (const gate of gateMap.values()) {
|
|
100
|
+
graph.set(gate.name.value, []);
|
|
101
|
+
}
|
|
102
|
+
for (const gate of gateMap.values()) {
|
|
103
|
+
const neighbors = graph.get(gate.name.value);
|
|
104
|
+
if (!neighbors) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
for (const dependencyName of gate.dependsOn) {
|
|
108
|
+
if (gateMap.has(dependencyName.value)) {
|
|
109
|
+
neighbors.push(dependencyName.value);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for (const cycle of GateGraph.detectCycles(graph)) {
|
|
115
|
+
violations.push(
|
|
116
|
+
toViolation(
|
|
117
|
+
'CYCLIC_DEPENDENCY',
|
|
118
|
+
`循環依存が検出されました: ${cycle.join(' -> ')}`,
|
|
119
|
+
cycle,
|
|
120
|
+
),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (violations.length > 0) {
|
|
125
|
+
throw new GateGraphValidationError(violations);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return new GateGraph(gateMap);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
resolveAncestors(name: GateName): GateName[] {
|
|
132
|
+
const target = this.gates.get(name.value);
|
|
133
|
+
if (!target) {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const visited = new Set<string>();
|
|
138
|
+
const ordered: GateName[] = [];
|
|
139
|
+
|
|
140
|
+
const visit = (gate: GateDefinition): void => {
|
|
141
|
+
for (const dependencyName of gate.dependsOn) {
|
|
142
|
+
if (visited.has(dependencyName.value)) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const dependency = this.gates.get(dependencyName.value);
|
|
147
|
+
if (!dependency) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
visit(dependency);
|
|
152
|
+
visited.add(dependencyName.value);
|
|
153
|
+
ordered.push(dependencyName);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
visit(target);
|
|
158
|
+
|
|
159
|
+
return ordered;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private static detectCycles(graph: ReadonlyMap<string, readonly string[]>): readonly string[][] {
|
|
163
|
+
let index = 0;
|
|
164
|
+
const indexMap = new Map<string, number>();
|
|
165
|
+
const lowLinkMap = new Map<string, number>();
|
|
166
|
+
const stack: string[] = [];
|
|
167
|
+
const inStack = new Set<string>();
|
|
168
|
+
const cycles: string[][] = [];
|
|
169
|
+
|
|
170
|
+
const strongConnect = (node: string): void => {
|
|
171
|
+
indexMap.set(node, index);
|
|
172
|
+
lowLinkMap.set(node, index);
|
|
173
|
+
index += 1;
|
|
174
|
+
stack.push(node);
|
|
175
|
+
inStack.add(node);
|
|
176
|
+
|
|
177
|
+
for (const next of graph.get(node) ?? []) {
|
|
178
|
+
if (!indexMap.has(next)) {
|
|
179
|
+
strongConnect(next);
|
|
180
|
+
lowLinkMap.set(node, Math.min(lowLinkMap.get(node) ?? 0, lowLinkMap.get(next) ?? 0));
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (inStack.has(next)) {
|
|
185
|
+
lowLinkMap.set(node, Math.min(lowLinkMap.get(node) ?? 0, indexMap.get(next) ?? 0));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if ((lowLinkMap.get(node) ?? -1) !== (indexMap.get(node) ?? -1)) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const component: string[] = [];
|
|
194
|
+
let current = '';
|
|
195
|
+
do {
|
|
196
|
+
current = stack.pop() ?? '';
|
|
197
|
+
if (current.length === 0) {
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
inStack.delete(current);
|
|
201
|
+
component.push(current);
|
|
202
|
+
} while (current !== node);
|
|
203
|
+
|
|
204
|
+
if (component.length > 1) {
|
|
205
|
+
cycles.push(component.sort());
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const [single] = component;
|
|
210
|
+
if ((graph.get(single) ?? []).includes(single)) {
|
|
211
|
+
cycles.push(component);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
for (const node of graph.keys()) {
|
|
216
|
+
if (!indexMap.has(node)) {
|
|
217
|
+
strongConnect(node);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return Object.freeze(cycles);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @layer domain
|
|
3
|
+
* @unit phase-dependency-model
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { StoryReflectionConfig } from '../values/story-reflection-config.js';
|
|
7
|
+
import type { StoryReflectionFileSystemPort } from '../ports/story-reflection-file-system-port.js';
|
|
8
|
+
import {
|
|
9
|
+
StoryReflectionResult,
|
|
10
|
+
type StoryReflectionViolation,
|
|
11
|
+
} from '../values/story-reflection-result.js';
|
|
12
|
+
|
|
13
|
+
export class StoryReflectionChecker {
|
|
14
|
+
constructor(private readonly fsPort: StoryReflectionFileSystemPort) {}
|
|
15
|
+
|
|
16
|
+
async check(
|
|
17
|
+
unitId: string,
|
|
18
|
+
config: StoryReflectionConfig,
|
|
19
|
+
): Promise<StoryReflectionResult> {
|
|
20
|
+
if (!config.enabled) {
|
|
21
|
+
return StoryReflectionResult.pass();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const storyIds = await this.fsPort.listStoryDirectories(unitId);
|
|
25
|
+
|
|
26
|
+
if (storyIds.length === 0) {
|
|
27
|
+
return StoryReflectionResult.pass();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const violations: StoryReflectionViolation[] = [];
|
|
31
|
+
const warnings: StoryReflectionViolation[] = [];
|
|
32
|
+
|
|
33
|
+
for (const storyId of storyIds) {
|
|
34
|
+
for (const mapping of config.mappings) {
|
|
35
|
+
const { inception: inceptionPath, product: productPath } =
|
|
36
|
+
mapping.resolve({ unitId, storyId });
|
|
37
|
+
|
|
38
|
+
const inceptionExists = await this.fsPort.fileExists(inceptionPath);
|
|
39
|
+
|
|
40
|
+
if (!inceptionExists) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const hasAnnotation =
|
|
45
|
+
await this.fsPort.fileContainsStoryAnnotation(productPath, storyId);
|
|
46
|
+
|
|
47
|
+
if (!hasAnnotation) {
|
|
48
|
+
const violation: StoryReflectionViolation = {
|
|
49
|
+
storyId,
|
|
50
|
+
mapping,
|
|
51
|
+
inceptionPath,
|
|
52
|
+
productPath,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (mapping.required) {
|
|
56
|
+
violations.push(violation);
|
|
57
|
+
} else {
|
|
58
|
+
warnings.push(violation);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return StoryReflectionResult.create({ violations, warnings });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @layer domain
|
|
3
|
+
* @unit phase-dependency-model
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { GateName } from './gate-name.js';
|
|
7
|
+
import { GateStoryAnnotation } from './gate-story-annotation.js';
|
|
8
|
+
import { PhaseLevel } from './phase-level.js';
|
|
9
|
+
|
|
10
|
+
export interface GateRequirement {
|
|
11
|
+
readonly path: string;
|
|
12
|
+
readonly required: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface GateDefinitionCreateArgs {
|
|
16
|
+
readonly name: GateName;
|
|
17
|
+
readonly level: PhaseLevel;
|
|
18
|
+
readonly requires: readonly GateRequirement[];
|
|
19
|
+
readonly blocks: readonly string[];
|
|
20
|
+
readonly dependsOn: readonly GateName[];
|
|
21
|
+
readonly storyAnnotation?: GateStoryAnnotation;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class InvalidGateDefinitionError extends Error {
|
|
25
|
+
constructor(message: string) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = 'InvalidGateDefinitionError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
32
|
+
typeof value === 'object' && value !== null;
|
|
33
|
+
|
|
34
|
+
const parseRequirement = (raw: unknown): GateRequirement => {
|
|
35
|
+
if (!isRecord(raw) || typeof raw.path !== 'string' || typeof raw.required !== 'boolean') {
|
|
36
|
+
throw new InvalidGateDefinitionError('requires の形式が不正です');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const path = raw.path.trim();
|
|
40
|
+
if (path.length === 0) {
|
|
41
|
+
throw new InvalidGateDefinitionError('requires.path は必須です');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return Object.freeze({
|
|
45
|
+
path,
|
|
46
|
+
required: raw.required,
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const parseStringArray = (raw: unknown, fieldName: string): readonly string[] => {
|
|
51
|
+
if (!Array.isArray(raw) || raw.some((entry) => typeof entry !== 'string')) {
|
|
52
|
+
throw new InvalidGateDefinitionError(`${fieldName} の形式が不正です`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return Object.freeze(raw.map((entry) => entry.trim()));
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export class GateDefinition {
|
|
59
|
+
readonly name: GateName;
|
|
60
|
+
readonly level: PhaseLevel;
|
|
61
|
+
readonly requires: readonly GateRequirement[];
|
|
62
|
+
readonly blocks: readonly string[];
|
|
63
|
+
readonly dependsOn: readonly GateName[];
|
|
64
|
+
readonly storyAnnotation?: GateStoryAnnotation;
|
|
65
|
+
|
|
66
|
+
private constructor(args: GateDefinitionCreateArgs) {
|
|
67
|
+
this.name = args.name;
|
|
68
|
+
this.level = args.level;
|
|
69
|
+
this.requires = Object.freeze([...args.requires]);
|
|
70
|
+
this.blocks = Object.freeze([...args.blocks]);
|
|
71
|
+
this.dependsOn = Object.freeze([...args.dependsOn]);
|
|
72
|
+
this.storyAnnotation = args.storyAnnotation;
|
|
73
|
+
Object.freeze(this);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static create(args: GateDefinitionCreateArgs): GateDefinition {
|
|
77
|
+
if (args.storyAnnotation && args.level.value !== 3) {
|
|
78
|
+
throw new InvalidGateDefinitionError('storyAnnotation は Level 3 のゲートにのみ指定できます');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return new GateDefinition(args);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static fromRaw(raw: unknown): GateDefinition {
|
|
85
|
+
if (!isRecord(raw)) {
|
|
86
|
+
throw new InvalidGateDefinitionError('GateDefinition の形式が不正です');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const name = GateName.create(String(raw.name ?? ''));
|
|
90
|
+
const level = PhaseLevel.create(Number(raw.level));
|
|
91
|
+
const requires = Array.isArray(raw.requires)
|
|
92
|
+
? Object.freeze(raw.requires.map((requirement) => parseRequirement(requirement)))
|
|
93
|
+
: (() => {
|
|
94
|
+
throw new InvalidGateDefinitionError('requires の形式が不正です');
|
|
95
|
+
})();
|
|
96
|
+
const blocks = parseStringArray(raw.blocks ?? [], 'blocks');
|
|
97
|
+
const dependsOn = parseStringArray(raw.dependsOn ?? [], 'dependsOn').map((value) =>
|
|
98
|
+
GateName.create(value),
|
|
99
|
+
);
|
|
100
|
+
const storyAnnotation =
|
|
101
|
+
raw.storyAnnotation === undefined
|
|
102
|
+
? undefined
|
|
103
|
+
: (() => {
|
|
104
|
+
if (
|
|
105
|
+
!isRecord(raw.storyAnnotation) ||
|
|
106
|
+
typeof raw.storyAnnotation.required !== 'boolean' ||
|
|
107
|
+
typeof raw.storyAnnotation.tag !== 'string'
|
|
108
|
+
) {
|
|
109
|
+
throw new InvalidGateDefinitionError('storyAnnotation の形式が不正です');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return GateStoryAnnotation.create({
|
|
113
|
+
required: raw.storyAnnotation.required,
|
|
114
|
+
tag: raw.storyAnnotation.tag,
|
|
115
|
+
});
|
|
116
|
+
})();
|
|
117
|
+
|
|
118
|
+
return GateDefinition.create({
|
|
119
|
+
name,
|
|
120
|
+
level,
|
|
121
|
+
requires,
|
|
122
|
+
blocks,
|
|
123
|
+
dependsOn: Object.freeze(dependsOn),
|
|
124
|
+
storyAnnotation,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @layer domain
|
|
3
|
+
* @unit phase-dependency-model
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const GATE_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
7
|
+
|
|
8
|
+
export class InvalidGateNameError extends Error {
|
|
9
|
+
constructor(value: string) {
|
|
10
|
+
super(`GateNameが不正です: ${value}`);
|
|
11
|
+
this.name = 'InvalidGateNameError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class GateName {
|
|
16
|
+
readonly value: string;
|
|
17
|
+
|
|
18
|
+
private constructor(value: string) {
|
|
19
|
+
this.value = value;
|
|
20
|
+
Object.freeze(this);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static create(value: string): GateName {
|
|
24
|
+
const normalizedValue = value.trim();
|
|
25
|
+
|
|
26
|
+
if (
|
|
27
|
+
normalizedValue.length === 0 ||
|
|
28
|
+
!GATE_NAME_PATTERN.test(normalizedValue) ||
|
|
29
|
+
normalizedValue.endsWith('-')
|
|
30
|
+
) {
|
|
31
|
+
throw new InvalidGateNameError(value);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return new GateName(normalizedValue);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
equals(other: GateName): boolean {
|
|
38
|
+
return this.value === other.value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @layer domain
|
|
3
|
+
* @unit phase-dependency-model
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface GateStoryAnnotationCreateArgs {
|
|
7
|
+
readonly required: boolean;
|
|
8
|
+
readonly tag: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class InvalidGateStoryAnnotationError extends Error {
|
|
12
|
+
constructor(message: string) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'InvalidGateStoryAnnotationError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class GateStoryAnnotation {
|
|
19
|
+
readonly required: boolean;
|
|
20
|
+
readonly tag: string;
|
|
21
|
+
|
|
22
|
+
private constructor(args: GateStoryAnnotationCreateArgs) {
|
|
23
|
+
this.required = args.required;
|
|
24
|
+
this.tag = args.tag.trim();
|
|
25
|
+
Object.freeze(this);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static create(args: GateStoryAnnotationCreateArgs): GateStoryAnnotation {
|
|
29
|
+
if (args.tag.trim().length === 0) {
|
|
30
|
+
throw new InvalidGateStoryAnnotationError('tagは必須です');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return new GateStoryAnnotation(args);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
equals(other: GateStoryAnnotation): boolean {
|
|
37
|
+
return this.required === other.required && this.tag === other.tag;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -5,19 +5,21 @@
|
|
|
5
5
|
|
|
6
6
|
import { CustomRule } from './custom-rule.js';
|
|
7
7
|
|
|
8
|
+
export type PresetName = 'full' | 'standard' | 'minimal' | 'custom';
|
|
9
|
+
|
|
8
10
|
export interface PhaseCustomizationPolicyCreateArgs {
|
|
9
|
-
readonly preset?:
|
|
11
|
+
readonly preset?: PresetName | 'default';
|
|
10
12
|
readonly rules: readonly CustomRule[];
|
|
11
13
|
readonly overrideEnabled: boolean;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export class PhaseCustomizationPolicy {
|
|
15
|
-
readonly preset:
|
|
17
|
+
readonly preset: PresetName;
|
|
16
18
|
readonly rules: readonly CustomRule[];
|
|
17
19
|
readonly overrideEnabled: boolean;
|
|
18
20
|
|
|
19
21
|
private constructor(args: {
|
|
20
|
-
readonly preset:
|
|
22
|
+
readonly preset: PresetName;
|
|
21
23
|
readonly rules: readonly CustomRule[];
|
|
22
24
|
readonly overrideEnabled: boolean;
|
|
23
25
|
}) {
|
|
@@ -27,9 +29,19 @@ export class PhaseCustomizationPolicy {
|
|
|
27
29
|
Object.freeze(this);
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
private static resolvePreset(preset: PresetName | 'default' | undefined, hasRules: boolean): PresetName {
|
|
33
|
+
if (preset === undefined) {
|
|
34
|
+
return hasRules ? 'custom' : 'full';
|
|
35
|
+
}
|
|
36
|
+
if (preset === 'default') {
|
|
37
|
+
return 'full';
|
|
38
|
+
}
|
|
39
|
+
return preset;
|
|
40
|
+
}
|
|
41
|
+
|
|
30
42
|
static create(args: PhaseCustomizationPolicyCreateArgs): PhaseCustomizationPolicy {
|
|
31
43
|
return new PhaseCustomizationPolicy({
|
|
32
|
-
preset: args.preset
|
|
44
|
+
preset: PhaseCustomizationPolicy.resolvePreset(args.preset, args.rules.length > 0),
|
|
33
45
|
rules: args.rules,
|
|
34
46
|
overrideEnabled: args.overrideEnabled,
|
|
35
47
|
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @layer domain
|
|
3
|
+
* @unit phase-dependency-model
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { StoryReflectionMapping } from './story-reflection-mapping.js';
|
|
7
|
+
|
|
8
|
+
export interface StoryReflectionConfigCreateArgs {
|
|
9
|
+
readonly enabled: boolean;
|
|
10
|
+
readonly mappings: readonly StoryReflectionMapping[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class StoryReflectionConfig {
|
|
14
|
+
readonly enabled: boolean;
|
|
15
|
+
readonly mappings: readonly StoryReflectionMapping[];
|
|
16
|
+
|
|
17
|
+
private constructor(args: StoryReflectionConfigCreateArgs) {
|
|
18
|
+
this.enabled = args.enabled;
|
|
19
|
+
this.mappings = Object.freeze([...args.mappings]);
|
|
20
|
+
Object.freeze(this);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static create(args: StoryReflectionConfigCreateArgs): StoryReflectionConfig {
|
|
24
|
+
return new StoryReflectionConfig(args);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static disabled(): StoryReflectionConfig {
|
|
28
|
+
return new StoryReflectionConfig({ enabled: false, mappings: [] });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
requiredMappings(): readonly StoryReflectionMapping[] {
|
|
32
|
+
return this.mappings.filter((m) => m.required);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
optionalMappings(): readonly StoryReflectionMapping[] {
|
|
36
|
+
return this.mappings.filter((m) => !m.required);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
equals(other: StoryReflectionConfig): boolean {
|
|
40
|
+
if (this.enabled !== other.enabled) return false;
|
|
41
|
+
if (this.mappings.length !== other.mappings.length) return false;
|
|
42
|
+
return this.mappings.every((m, i) => m.equals(other.mappings[i]));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @layer domain
|
|
3
|
+
* @unit phase-dependency-model
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface StoryReflectionMappingCreateArgs {
|
|
7
|
+
readonly inception: string;
|
|
8
|
+
readonly product: string;
|
|
9
|
+
readonly required: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class InvalidStoryReflectionMappingError extends Error {
|
|
13
|
+
constructor(message: string) {
|
|
14
|
+
super(`StoryReflectionMapping が不正です: ${message}`);
|
|
15
|
+
this.name = 'InvalidStoryReflectionMappingError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class StoryReflectionMapping {
|
|
20
|
+
readonly inception: string;
|
|
21
|
+
readonly product: string;
|
|
22
|
+
readonly required: boolean;
|
|
23
|
+
|
|
24
|
+
private constructor(args: StoryReflectionMappingCreateArgs) {
|
|
25
|
+
this.inception = args.inception;
|
|
26
|
+
this.product = args.product;
|
|
27
|
+
this.required = args.required;
|
|
28
|
+
Object.freeze(this);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static create(args: StoryReflectionMappingCreateArgs): StoryReflectionMapping {
|
|
32
|
+
const inception = args.inception.trim();
|
|
33
|
+
const product = args.product.trim();
|
|
34
|
+
|
|
35
|
+
if (!inception.startsWith('docs/inception/')) {
|
|
36
|
+
throw new InvalidStoryReflectionMappingError(
|
|
37
|
+
`inception は docs/inception/ で始まる必要があります: ${inception}`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!product.startsWith('docs/product/')) {
|
|
42
|
+
throw new InvalidStoryReflectionMappingError(
|
|
43
|
+
`product は docs/product/ で始まる必要があります: ${product}`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!inception.includes('{storyId}')) {
|
|
48
|
+
throw new InvalidStoryReflectionMappingError(
|
|
49
|
+
`inception に {storyId} プレースホルダが必要です: ${inception}`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!inception.includes('{unit}')) {
|
|
54
|
+
throw new InvalidStoryReflectionMappingError(
|
|
55
|
+
`inception に {unit} プレースホルダが必要です: ${inception}`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!product.includes('{unit}')) {
|
|
60
|
+
throw new InvalidStoryReflectionMappingError(
|
|
61
|
+
`product に {unit} プレースホルダが必要です: ${product}`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return new StoryReflectionMapping({
|
|
66
|
+
inception,
|
|
67
|
+
product,
|
|
68
|
+
required: args.required,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
resolve(scope: { unitId: string; storyId: string }): { inception: string; product: string } {
|
|
73
|
+
const inception = this.inception
|
|
74
|
+
.replaceAll('{unit}', scope.unitId)
|
|
75
|
+
.replaceAll('{storyId}', scope.storyId);
|
|
76
|
+
|
|
77
|
+
const product = this.product
|
|
78
|
+
.replaceAll('{unit}', scope.unitId)
|
|
79
|
+
.replaceAll('{storyId}', scope.storyId);
|
|
80
|
+
|
|
81
|
+
return { inception, product };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
equals(other: StoryReflectionMapping): boolean {
|
|
85
|
+
return (
|
|
86
|
+
this.inception === other.inception &&
|
|
87
|
+
this.product === other.product &&
|
|
88
|
+
this.required === other.required
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|