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.
Files changed (215) hide show
  1. package/README.ja.md +71 -39
  2. package/README.md +69 -9
  3. package/docs/ADR/ADR-013-story-reflection-gate.md +100 -0
  4. package/docs/guide/configuration.md +244 -7
  5. package/package.json +4 -2
  6. package/scripts/harness/adr-foundation/domain/ports/adr-document-parser-port.ts +1 -0
  7. package/scripts/harness/adr-foundation/domain/ports/adr-frontmatter-parser-port.ts +1 -0
  8. package/scripts/harness/adr-foundation/domain/ports/adr-repository-port.ts +1 -0
  9. package/scripts/harness/adr-foundation/domain/value-objects/adr-status.ts +6 -6
  10. package/scripts/harness/agent-integration/application/dto/handle-pre-tool-use-dto.ts +3 -1
  11. package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +108 -2
  12. package/scripts/harness/agent-integration/domain/ports/config-query-port.ts +2 -0
  13. package/scripts/harness/agent-integration/domain/ports/phase-gate-query-port.ts +1 -1
  14. package/scripts/harness/agent-integration/domain/ports/story-reflection-query-port.ts +8 -0
  15. package/scripts/harness/agent-integration/domain/services/bash-write-target-extractor.ts +258 -0
  16. package/scripts/harness/agent-integration/domain/services/hook-to-cli-translator.ts +21 -4
  17. package/scripts/harness/agent-integration/domain/value-objects/protected-file-list.ts +20 -0
  18. package/scripts/harness/agent-integration/domain/value-objects/story-reflection-query-result.ts +63 -0
  19. package/scripts/harness/agent-integration/infrastructure/adapters/file-system-story-reflection-query-adapter.ts +122 -0
  20. package/scripts/harness/agent-integration/infrastructure/adapters/harness-config-config-query-adapter.ts +20 -0
  21. package/scripts/harness/agent-integration/infrastructure/adapters/phase-gate-query-adapter.ts +17 -3
  22. package/scripts/harness/agent-integration/presentation/pre-tool-use-hook.ts +29 -2
  23. package/scripts/harness/biome-ast-engine/domain/services/lint-runner.ts +24 -0
  24. package/scripts/harness/biome-ast-engine/domain/services/rule-definition-registry.ts +1 -1
  25. package/scripts/harness/biome-ast-engine/domain/value-objects/source-module-snapshot.ts +5 -2
  26. package/scripts/harness/biome-ast-engine/infrastructure/adapters/node-workspace-file-adapter.ts +1 -1
  27. package/scripts/harness/biome-ast-engine/infrastructure/adapters/typescript-source-module-analyzer-adapter.ts +1 -1
  28. package/scripts/harness/biome-ast-engine/infrastructure/parsers/unit-comment-parser.ts +12 -7
  29. package/scripts/harness/ci-governance/composition-root.ts +1 -0
  30. package/scripts/harness/config-foundation/application/mappers/phase-config-section-mapper.ts +44 -0
  31. package/scripts/harness/config-foundation/application/usecases/load-resolved-config-use-case.ts +20 -1
  32. package/scripts/harness/config-foundation/domain/harness-config.ts +2 -1
  33. package/scripts/harness/config-foundation/domain/value-objects/phase-dependencies-config.ts +18 -4
  34. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +185 -10
  35. package/scripts/harness/config-foundation/infrastructure/validators/ajv-config-schema-validator.ts +2 -1
  36. package/scripts/harness/harness-api/application/dto/command-dispatch-input.ts +1 -0
  37. package/scripts/harness/harness-api/application/dto/command-dispatch-output.ts +1 -0
  38. package/scripts/harness/harness-api/application/dto/exit-code-decision-input.ts +1 -0
  39. package/scripts/harness/harness-api/application/dto/exit-code-decision-output.ts +1 -0
  40. package/scripts/harness/harness-api/application/dto/harness-api-response-contract.ts +1 -0
  41. package/scripts/harness/harness-api/application/dto/initialize-registry-input.ts +1 -0
  42. package/scripts/harness/harness-api/application/dto/registry-summary-output.ts +1 -0
  43. package/scripts/harness/harness-api/application/dto/status-derivation-input.ts +1 -0
  44. package/scripts/harness/harness-api/application/mappers/harness-api-response-mapper.ts +1 -0
  45. package/scripts/harness/harness-api/application/usecases/decide-exit-code-usecase.ts +1 -0
  46. package/scripts/harness/harness-api/application/usecases/derive-harness-status-usecase.ts +1 -0
  47. package/scripts/harness/harness-api/application/usecases/dispatch-command-usecase.ts +1 -0
  48. package/scripts/harness/harness-api/application/usecases/initialize-command-registry-usecase.ts +1 -0
  49. package/scripts/harness/harness-api/composition-root.ts +1 -0
  50. package/scripts/harness/harness-api/domain/ports/artifact-scanner-port.ts +1 -0
  51. package/scripts/harness/harness-api/domain/ports/biome-lint-port.ts +1 -0
  52. package/scripts/harness/harness-api/domain/ports/config-query-port.ts +1 -0
  53. package/scripts/harness/harness-api/domain/ports/impact-analysis-port.ts +1 -0
  54. package/scripts/harness/harness-api/domain/ports/phase-gate-query-port.ts +1 -0
  55. package/scripts/harness/harness-api/domain/ports/validator-execution-port.ts +1 -0
  56. package/scripts/harness/harness-api/domain/services/command-dispatch-service.ts +1 -0
  57. package/scripts/harness/harness-api/domain/services/command-registry.ts +1 -0
  58. package/scripts/harness/harness-api/domain/services/status-derivation-service.ts +1 -0
  59. package/scripts/harness/harness-api/domain/value-objects/artifact-scan-result.ts +1 -0
  60. package/scripts/harness/harness-api/domain/value-objects/check-ready-result.ts +1 -0
  61. package/scripts/harness/harness-api/domain/value-objects/ci-check-result.ts +1 -0
  62. package/scripts/harness/harness-api/domain/value-objects/cli-command-definition.ts +1 -0
  63. package/scripts/harness/harness-api/domain/value-objects/command-input-spec.ts +1 -0
  64. package/scripts/harness/harness-api/domain/value-objects/drift-report-summary.ts +1 -0
  65. package/scripts/harness/harness-api/domain/value-objects/exit-code-spec.ts +1 -0
  66. package/scripts/harness/harness-api/domain/value-objects/harness-api-response.ts +1 -0
  67. package/scripts/harness/harness-api/domain/value-objects/harness-status-summary.ts +1 -0
  68. package/scripts/harness/harness-api/domain/value-objects/layer-health.ts +1 -0
  69. package/scripts/harness/harness-api/domain/value-objects/phase-info.ts +1 -0
  70. package/scripts/harness/harness-api/index.ts +1 -0
  71. package/scripts/harness/harness-api/infrastructure/adapters/biome-ast-engine-lint-adapter.ts +1 -0
  72. package/scripts/harness/harness-api/infrastructure/adapters/file-system-artifact-scanner-adapter.ts +1 -0
  73. package/scripts/harness/harness-api/infrastructure/adapters/harness-config-query-adapter.ts +1 -0
  74. package/scripts/harness/harness-api/infrastructure/adapters/nyquist-validation-impact-analysis-adapter.ts +1 -0
  75. package/scripts/harness/harness-api/infrastructure/adapters/phase-dependency-model-query-adapter.ts +1 -0
  76. package/scripts/harness/harness-api/infrastructure/adapters/validator-system-execution-adapter.ts +1 -0
  77. package/scripts/harness/harness-api/presentation/dto/cli-output-options.ts +1 -0
  78. package/scripts/harness/harness-api/presentation/formatters/harness-api-json-formatter.ts +1 -0
  79. package/scripts/harness/harness-api/presentation/handlers/check-phase-handler.ts +1 -0
  80. package/scripts/harness/harness-api/presentation/handlers/check-ready-handler.ts +1 -0
  81. package/scripts/harness/harness-api/presentation/handlers/ci-check-handler.ts +1 -0
  82. package/scripts/harness/harness-api/presentation/handlers/complete-check-handler.ts +1 -0
  83. package/scripts/harness/harness-api/presentation/handlers/detect-drift-handler.ts +1 -0
  84. package/scripts/harness/harness-api/presentation/handlers/impact-analysis-handler.ts +1 -0
  85. package/scripts/harness/harness-api/presentation/handlers/lint-handler.ts +1 -0
  86. package/scripts/harness/harness-api/presentation/handlers/status-handler.ts +1 -0
  87. package/scripts/harness/harness-error/infrastructure/adapters/legacy-error-reporter-adapter.ts +7 -1
  88. package/scripts/harness/integrations/pre-commit.ts +1 -0
  89. package/scripts/harness/main.ts +154 -34
  90. package/scripts/harness/phase-dependency-model/application/dto/resolve-gate-result-dto.ts +14 -0
  91. package/scripts/harness/phase-dependency-model/application/services/phase-gate-result-mapper.ts +20 -0
  92. package/scripts/harness/phase-dependency-model/application/usecases/check-story-reflection-usecase.ts +42 -0
  93. package/scripts/harness/phase-dependency-model/application/usecases/resolve-gate-usecase.ts +161 -0
  94. package/scripts/harness/phase-dependency-model/composition-root.ts +57 -1
  95. package/scripts/harness/phase-dependency-model/domain/definitions/{default-phase-dependencies.ts → full-phase-dependencies.ts} +3 -3
  96. package/scripts/harness/phase-dependency-model/domain/definitions/{default-phase-nodes.ts → full-phase-nodes.ts} +1 -1
  97. package/scripts/harness/phase-dependency-model/domain/definitions/full-story-reflection-defaults.ts +28 -0
  98. package/scripts/harness/phase-dependency-model/domain/definitions/minimal-phase-dependencies.ts +32 -0
  99. package/scripts/harness/phase-dependency-model/domain/definitions/minimal-phase-nodes.ts +50 -0
  100. package/scripts/harness/phase-dependency-model/domain/definitions/minimal-story-reflection-defaults.ts +8 -0
  101. package/scripts/harness/phase-dependency-model/domain/definitions/standard-phase-dependencies.ts +39 -0
  102. package/scripts/harness/phase-dependency-model/domain/definitions/standard-phase-nodes.ts +118 -0
  103. package/scripts/harness/phase-dependency-model/domain/definitions/standard-story-reflection-defaults.ts +23 -0
  104. package/scripts/harness/phase-dependency-model/domain/models/phase-structure.ts +95 -23
  105. package/scripts/harness/phase-dependency-model/domain/ports/glob-matcher-port.ts +8 -0
  106. package/scripts/harness/phase-dependency-model/domain/ports/phase-config-provider-port.ts +2 -0
  107. package/scripts/harness/phase-dependency-model/domain/ports/story-annotation-verifier-port.ts +14 -0
  108. package/scripts/harness/phase-dependency-model/domain/ports/story-reflection-file-system-port.ts +16 -0
  109. package/scripts/harness/phase-dependency-model/domain/services/gate-graph.ts +223 -0
  110. package/scripts/harness/phase-dependency-model/domain/services/story-reflection-checker.ts +66 -0
  111. package/scripts/harness/phase-dependency-model/domain/values/gate-definition.ts +127 -0
  112. package/scripts/harness/phase-dependency-model/domain/values/gate-name.ts +40 -0
  113. package/scripts/harness/phase-dependency-model/domain/values/gate-story-annotation.ts +39 -0
  114. package/scripts/harness/phase-dependency-model/domain/values/phase-customization-policy.ts +16 -4
  115. package/scripts/harness/phase-dependency-model/domain/values/story-reflection-config.ts +44 -0
  116. package/scripts/harness/phase-dependency-model/domain/values/story-reflection-mapping.ts +91 -0
  117. package/scripts/harness/phase-dependency-model/domain/values/story-reflection-result.ts +55 -0
  118. package/scripts/harness/phase-dependency-model/infrastructure/adapters/picomatch-glob-matcher.ts +17 -0
  119. package/scripts/harness/phase-dependency-model/infrastructure/adapters/story-annotation-verifier-adapter.ts +46 -0
  120. package/scripts/harness/phase-dependency-model/infrastructure/config/custom-gates-config-parser.ts +22 -0
  121. package/scripts/harness/phase-dependency-model/infrastructure/config/harness-config-phase-config-provider.ts +78 -2
  122. package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.ts +94 -0
  123. package/scripts/harness/phase-dependency-model/presentation/cli/check-phase-gate-command-handler.ts +12 -4
  124. package/scripts/harness/phase-dependency-model/presentation/cli/story-reflection-status-presenter.ts +87 -0
  125. package/scripts/harness/regression-suite/application/dto/analyze-migration-input.ts +1 -0
  126. package/scripts/harness/regression-suite/application/dto/analyze-migration-output.ts +1 -0
  127. package/scripts/harness/regression-suite/application/dto/configure-ci-gate-input.ts +1 -0
  128. package/scripts/harness/regression-suite/application/dto/configure-ci-gate-output.ts +1 -0
  129. package/scripts/harness/regression-suite/application/dto/migrate-v0-tests-input.ts +1 -0
  130. package/scripts/harness/regression-suite/application/dto/migrate-v0-tests-output.ts +1 -0
  131. package/scripts/harness/regression-suite/application/dto/run-regression-suite-input.ts +1 -0
  132. package/scripts/harness/regression-suite/application/dto/run-regression-suite-output.ts +1 -0
  133. package/scripts/harness/regression-suite/application/mappers/migration-mapping-mapper.ts +1 -0
  134. package/scripts/harness/regression-suite/application/mappers/test-execution-summary-mapper.ts +1 -0
  135. package/scripts/harness/regression-suite/application/usecases/analyze-v0-migration-usecase.ts +1 -0
  136. package/scripts/harness/regression-suite/application/usecases/configure-ci-gate-usecase.ts +1 -0
  137. package/scripts/harness/regression-suite/application/usecases/migrate-v0-tests-usecase.ts +1 -0
  138. package/scripts/harness/regression-suite/application/usecases/run-agent-independence-guard-usecase.ts +1 -0
  139. package/scripts/harness/regression-suite/application/usecases/run-gng-gate-regression-usecase.ts +1 -0
  140. package/scripts/harness/regression-suite/application/usecases/run-k-requirements-regression-usecase.ts +1 -0
  141. package/scripts/harness/regression-suite/application/usecases/run-k14-k15-regression-usecase.ts +1 -0
  142. package/scripts/harness/regression-suite/composition-root.ts +1 -0
  143. package/scripts/harness/regression-suite/domain/aggregates/v0-test-migration.ts +1 -0
  144. package/scripts/harness/regression-suite/domain/ports/ci-gate-result-writer-port.ts +1 -0
  145. package/scripts/harness/regression-suite/domain/ports/config-query-port.ts +1 -0
  146. package/scripts/harness/regression-suite/domain/ports/import-analyzer-port.ts +1 -0
  147. package/scripts/harness/regression-suite/domain/ports/migration-mapping-repository-port.ts +1 -0
  148. package/scripts/harness/regression-suite/domain/ports/suite-registry-port.ts +1 -0
  149. package/scripts/harness/regression-suite/domain/ports/test-runner-port.ts +1 -0
  150. package/scripts/harness/regression-suite/domain/ports/v0-spec-reader-port.ts +1 -0
  151. package/scripts/harness/regression-suite/domain/services/import-guard-service.ts +1 -0
  152. package/scripts/harness/regression-suite/domain/services/migration-analyzer.ts +1 -0
  153. package/scripts/harness/regression-suite/domain/services/regression-runner.ts +1 -0
  154. package/scripts/harness/regression-suite/domain/value-objects/agent-independence-test.ts +1 -0
  155. package/scripts/harness/regression-suite/domain/value-objects/biome-modification-spec.ts +1 -0
  156. package/scripts/harness/regression-suite/domain/value-objects/ci-gate-config.ts +1 -0
  157. package/scripts/harness/regression-suite/domain/value-objects/coverage-rate.ts +1 -0
  158. package/scripts/harness/regression-suite/domain/value-objects/gng-condition-test.ts +1 -0
  159. package/scripts/harness/regression-suite/domain/value-objects/import-violation.ts +1 -0
  160. package/scripts/harness/regression-suite/domain/value-objects/k-requirement-test.ts +1 -0
  161. package/scripts/harness/regression-suite/domain/value-objects/migration-mapping.ts +1 -0
  162. package/scripts/harness/regression-suite/domain/value-objects/regression-suite-definition.ts +1 -0
  163. package/scripts/harness/regression-suite/domain/value-objects/suite-id.ts +1 -0
  164. package/scripts/harness/regression-suite/domain/value-objects/test-execution-summary.ts +1 -0
  165. package/scripts/harness/regression-suite/domain/value-objects/test-failure-detail.ts +1 -0
  166. package/scripts/harness/regression-suite/domain/value-objects/v0-test-id.ts +1 -0
  167. package/scripts/harness/regression-suite/domain/value-objects/v1-test-path.ts +1 -0
  168. package/scripts/harness/regression-suite/infrastructure/adapters/biome-ast-import-analyzer-adapter.ts +1 -0
  169. package/scripts/harness/regression-suite/infrastructure/adapters/file-system-v0-spec-reader-adapter.ts +1 -0
  170. package/scripts/harness/regression-suite/infrastructure/adapters/harness-config-query-adapter.ts +1 -0
  171. package/scripts/harness/regression-suite/infrastructure/adapters/json-ci-gate-result-writer-adapter.ts +1 -0
  172. package/scripts/harness/regression-suite/infrastructure/adapters/markdown-migration-mapping-repository-adapter.ts +1 -0
  173. package/scripts/harness/regression-suite/infrastructure/adapters/static-suite-registry-adapter.ts +1 -0
  174. package/scripts/harness/regression-suite/infrastructure/adapters/vitest-test-runner-adapter.ts +1 -0
  175. package/scripts/harness/regression-suite/infrastructure/registry/agent-independence-suite-definition.ts +1 -0
  176. package/scripts/harness/regression-suite/infrastructure/registry/gng-gate-suite-definition.ts +1 -0
  177. package/scripts/harness/regression-suite/infrastructure/registry/k-requirements-suite-definition.ts +1 -0
  178. package/scripts/harness/regression-suite/infrastructure/registry/v0-migration-suite-definition.ts +1 -0
  179. package/scripts/harness/setup/skill-deployer.ts +82 -5
  180. package/scripts/harness/shared-kernel/harness-api.ts +1 -0
  181. package/scripts/harness/skill-quality/composition-root.ts +1 -0
  182. package/scripts/harness/skill-quality/infrastructure/adapters/ajv-lesson-artifact-schema-adapter.ts +5 -4
  183. package/scripts/harness/traceability-model/domain/services/metadata-validator.ts +12 -10
  184. package/scripts/harness/traceability-model/infrastructure/parsers/source-metadata-parser.ts +16 -2
  185. package/skills/cascade-updater/SKILL.md +16 -3
  186. package/skills/codebase-mapper/SKILL.md +9 -9
  187. package/skills/codex-delegator/SKILL.md +5 -56
  188. package/skills/consistency-checker/SKILL.md +5 -9
  189. 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
  190. package/skills/doc-freshness-checker/SKILL.md +6 -6
  191. package/skills/engineering-perspective/SKILL.md +2 -58
  192. package/skills/engineering-perspective/references/domain-model.md +4 -135
  193. package/skills/implementation-readiness-checker/SKILL.md +18 -72
  194. package/skills/it-test-designer/SKILL.md +25 -161
  195. package/skills/it-test-designer/references/it-test-design-template.md +131 -0
  196. package/skills/it-test-logic-designer/SKILL.md +13 -388
  197. package/skills/it-test-logic-designer/references/controller-test-patterns.md +100 -0
  198. package/skills/it-test-logic-designer/references/repository-test-patterns.md +206 -0
  199. package/skills/it-test-logic-designer/references/usecase-test-patterns.md +87 -0
  200. package/skills/pointer-validator/SKILL.md +4 -4
  201. package/skills/scenario-test-designer/SKILL.md +4 -68
  202. package/skills/scenario-test-designer/references/scenario-test-design-template.md +41 -0
  203. package/skills/scenario-test-designer/references/scenario-test-plan-template.md +35 -0
  204. package/skills/scenario-test-logic-designer/SKILL.md +21 -304
  205. package/skills/scenario-test-logic-designer/references/msw-patterns.md +36 -0
  206. package/skills/scenario-test-logic-designer/references/playwright-patterns.md +237 -0
  207. package/skills/test-coverage-checker/SKILL.md +39 -121
  208. package/skills/test-coverage-checker/references/coverage-report-template.md +99 -0
  209. package/skills/uiux-designer/SKILL.md +27 -92
  210. package/skills/uiux-designer/references/data-testid-convention.md +38 -0
  211. package/skills/uiux-designer/references/uiux-design-template.md +105 -0
  212. package/skills/unit-test-designer/SKILL.md +5 -106
  213. package/skills/unit-test-designer/references/unit-test-design-template.md +127 -0
  214. package/skills/unit-test-logic-designer/SKILL.md +28 -250
  215. package/skills/unit-test-logic-designer/references/test-patterns.md +207 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phasegate",
3
- "version": "0.16.0",
3
+ "version": "0.31.0",
4
4
  "packageManager": "pnpm@10.30.1",
5
5
  "description": "Phasegate — AI-agnostic quality defense toolkit. Enforces structural integrity between design intent and code.",
6
6
  "license": "Apache-2.0",
@@ -49,14 +49,16 @@
49
49
  "phasegate:disable": "npx tsx scripts/harness/main.ts disable-feature",
50
50
  "phasegate:check-phase": "npx tsx scripts/harness/main.ts harness:check-phase",
51
51
  "phasegate:check-ready": "npx tsx scripts/harness/main.ts harness:check-ready",
52
- "test": "vitest run --config scripts/harness/__tests__/vitest.config.ts"
52
+ "test": "vitest run --config scripts/harness/__tests__/vitest.config.forks.ts && vitest run --config scripts/harness/__tests__/vitest.config.ts"
53
53
  },
54
54
  "dependencies": {
55
55
  "ajv": "^8.18.0",
56
+ "picomatch": "^4.0.4",
56
57
  "tsx": "^4.0.0"
57
58
  },
58
59
  "devDependencies": {
59
60
  "@biomejs/biome": "^2.4.7",
61
+ "@types/picomatch": "^4.0.0",
60
62
  "@types/node": "^25.3.5",
61
63
  "typescript": "^5.0.0",
62
64
  "vitest": "^3.0.0"
@@ -1,3 +1,4 @@
1
+ // @layer domain
1
2
  import type { ADR } from '../aggregates/adr.js';
2
3
  import type { AdrBody } from '../value-objects/adr-body.js';
3
4
  import type { AdrFrontmatter } from '../value-objects/adr-frontmatter.js';
@@ -1,3 +1,4 @@
1
+ // @layer domain
1
2
  import type { AdrFrontmatter } from '../value-objects/adr-frontmatter.js';
2
3
 
3
4
  export interface AdrFrontmatterParserPort {
@@ -1,3 +1,4 @@
1
+ // @layer domain
1
2
  import type { ADR } from '../aggregates/adr.js';
2
3
  import type { AdrId } from '../value-objects/adr-id.js';
3
4
  import type { AdrStatus } from '../value-objects/adr-status.js';
@@ -5,11 +5,11 @@
5
5
  const ALLOWED_STATUSES = ['Proposed', 'Accepted', 'Deprecated', 'Superseded'] as const;
6
6
  type AdrStatusValue = (typeof ALLOWED_STATUSES)[number];
7
7
 
8
- const TRANSITIONS: Readonly<Record<AdrStatusValue, readonly AdrStatusValue[]>> = Object.freeze({
9
- Proposed: Object.freeze(['Accepted', 'Deprecated']),
10
- Accepted: Object.freeze(['Deprecated', 'Superseded']),
11
- Deprecated: Object.freeze(['Proposed']),
12
- Superseded: Object.freeze([]),
8
+ const TRANSITIONS = Object.freeze({
9
+ Proposed: Object.freeze(['Accepted', 'Deprecated'] as const),
10
+ Accepted: Object.freeze(['Deprecated', 'Superseded'] as const),
11
+ Deprecated: Object.freeze(['Proposed'] as const),
12
+ Superseded: Object.freeze([] as const),
13
13
  });
14
14
 
15
15
  export class InvalidAdrStatusError extends Error {
@@ -52,7 +52,7 @@ export class AdrStatus {
52
52
  }
53
53
 
54
54
  canTransitionTo(target: AdrStatus): boolean {
55
- return TRANSITIONS[this.value].includes(target.value);
55
+ return (TRANSITIONS[this.value] as readonly string[]).includes(target.value);
56
56
  }
57
57
 
58
58
  equals(other: AdrStatus): boolean {
@@ -11,8 +11,10 @@ export interface HandlePreToolUseInput {
11
11
  export interface HandlePreToolUseOutput {
12
12
  shouldBlock: boolean;
13
13
  blockedFilePath?: string;
14
- blockReason?: 'PROTECTED_FILE' | 'PHASE_GATE';
14
+ blockReason?: 'PROTECTED_FILE' | 'PHASE_GATE' | 'STORY_REFLECTION';
15
15
  error?: { message: string };
16
16
  phaseGateBlockers?: string[];
17
+ storyReflectionBlockers?: string[];
18
+ storyReflectionWarnings?: string[];
17
19
  nextAction?: string;
18
20
  }
@@ -9,15 +9,17 @@
9
9
 
10
10
  import { AsyncHookToCliTranslator } from '../../domain/services/hook-to-cli-translator.js';
11
11
  import { HookEvent } from '../../domain/value-objects/hook-event.js';
12
- import { ProtectedFileList } from '../../domain/value-objects/protected-file-list.js';
13
12
  import type { BlockMetadata } from '../../domain/value-objects/hook-translation-result.js';
14
13
  import type { ConfigQueryPort } from '../../domain/ports/config-query-port.js';
15
14
  import type { PhaseGateQueryPort } from '../../domain/ports/phase-gate-query-port.js';
15
+ import type { StoryReflectionQueryPort } from '../../domain/ports/story-reflection-query-port.js';
16
+ import { WriteTargetScope } from '../../domain/value-objects/write-target-scope.js';
16
17
  import type { HandlePreToolUseInput, HandlePreToolUseOutput } from '../dto/handle-pre-tool-use-dto.js';
17
18
 
18
19
  export interface HandlePreToolUseUseCasePorts {
19
20
  configQueryPort: ConfigQueryPort;
20
21
  phaseGateQueryPort: PhaseGateQueryPort;
22
+ storyReflectionQueryPort?: StoryReflectionQueryPort;
21
23
  }
22
24
 
23
25
  export class HandlePreToolUseInputValidationError extends Error {
@@ -29,11 +31,17 @@ export class HandlePreToolUseInputValidationError extends Error {
29
31
  }
30
32
 
31
33
  export class HandlePreToolUseUseCase {
34
+ private static readonly WRITE_TOOLS: ReadonlySet<string> = new Set([
35
+ 'Write', 'Edit', 'NotebookEdit', 'str_replace_editor',
36
+ ]);
37
+
32
38
  private readonly translator: AsyncHookToCliTranslator;
33
39
  private readonly configQueryPort: ConfigQueryPort;
40
+ private readonly storyReflectionQueryPort?: StoryReflectionQueryPort;
34
41
 
35
42
  constructor(ports: HandlePreToolUseUseCasePorts) {
36
43
  this.configQueryPort = ports.configQueryPort;
44
+ this.storyReflectionQueryPort = ports.storyReflectionQueryPort;
37
45
  this.translator = new AsyncHookToCliTranslator({
38
46
  configQueryPort: ports.configQueryPort,
39
47
  reentryGuard: { isActive: () => false } as never,
@@ -71,7 +79,39 @@ export class HandlePreToolUseUseCase {
71
79
  };
72
80
  }
73
81
 
74
- return { shouldBlock: false };
82
+ const scope = this.resolveStoryReflectionScope(input);
83
+ if (scope === null || this.storyReflectionQueryPort === undefined) {
84
+ return { shouldBlock: false };
85
+ }
86
+
87
+ const reflectionResult = await this.storyReflectionQueryPort.checkReflection(scope.unitId!);
88
+
89
+ if (reflectionResult.skipped || reflectionResult.passed) {
90
+ return { shouldBlock: false };
91
+ }
92
+
93
+ return HandlePreToolUseUseCase.buildStoryReflectionBlockOutput(
94
+ input.targetFilePaths[0],
95
+ reflectionResult.blockers,
96
+ reflectionResult.warnings,
97
+ );
98
+ }
99
+
100
+ private resolveStoryReflectionScope(input: HandlePreToolUseInput): WriteTargetScope | null {
101
+ if (!HandlePreToolUseUseCase.WRITE_TOOLS.has(input.toolName)) {
102
+ return null;
103
+ }
104
+
105
+ const projectPaths = this.configQueryPort.getProjectPaths();
106
+
107
+ for (const targetFilePath of input.targetFilePaths) {
108
+ const scope = WriteTargetScope.fromPath(targetFilePath, projectPaths);
109
+ if (scope?.level === 3 && scope.unitId !== undefined) {
110
+ return scope;
111
+ }
112
+ }
113
+
114
+ return null;
75
115
  }
76
116
 
77
117
  private static readonly LEVEL_LABELS: Record<number, string> = {
@@ -161,4 +201,70 @@ export class HandlePreToolUseUseCase {
161
201
  error: { message },
162
202
  };
163
203
  }
204
+
205
+ private static buildStoryReflectionBlockOutput(
206
+ blockedFilePath: string | undefined,
207
+ blockers: readonly string[],
208
+ warnings: readonly string[],
209
+ ): HandlePreToolUseOutput {
210
+ return {
211
+ shouldBlock: true,
212
+ blockedFilePath,
213
+ blockReason: 'STORY_REFLECTION',
214
+ error: {
215
+ message: HandlePreToolUseUseCase.buildStoryReflectionErrorMessage(blockers),
216
+ },
217
+ storyReflectionBlockers: [...blockers],
218
+ storyReflectionWarnings: [...warnings],
219
+ };
220
+ }
221
+
222
+ private static buildStoryReflectionErrorMessage(blockers: readonly string[]): string {
223
+ const firstBlocker = blockers[0];
224
+ const details = firstBlocker === undefined
225
+ ? null
226
+ : HandlePreToolUseUseCase.extractStoryReflectionDetails(firstBlocker);
227
+ const lines: string[] = [];
228
+
229
+ if (details !== null) {
230
+ lines.push(`[L2-STORY-REFLECTION] ${details.productPath} に`);
231
+ lines.push(`@story-id ${details.storyId} が反映されていません。`);
232
+ lines.push('');
233
+ } else {
234
+ lines.push('[L2-STORY-REFLECTION] product 文書に @story-id が反映されていません。');
235
+ lines.push('');
236
+ }
237
+
238
+ for (const blocker of blockers) {
239
+ lines.push(`- ${blocker}`);
240
+ }
241
+
242
+ lines.push('');
243
+ lines.push('修正方法:');
244
+ lines.push(' 1. cascade-updater を実行して product 文書を更新');
245
+ lines.push(
246
+ ` 2. または手動で該当 product 文書に @story-id ${details?.storyId ?? '<STORY-ID>'} を追加`,
247
+ );
248
+ lines.push('');
249
+ lines.push('参照: ADR-XXX');
250
+
251
+ return lines.join('\n');
252
+ }
253
+
254
+ private static extractStoryReflectionDetails(
255
+ blocker: string,
256
+ ): { productPath: string; storyId: string } | null {
257
+ const productPathMatch = blocker.match(/docs\/product\/construction\/[^\s]+\.md/);
258
+ const storyIdMatch = blocker.match(/@story-id\s+([A-Z][\w-]*-\d+)|\b([A-Z][\w-]*-\d+)\b/);
259
+ const storyId = storyIdMatch?.[1] ?? storyIdMatch?.[2];
260
+
261
+ if (productPathMatch === null || storyId === undefined) {
262
+ return null;
263
+ }
264
+
265
+ return {
266
+ productPath: productPathMatch[0],
267
+ storyId,
268
+ };
269
+ }
164
270
  }
@@ -8,5 +8,7 @@ export type HookType = 'pre-tool-use' | 'post-tool-use' | 'stop';
8
8
  export interface ConfigQueryPort {
9
9
  isHookEnabled(hookType: HookType): Promise<boolean>;
10
10
  getProtectedFilePatterns(): Promise<string[]>;
11
+ getProtectedFileExclusions(): Promise<string[]>;
12
+ getRelaxedGates(): Promise<readonly string[]>;
11
13
  getProjectPaths(): ProjectPaths;
12
14
  }
@@ -5,5 +5,5 @@ import type { WriteTargetScope } from '../value-objects/write-target-scope.js';
5
5
  import type { PhaseGateQueryResult } from '../value-objects/phase-gate-query-result.js';
6
6
 
7
7
  export interface PhaseGateQueryPort {
8
- checkGate(scope: WriteTargetScope): Promise<PhaseGateQueryResult>;
8
+ checkGate(scope: WriteTargetScope, targetFilePath?: string): Promise<PhaseGateQueryResult>;
9
9
  }
@@ -0,0 +1,8 @@
1
+ // @unit agent-integration
2
+ // @layer domain
3
+
4
+ import type { StoryReflectionQueryResult } from '../value-objects/story-reflection-query-result.js';
5
+
6
+ export interface StoryReflectionQueryPort {
7
+ checkReflection(unitId: string): Promise<StoryReflectionQueryResult>;
8
+ }
@@ -0,0 +1,258 @@
1
+ /**
2
+ * @layer domain
3
+ * @unit agent-integration
4
+ *
5
+ * BashWriteTargetExtractor ドメインサービス
6
+ *
7
+ * Bash コマンド文字列を静的解析し、書き込み対象となるファイルパスを抽出する。
8
+ * Phase Gate フックが Bash 経由のファイル書き込みを検知して保護するための基盤。
9
+ *
10
+ * 副作用なし・純粋関数相当。対応パターンは以下:
11
+ * - リダイレクト `>` / `>>`
12
+ * - heredoc (`<<EOF > path`)
13
+ * - `tee` / `tee -a`
14
+ * - `sed -i` / `sed -i ''` (BSD)
15
+ * - `cp` / `mv` (宛先のみ)
16
+ * - `touch`
17
+ * - 複合コマンド (`&&`, `;`, `||`) とパイプ (`|`) 分割
18
+ * - ダブル/シングルクォート対応
19
+ */
20
+
21
+ /** 引数トークン (値とクォート種別) */
22
+ type Token = {
23
+ readonly value: string;
24
+ readonly quoted: 'none' | 'single' | 'double';
25
+ };
26
+
27
+ /** シェル風の簡易トークナイザ。クォート境界を尊重する。 */
28
+ function tokenize(input: string): Token[] {
29
+ const tokens: Token[] = [];
30
+ let i = 0;
31
+ const len = input.length;
32
+
33
+ while (i < len) {
34
+ const ch = input[i];
35
+
36
+ // 空白スキップ
37
+ if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
38
+ i += 1;
39
+ continue;
40
+ }
41
+
42
+ // 演算子をそのままトークン化
43
+ if (ch === '>' || ch === '<' || ch === '|' || ch === ';' || ch === '&') {
44
+ let op = ch;
45
+ if (i + 1 < len && input[i + 1] === ch && (ch === '>' || ch === '<' || ch === '|' || ch === '&')) {
46
+ op = ch + ch;
47
+ i += 2;
48
+ } else {
49
+ i += 1;
50
+ }
51
+ tokens.push({ value: op, quoted: 'none' });
52
+ continue;
53
+ }
54
+
55
+ // ダブルクォート
56
+ if (ch === '"') {
57
+ let buf = '';
58
+ i += 1;
59
+ while (i < len && input[i] !== '"') {
60
+ if (input[i] === '\\' && i + 1 < len) {
61
+ buf += input[i + 1];
62
+ i += 2;
63
+ } else {
64
+ buf += input[i];
65
+ i += 1;
66
+ }
67
+ }
68
+ i += 1; // closing quote
69
+ tokens.push({ value: buf, quoted: 'double' });
70
+ continue;
71
+ }
72
+
73
+ // シングルクォート
74
+ if (ch === "'") {
75
+ let buf = '';
76
+ i += 1;
77
+ while (i < len && input[i] !== "'") {
78
+ buf += input[i];
79
+ i += 1;
80
+ }
81
+ i += 1; // closing quote
82
+ tokens.push({ value: buf, quoted: 'single' });
83
+ continue;
84
+ }
85
+
86
+ // 通常トークン
87
+ let buf = '';
88
+ while (i < len) {
89
+ const c = input[i];
90
+ if (c === ' ' || c === '\t' || c === '\n' || c === '\r') break;
91
+ if (c === '>' || c === '<' || c === '|' || c === ';' || c === '&') break;
92
+ if (c === '"' || c === "'") break;
93
+ buf += c;
94
+ i += 1;
95
+ }
96
+ if (buf.length > 0) {
97
+ tokens.push({ value: buf, quoted: 'none' });
98
+ }
99
+ }
100
+
101
+ return tokens;
102
+ }
103
+
104
+ const OPERATOR_SEPARATORS = new Set(['&&', '||', ';', '|']);
105
+
106
+ /** トークン列を複合・パイプ境界で分割 */
107
+ function splitByOperators(tokens: Token[]): Token[][] {
108
+ const groups: Token[][] = [];
109
+ let current: Token[] = [];
110
+ for (const t of tokens) {
111
+ if (t.quoted === 'none' && OPERATOR_SEPARATORS.has(t.value)) {
112
+ if (current.length > 0) {
113
+ groups.push(current);
114
+ current = [];
115
+ }
116
+ continue;
117
+ }
118
+ current.push(t);
119
+ }
120
+ if (current.length > 0) groups.push(current);
121
+ return groups;
122
+ }
123
+
124
+ /** コマンドグループ先頭の環境変数代入 (`FOO=bar`) をスキップして実コマンドを返す */
125
+ function getCommandName(tokens: Token[]): string | undefined {
126
+ for (const t of tokens) {
127
+ if (t.quoted === 'none' && /^[A-Za-z_][A-Za-z0-9_]*=/.test(t.value)) continue;
128
+ return t.value;
129
+ }
130
+ return undefined;
131
+ }
132
+
133
+ /**
134
+ * 1 コマンド分のトークン列から書き込み先を抽出する。
135
+ * リダイレクト先はコマンド種別によらず検出する (全コマンド共通)。
136
+ */
137
+ function extractFromSingleCommand(tokens: Token[]): string[] {
138
+ const results: string[] = [];
139
+
140
+ // 1) リダイレクト `>` / `>>` の右辺 (heredoc の `>` もこのパスで拾える)
141
+ for (let i = 0; i < tokens.length; i += 1) {
142
+ const t = tokens[i];
143
+ if (t.quoted === 'none' && (t.value === '>' || t.value === '>>')) {
144
+ const next = tokens[i + 1];
145
+ if (next !== undefined) {
146
+ results.push(next.value);
147
+ }
148
+ }
149
+ }
150
+
151
+ const cmd = getCommandName(tokens);
152
+ if (cmd === undefined) return results;
153
+
154
+ // コマンド別の特殊処理
155
+ const baseName = cmd.split('/').pop() ?? cmd;
156
+
157
+ if (baseName === 'tee') {
158
+ // `tee [-a] FILE...`
159
+ for (let i = 1; i < tokens.length; i += 1) {
160
+ const t = tokens[i];
161
+ if (t.quoted === 'none' && t.value.startsWith('-')) continue;
162
+ if (t.quoted === 'none' && (t.value === '>' || t.value === '>>' || t.value === '|' || t.value === '<')) break;
163
+ results.push(t.value);
164
+ }
165
+ } else if (baseName === 'sed') {
166
+ // `sed -i [''] SCRIPT FILE` — `-i` の直後の空文字列 (BSD) はバックアップ拡張子
167
+ const argTokens = tokens.slice(1);
168
+ let inPlace = false;
169
+ let skipBackupExt = false;
170
+ const positionals: Token[] = [];
171
+ for (let i = 0; i < argTokens.length; i += 1) {
172
+ const t = argTokens[i];
173
+ if (t.quoted === 'none' && t.value === '-i') {
174
+ inPlace = true;
175
+ skipBackupExt = true;
176
+ continue;
177
+ }
178
+ if (t.quoted === 'none' && t.value.startsWith('-i')) {
179
+ // -iBAK
180
+ inPlace = true;
181
+ continue;
182
+ }
183
+ if (t.quoted === 'none' && t.value.startsWith('-')) continue;
184
+ if (skipBackupExt && t.quoted !== 'none' && t.value === '') {
185
+ // BSD `sed -i ''` — 空文字はバックアップ拡張子扱いで無視
186
+ skipBackupExt = false;
187
+ continue;
188
+ }
189
+ skipBackupExt = false;
190
+ positionals.push(t);
191
+ }
192
+ if (inPlace && positionals.length >= 2) {
193
+ // 先頭はスクリプト、残りはファイル
194
+ for (let i = 1; i < positionals.length; i += 1) {
195
+ results.push(positionals[i].value);
196
+ }
197
+ }
198
+ } else if (baseName === 'cp' || baseName === 'mv') {
199
+ // 宛先は最後の非オプション引数
200
+ const positionals: Token[] = [];
201
+ for (let i = 1; i < tokens.length; i += 1) {
202
+ const t = tokens[i];
203
+ if (t.quoted === 'none' && t.value.startsWith('-')) continue;
204
+ if (t.quoted === 'none' && (t.value === '>' || t.value === '>>' || t.value === '|' || t.value === '<')) break;
205
+ positionals.push(t);
206
+ }
207
+ if (positionals.length >= 2) {
208
+ results.push(positionals[positionals.length - 1].value);
209
+ }
210
+ } else if (baseName === 'touch') {
211
+ for (let i = 1; i < tokens.length; i += 1) {
212
+ const t = tokens[i];
213
+ if (t.quoted === 'none' && t.value.startsWith('-')) continue;
214
+ if (t.quoted === 'none' && (t.value === '>' || t.value === '>>' || t.value === '|' || t.value === '<')) break;
215
+ results.push(t.value);
216
+ }
217
+ }
218
+
219
+ return results;
220
+ }
221
+
222
+ export class BashWriteTargetExtractor {
223
+ /**
224
+ * Bash コマンド文字列から書き込み先ファイルパスを抽出する。
225
+ * 副作用なし、純粋関数相当。
226
+ *
227
+ * @param command Bash コマンド文字列
228
+ * @returns 書き込み先ファイルパスの配列 (重複除去済み・入力順)
229
+ */
230
+ extract(command: string): readonly string[] {
231
+ if (typeof command !== 'string' || command.length === 0) {
232
+ return Object.freeze([]);
233
+ }
234
+
235
+ const tokens = tokenize(command);
236
+ const groups = splitByOperators(tokens);
237
+
238
+ const collected: string[] = [];
239
+ for (const group of groups) {
240
+ const found = extractFromSingleCommand(group);
241
+ for (const path of found) {
242
+ collected.push(path);
243
+ }
244
+ }
245
+
246
+ // 重複除去 (挿入順保持)
247
+ const seen = new Set<string>();
248
+ const unique: string[] = [];
249
+ for (const p of collected) {
250
+ if (p.length === 0) continue;
251
+ if (seen.has(p)) continue;
252
+ seen.add(p);
253
+ unique.push(p);
254
+ }
255
+
256
+ return Object.freeze(unique);
257
+ }
258
+ }
@@ -169,7 +169,7 @@ export class AsyncHookToCliTranslator {
169
169
  hasCommand(commandName: string): Promise<boolean>;
170
170
  };
171
171
  private readonly phaseGateQueryPort: {
172
- checkGate(scope: WriteTargetScope): Promise<{
172
+ checkGate(scope: WriteTargetScope, targetFilePath?: string): Promise<{
173
173
  hasPassed(): boolean;
174
174
  getBlockers(): readonly string[];
175
175
  getWarnings(): readonly string[];
@@ -181,7 +181,7 @@ export class AsyncHookToCliTranslator {
181
181
  reentryGuard: ReentryGuard;
182
182
  cliCommandRegistryPort: { hasCommand(commandName: string): Promise<boolean> };
183
183
  phaseGateQueryPort?: {
184
- checkGate(scope: WriteTargetScope): Promise<{
184
+ checkGate(scope: WriteTargetScope, targetFilePath?: string): Promise<{
185
185
  hasPassed(): boolean;
186
186
  getBlockers(): readonly string[];
187
187
  getWarnings(): readonly string[];
@@ -222,7 +222,11 @@ export class AsyncHookToCliTranslator {
222
222
  private async translatePreToolUse(event: PreToolUseEvent): Promise<HookTranslationResult> {
223
223
  // Step 1: Protected file check (applies to all tools)
224
224
  const additionalPatterns = await this.configQueryPort.getProtectedFilePatterns();
225
- const protectedFileList = ProtectedFileList.createWithAdditional(additionalPatterns);
225
+ const exclusions = await this.configQueryPort.getProtectedFileExclusions();
226
+ const protectedFileList = ProtectedFileList.createWithAdditionalAndExclusions(
227
+ additionalPatterns,
228
+ exclusions,
229
+ );
226
230
 
227
231
  const blockedPath = event.targetFilePaths.find((fp) => protectedFileList.matches(fp));
228
232
  if (blockedPath !== undefined) {
@@ -256,7 +260,20 @@ export class AsyncHookToCliTranslator {
256
260
  });
257
261
  }
258
262
 
259
- const phaseGateResult = await this.phaseGateQueryPort.checkGate(detectedScope);
263
+ // Step 3: Check if phase gate is relaxed via quickMode.relaxedGates
264
+ const relaxedGates = await this.configQueryPort.getRelaxedGates();
265
+ if (relaxedGates.includes('phase-gate')) {
266
+ return HookTranslationResult.create({
267
+ shouldBlock: false,
268
+ cliArgs: [],
269
+ expectedExitCode: 0,
270
+ });
271
+ }
272
+
273
+ const phaseGateResult = await this.phaseGateQueryPort.checkGate(
274
+ detectedScope,
275
+ event.targetFilePaths[0],
276
+ );
260
277
  if (!phaseGateResult.hasPassed()) {
261
278
  return HookTranslationResult.block({
262
279
  reason: 'PHASE_GATE',
@@ -69,6 +69,26 @@ export class ProtectedFileList {
69
69
  return new ProtectedFileList(allPatterns);
70
70
  }
71
71
 
72
+ static createWithExclusions(exclusions: string[]): ProtectedFileList {
73
+ const filtered = DEFAULT_PATTERNS.filter((p) => !exclusions.includes(p));
74
+ if (filtered.length === 0) {
75
+ return new ProtectedFileList([...DEFAULT_PATTERNS]);
76
+ }
77
+ return new ProtectedFileList(filtered);
78
+ }
79
+
80
+ static createWithAdditionalAndExclusions(
81
+ additionalPatterns: string[],
82
+ exclusions: string[],
83
+ ): ProtectedFileList {
84
+ const base = DEFAULT_PATTERNS.filter((p) => !exclusions.includes(p));
85
+ const allPatterns = [...base, ...additionalPatterns];
86
+ if (allPatterns.length === 0) {
87
+ return new ProtectedFileList([...DEFAULT_PATTERNS]);
88
+ }
89
+ return new ProtectedFileList(allPatterns);
90
+ }
91
+
72
92
  matches(filePath: string): boolean {
73
93
  if (filePath === '') return false;
74
94
  return this.patterns.some((pattern) => {
@@ -0,0 +1,63 @@
1
+ // @unit agent-integration
2
+ // @layer domain
3
+
4
+ export class StoryReflectionQueryResultInvariantError extends Error {
5
+ constructor(message: string) {
6
+ super(message);
7
+ this.name = 'StoryReflectionQueryResultInvariantError';
8
+ Object.setPrototypeOf(this, new.target.prototype);
9
+ }
10
+ }
11
+
12
+ export class StoryReflectionQueryResult {
13
+ readonly passed: boolean;
14
+ readonly blockers: readonly string[];
15
+ readonly warnings: readonly string[];
16
+ readonly skipped: boolean;
17
+
18
+ private constructor(
19
+ passed: boolean,
20
+ blockers: readonly string[],
21
+ warnings: readonly string[],
22
+ skipped: boolean,
23
+ ) {
24
+ this.passed = passed;
25
+ this.blockers = Object.freeze([...blockers]);
26
+ this.warnings = Object.freeze([...warnings]);
27
+ this.skipped = skipped;
28
+ }
29
+
30
+ static pass(): StoryReflectionQueryResult {
31
+ return new StoryReflectionQueryResult(true, [], [], false);
32
+ }
33
+
34
+ static skipped(warnings: string[] = []): StoryReflectionQueryResult {
35
+ return new StoryReflectionQueryResult(true, [], warnings, true);
36
+ }
37
+
38
+ static block(blockers: string[], warnings: string[]): StoryReflectionQueryResult {
39
+ if (blockers.length === 0) {
40
+ throw new StoryReflectionQueryResultInvariantError(
41
+ 'passed=falseの場合、blockersは1件以上必要です',
42
+ );
43
+ }
44
+
45
+ return new StoryReflectionQueryResult(false, blockers, warnings, false);
46
+ }
47
+
48
+ hasPassed(): boolean {
49
+ return this.passed;
50
+ }
51
+
52
+ getBlockers(): readonly string[] {
53
+ return this.blockers;
54
+ }
55
+
56
+ getWarnings(): readonly string[] {
57
+ return this.warnings;
58
+ }
59
+
60
+ isSkipped(): boolean {
61
+ return this.skipped;
62
+ }
63
+ }