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
@@ -0,0 +1,122 @@
1
+ // @unit agent-integration
2
+ // @layer infrastructure
3
+ /**
4
+ * FileSystemStoryReflectionQueryAdapter
5
+ *
6
+ * agent-integration ドメインの `StoryReflectionQueryPort` を、
7
+ * phase-dependency-model 側の CheckStoryReflectionUseCase +
8
+ * FileSystemStoryReflectionAdapter + HarnessConfigPhaseConfigProvider を
9
+ * オーケストレートして実装するアダプタ。
10
+ *
11
+ * 役割:
12
+ * 1. phasegate.config.json を読み込み、PhaseConfigSection へ正規化
13
+ * 2. HarnessConfigPhaseConfigProvider 経由で storyReflection 設定を解決
14
+ * 3. StoryReflectionChecker + FileSystemStoryReflectionAdapter 経由で
15
+ * inception → product の @story-id 反映を検証
16
+ * 4. 結果を agent-integration の StoryReflectionQueryResult に写像
17
+ *
18
+ * 例外: 設定ファイルが存在しない/読めない場合は "skipped" を返し、
19
+ * hook 全体をブロックしない(Fail-safe)。
20
+ */
21
+
22
+ import { readFile } from 'node:fs/promises';
23
+
24
+ import type { StoryReflectionQueryPort } from '../../domain/ports/story-reflection-query-port.js';
25
+ import { StoryReflectionQueryResult } from '../../domain/value-objects/story-reflection-query-result.js';
26
+ import { FileSystemStoryReflectionAdapter } from '../../../phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.js';
27
+ import { StoryReflectionChecker } from '../../../phase-dependency-model/domain/services/story-reflection-checker.js';
28
+ import { CheckStoryReflectionUseCase } from '../../../phase-dependency-model/application/usecases/check-story-reflection-usecase.js';
29
+ import {
30
+ HarnessConfigPhaseConfigProvider,
31
+ type PhaseConfigSection,
32
+ type PhasePresetInput,
33
+ type StoryReflectionSectionInput,
34
+ } from '../../../phase-dependency-model/infrastructure/config/harness-config-phase-config-provider.js';
35
+ import type { StoryReflectionResult } from '../../../phase-dependency-model/domain/values/story-reflection-result.js';
36
+
37
+ export interface FileSystemStoryReflectionQueryAdapterDeps {
38
+ readonly rootDir: string;
39
+ readonly configPath: string;
40
+ }
41
+
42
+ interface RawPhaseDependenciesSection {
43
+ readonly preset?: PhasePresetInput;
44
+ readonly override?: boolean;
45
+ readonly storyReflection?: StoryReflectionSectionInput;
46
+ }
47
+
48
+ interface RawConfigDocument {
49
+ readonly phaseDependencies?: RawPhaseDependenciesSection;
50
+ readonly reporting?: { readonly outputDir?: string };
51
+ }
52
+
53
+ export class FileSystemStoryReflectionQueryAdapter
54
+ implements StoryReflectionQueryPort
55
+ {
56
+ private readonly rootDir: string;
57
+ private readonly configPath: string;
58
+
59
+ constructor(deps: FileSystemStoryReflectionQueryAdapterDeps) {
60
+ this.rootDir = deps.rootDir;
61
+ this.configPath = deps.configPath;
62
+ }
63
+
64
+ async checkReflection(unitId: string): Promise<StoryReflectionQueryResult> {
65
+ let raw: RawConfigDocument;
66
+ try {
67
+ const content = await readFile(this.configPath, 'utf8');
68
+ raw = JSON.parse(content) as RawConfigDocument;
69
+ } catch {
70
+ // 設定が読めない場合は fail-safe で skipped 扱い
71
+ return StoryReflectionQueryResult.skipped();
72
+ }
73
+
74
+ const phaseConfigSection: PhaseConfigSection = {
75
+ customization: {
76
+ preset: raw.phaseDependencies?.preset,
77
+ overrideEnabled: raw.phaseDependencies?.override ?? false,
78
+ },
79
+ storyReflection: raw.phaseDependencies?.storyReflection,
80
+ reportingOutputDir: raw.reporting?.outputDir,
81
+ };
82
+
83
+ const provider = new HarnessConfigPhaseConfigProvider({
84
+ config: phaseConfigSection,
85
+ defaultOutputDir: raw.reporting?.outputDir ?? '.harness/reports',
86
+ });
87
+
88
+ const config = await provider.getStoryReflectionConfig();
89
+
90
+ if (!config.enabled) {
91
+ return StoryReflectionQueryResult.skipped();
92
+ }
93
+
94
+ const fsAdapter = new FileSystemStoryReflectionAdapter({ rootDir: this.rootDir });
95
+ const checker = new StoryReflectionChecker(fsAdapter);
96
+ const useCase = new CheckStoryReflectionUseCase({ checker });
97
+
98
+ const result = await useCase.execute({ unitId, config });
99
+
100
+ return FileSystemStoryReflectionQueryAdapter.mapResult(result);
101
+ }
102
+
103
+ private static mapResult(
104
+ result: StoryReflectionResult,
105
+ ): StoryReflectionQueryResult {
106
+ const warnings = result.warnings.map(
107
+ (w) =>
108
+ `${w.storyId}: ${w.productPath} に @story-id ${w.storyId} が未反映 (optional, inception: ${w.inceptionPath})`,
109
+ );
110
+
111
+ if (!result.isBlocked()) {
112
+ return StoryReflectionQueryResult.pass();
113
+ }
114
+
115
+ const blockers = result.violations.map(
116
+ (v) =>
117
+ `${v.productPath} に @story-id ${v.storyId} が反映されていません (inception: ${v.inceptionPath})`,
118
+ );
119
+
120
+ return StoryReflectionQueryResult.block(blockers, warnings);
121
+ }
122
+ }
@@ -31,9 +31,19 @@ interface HarnessesSection {
31
31
  deadCodeGC?: boolean;
32
32
  }
33
33
 
34
+ interface ProtectedFilesSection {
35
+ exclude?: string[];
36
+ }
37
+
38
+ interface QuickModeSection {
39
+ relaxedGates?: string[];
40
+ }
41
+
34
42
  interface HarnessConfigDocument {
35
43
  harnesses?: HarnessesSection;
36
44
  project?: ProjectSection;
45
+ protectedFiles?: ProtectedFilesSection;
46
+ quickMode?: QuickModeSection;
37
47
  }
38
48
 
39
49
  export class HarnessConfigConfigQueryAdapter implements ConfigQueryPort {
@@ -77,6 +87,16 @@ export class HarnessConfigConfigQueryAdapter implements ConfigQueryPort {
77
87
  return [];
78
88
  }
79
89
 
90
+ async getProtectedFileExclusions(): Promise<string[]> {
91
+ const config = this.loadConfig();
92
+ return config.protectedFiles?.exclude ?? [];
93
+ }
94
+
95
+ async getRelaxedGates(): Promise<readonly string[]> {
96
+ const config = this.loadConfig();
97
+ return config.quickMode?.relaxedGates ?? [];
98
+ }
99
+
80
100
  getProjectPaths(): ProjectPaths {
81
101
  const config = this.loadConfig();
82
102
  const paths = config.project?.paths;
@@ -4,16 +4,26 @@
4
4
  import type { PhaseGateQueryPort } from '../../domain/ports/phase-gate-query-port.js';
5
5
  import type { WriteTargetScope } from '../../domain/value-objects/write-target-scope.js';
6
6
  import { PhaseGateQueryResult } from '../../domain/value-objects/phase-gate-query-result.js';
7
+ import { toPhaseConfigSection } from '../../../config-foundation/application/mappers/phase-config-section-mapper.js';
8
+ import { createConfigFoundationModule } from '../../../config-foundation/composition-root.js';
9
+ import { ConfigValidationError } from '../../../config-foundation/domain/errors/config-validation-error.js';
7
10
 
8
11
  export class PhaseGateQueryAdapter implements PhaseGateQueryPort {
9
- async checkGate(scope: WriteTargetScope): Promise<PhaseGateQueryResult> {
12
+ async checkGate(scope: WriteTargetScope, targetFilePath?: string): Promise<PhaseGateQueryResult> {
10
13
  try {
14
+ const configModule = createConfigFoundationModule();
15
+ const resolvedConfig = await configModule.usecases.loadResolvedConfigUseCase.execute();
11
16
  const { createPhaseDependencyModelModule } = await import('../../../phase-dependency-model/composition-root.js');
12
- const mod = createPhaseDependencyModelModule({ rootDir: process.cwd() });
17
+ const mod = createPhaseDependencyModelModule({
18
+ rootDir: process.cwd(),
19
+ phaseConfig: toPhaseConfigSection(resolvedConfig.config),
20
+ reportOutputDir: resolvedConfig.config.reporting.outputDir,
21
+ });
13
22
  const result = await mod.checkPhaseGateCommandHandler.execute({
14
23
  targetLevel: scope.level,
15
24
  unitId: scope.unitId,
16
25
  storyId: scope.storyId,
26
+ targetFilePath,
17
27
  });
18
28
 
19
29
  if (result.exitCode === 0) {
@@ -25,7 +35,11 @@ export class PhaseGateQueryAdapter implements PhaseGateQueryPort {
25
35
  }
26
36
 
27
37
  return PhaseGateQueryResult.create(true, [], ['phase gate check returned error']);
28
- } catch {
38
+ } catch (error) {
39
+ if (error instanceof ConfigValidationError) {
40
+ return PhaseGateQueryResult.create(false, [error.message], []);
41
+ }
42
+
29
43
  return PhaseGateQueryResult.create(true, [], ['phase-dependency-model not available']);
30
44
  }
31
45
  }
@@ -8,8 +8,10 @@
8
8
  */
9
9
 
10
10
  import { HandlePreToolUseUseCase } from '../application/usecases/handle-pre-tool-use-usecase.js';
11
+ import { BashWriteTargetExtractor } from '../domain/services/bash-write-target-extractor.js';
11
12
  import { HarnessConfigConfigQueryAdapter } from '../infrastructure/adapters/harness-config-config-query-adapter.js';
12
13
  import { PhaseGateQueryAdapter } from '../infrastructure/adapters/phase-gate-query-adapter.js';
14
+ import { FileSystemStoryReflectionQueryAdapter } from '../infrastructure/adapters/file-system-story-reflection-query-adapter.js';
13
15
  import * as path from 'node:path';
14
16
  import * as fs from 'node:fs/promises';
15
17
 
@@ -20,6 +22,7 @@ interface PreToolUseHookInput {
20
22
  path?: string;
21
23
  file_path?: string;
22
24
  paths?: string[];
25
+ command?: string;
23
26
  [key: string]: unknown;
24
27
  };
25
28
  }
@@ -91,13 +94,37 @@ async function main(): Promise<void> {
91
94
  targetFilePaths.push(...input.tool_input.paths.map(toRelative));
92
95
  }
93
96
 
97
+ // Bash 経由書き込みのフェーズゲート対応 (A-2.5)
98
+ // Bash command 文字列からリダイレクト・tee・sed -i・cp・mv・touch 等の
99
+ // 書き込み先ファイルパスを抽出し、フェーズゲートチェック対象に含める。
100
+ // Bash 書き込みを検出した場合、effectiveToolName を 'Write' に偽装して
101
+ // translator の WRITE_TOOLS チェックを通過させる(Bash のままではフェーズゲートが
102
+ // スキップされるため)。
103
+ let effectiveToolName = toolName;
104
+ if (toolName === 'Bash' && typeof input.tool_input?.command === 'string') {
105
+ const extractor = new BashWriteTargetExtractor();
106
+ const bashTargets = extractor.extract(input.tool_input.command);
107
+ if (bashTargets.length > 0) {
108
+ targetFilePaths.push(...bashTargets.map(toRelative));
109
+ effectiveToolName = 'Write';
110
+ }
111
+ }
112
+
94
113
  try {
95
114
  const configPath = await findConfigPath();
96
115
  const configQueryPort = new HarnessConfigConfigQueryAdapter(configPath);
97
116
  const phaseGateQueryPort = new PhaseGateQueryAdapter();
98
- const useCase = new HandlePreToolUseUseCase({ configQueryPort, phaseGateQueryPort });
117
+ const storyReflectionQueryPort = new FileSystemStoryReflectionQueryAdapter({
118
+ rootDir: path.dirname(configPath),
119
+ configPath,
120
+ });
121
+ const useCase = new HandlePreToolUseUseCase({
122
+ configQueryPort,
123
+ phaseGateQueryPort,
124
+ storyReflectionQueryPort,
125
+ });
99
126
 
100
- const output = await useCase.execute({ toolName, targetFilePaths });
127
+ const output = await useCase.execute({ toolName: effectiveToolName, targetFilePaths });
101
128
 
102
129
  if (output.shouldBlock) {
103
130
  const msg = output.error?.message
@@ -26,6 +26,9 @@ const toStringArray = (value: unknown, fallback: readonly string[]): readonly st
26
26
  ? Object.freeze([...value])
27
27
  : fallback;
28
28
 
29
+ const isTestFile = (snapshot: SourceModuleSnapshot): boolean =>
30
+ snapshot.filePath.toString().includes('__tests__/');
31
+
29
32
  export class LintRunner {
30
33
  private readonly ruleDefinitionRegistry: RuleDefinitionRegistry;
31
34
 
@@ -98,6 +101,10 @@ export class LintRunner {
98
101
  LayerBoundary.standardMatrix(),
99
102
  layerByFile
100
103
  )) {
104
+ const fromSnapshot = snapshotByPath.get(edge.from.toString());
105
+ if (fromSnapshot && isTestFile(fromSnapshot)) {
106
+ continue;
107
+ }
101
108
  ruleViolations.push(
102
109
  RuleViolation.create({
103
110
  filePath: edge.from,
@@ -112,6 +119,10 @@ export class LintRunner {
112
119
 
113
120
  for (const cycle of params.importGraph.detectCycles()) {
114
121
  const firstPath = cycle.path[0];
122
+ const firstSnapshot = snapshotByPath.get(firstPath.toString());
123
+ if (firstSnapshot && isTestFile(firstSnapshot)) {
124
+ continue;
125
+ }
115
126
  ruleViolations.push(
116
127
  RuleViolation.create({
117
128
  filePath: firstPath,
@@ -127,6 +138,9 @@ export class LintRunner {
127
138
  }
128
139
  case 'enforce-folder-structure': {
129
140
  for (const snapshot of params.snapshots) {
141
+ if (isTestFile(snapshot)) {
142
+ continue;
143
+ }
130
144
  if (snapshot.declaredLayer !== null && !snapshot.belongsToLayerDirectory()) {
131
145
  ruleViolations.push(
132
146
  RuleViolation.create({
@@ -147,6 +161,9 @@ export class LintRunner {
147
161
  const maxAnyRatio = toNumber(rule.config.maxAnyRatio, 0.05);
148
162
 
149
163
  for (const snapshot of params.snapshots) {
164
+ if (isTestFile(snapshot)) {
165
+ continue;
166
+ }
150
167
  if (
151
168
  snapshot.anyTypeCount > maxAnyCount ||
152
169
  snapshot.anyRatio() > maxAnyRatio
@@ -223,8 +240,15 @@ export class LintRunner {
223
240
  case 'no-comment-flood': {
224
241
  const maxCommentRatio = toNumber(rule.config.maxCommentRatio, 0.35);
225
242
  const maxRepeatedBlocks = toNumber(rule.config.maxRepeatedBlocks, 1);
243
+ const minLogicalLines = toNumber(rule.config.minLogicalLines, 15);
226
244
 
227
245
  for (const snapshot of params.snapshots) {
246
+ if (isTestFile(snapshot)) {
247
+ continue;
248
+ }
249
+ if (snapshot.logicalLineCount < minLogicalLines) {
250
+ continue;
251
+ }
228
252
  if (
229
253
  snapshot.commentDensity() > maxCommentRatio ||
230
254
  snapshot.repeatedCommentBlocks > maxRepeatedBlocks
@@ -92,7 +92,7 @@ export class RuleDefinitionRegistry {
92
92
  'no-comment-flood',
93
93
  'L1-008',
94
94
  Object.freeze([REQUIRED_INPUT.sourceModuleSnapshots]),
95
- Object.freeze({ maxCommentRatio: 0.35, maxRepeatedBlocks: 1 }),
95
+ Object.freeze({ maxCommentRatio: 0.35, maxRepeatedBlocks: 1, minLogicalLines: 15 }),
96
96
  'コメント過多を検出する',
97
97
  '不要なコメントを整理し自己説明的なコードへ改善する'
98
98
  ),
@@ -59,8 +59,9 @@ export class SourceModuleSnapshot {
59
59
  readonly duplicationFingerprints: readonly string[];
60
60
  readonly exportedSymbols: readonly string[];
61
61
  readonly isEntrypointCandidate: boolean;
62
+ readonly hasRawLayerAnnotation: boolean;
62
63
 
63
- private constructor(props: Omit<SourceModuleSnapshotProps, 'declaredLayer'> & { declaredLayer: LayerName | null }) {
64
+ private constructor(props: Omit<SourceModuleSnapshotProps, 'declaredLayer'> & { declaredLayer: LayerName | null; hasRawLayerAnnotation: boolean }) {
64
65
  this.filePath = props.filePath;
65
66
  this.declaredUnit = props.declaredUnit;
66
67
  this.declaredLayer = props.declaredLayer;
@@ -73,6 +74,7 @@ export class SourceModuleSnapshot {
73
74
  this.duplicationFingerprints = props.duplicationFingerprints;
74
75
  this.exportedSymbols = props.exportedSymbols;
75
76
  this.isEntrypointCandidate = props.isEntrypointCandidate;
77
+ this.hasRawLayerAnnotation = props.hasRawLayerAnnotation;
76
78
  }
77
79
 
78
80
  static create(props: SourceModuleSnapshotProps): SourceModuleSnapshot {
@@ -83,6 +85,7 @@ export class SourceModuleSnapshot {
83
85
  filePath: props.filePath,
84
86
  declaredUnit: props.declaredUnit,
85
87
  declaredLayer,
88
+ hasRawLayerAnnotation: props.declaredLayer !== null,
86
89
  imports: Object.freeze([...props.imports]),
87
90
  anyTypeCount: ensureNonNegative(props.anyTypeCount, 'anyTypeCount'),
88
91
  typedNodeCount: ensureNonNegative(props.typedNodeCount, 'typedNodeCount'),
@@ -104,7 +107,7 @@ export class SourceModuleSnapshot {
104
107
  }
105
108
 
106
109
  hasLayerComment(): boolean {
107
- return this.declaredLayer !== null;
110
+ return this.hasRawLayerAnnotation;
108
111
  }
109
112
 
110
113
  anyRatio(): number {
@@ -11,7 +11,7 @@ import { FilePath as FilePathVO } from '../../domain/value-objects/file-path.js'
11
11
  import type { WorkspaceFilePort } from '../../domain/ports/workspace-file-port.js';
12
12
 
13
13
  const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts']);
14
- const EXCLUDED_DIRS = new Set(['node_modules', 'dist', 'coverage', '__fixtures__']);
14
+ const EXCLUDED_DIRS = new Set(['node_modules', 'dist', 'coverage', '__fixtures__', 'fixtures']);
15
15
 
16
16
  export interface NodeWorkspaceFileAdapterDeps {
17
17
  readonly rootDir: string;
@@ -64,7 +64,7 @@ export class TypeScriptSourceModuleAnalyzerAdapter implements SourceModuleAnalyz
64
64
  snapshots.push(
65
65
  SourceModuleSnapshot.create({
66
66
  filePath,
67
- declaredUnit: unitResult.unitName,
67
+ declaredUnit: unitResult.unitNames[0],
68
68
  declaredLayer: layerResult.layerName,
69
69
  imports,
70
70
  anyTypeCount: anyCount,
@@ -3,24 +3,29 @@
3
3
  * @unit biome-ast-engine
4
4
  */
5
5
 
6
- const UNIT_COMMENT_PATTERN = /^\s*(?:\/\/|\/\*\*?\s*|\*)\s*@unit\s+(\S+)/m;
6
+ const UNIT_COMMENT_PATTERN = /^\s*(?:\/\/|\/\*\*?\s*|\*)\s*@unit\s+(.+)/gm;
7
7
 
8
8
  export type UnitCommentResult = {
9
- readonly unitName: string | null;
9
+ readonly unitNames: readonly string[];
10
10
  };
11
11
 
12
12
  /**
13
13
  * ソースコードから `// @unit {unit}` コメントを抽出する。
14
14
  * JSDoc (`/** @unit ... * /`) 形式にも対応する。
15
+ * カンマ区切りで複数ユニットを指定でき、複数行の @unit も収集する。
16
+ * 結果は重複除去済み。
15
17
  */
16
18
  export const parseUnitComment = (sourceCode: string): UnitCommentResult => {
17
- const match = sourceCode.match(UNIT_COMMENT_PATTERN);
19
+ const matches = sourceCode.matchAll(UNIT_COMMENT_PATTERN);
20
+ const names: string[] = [];
18
21
 
19
- if (!match) {
20
- return { unitName: null };
22
+ for (const match of matches) {
23
+ const raw = match[1].replace(/\s*\*\/$/, '').trim();
24
+ const parts = raw.split(',').map((s) => s.trim()).filter((s) => s.length > 0);
25
+ names.push(...parts);
21
26
  }
22
27
 
23
- const raw = match[1].replace(/\s*\*\/$/, '');
28
+ const deduplicated = [...new Set(names)];
24
29
 
25
- return { unitName: raw };
30
+ return { unitNames: Object.freeze(deduplicated) };
26
31
  };
@@ -1,4 +1,5 @@
1
1
  /**
2
+ * @layer infrastructure
2
3
  * @unit ci-governance
3
4
  *
4
5
  * Composition Root - 依存性の組み立て
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @layer application
3
+ * @unit config-foundation
4
+ */
5
+ import type { HarnessConfigV2 } from '../../domain/harness-config.js';
6
+
7
+ function toPhasePreset(
8
+ value: string,
9
+ ): 'default' | 'full' | 'standard' | 'minimal' | 'custom' {
10
+ if (
11
+ value === 'default'
12
+ || value === 'full'
13
+ || value === 'standard'
14
+ || value === 'minimal'
15
+ || value === 'custom'
16
+ ) {
17
+ return value;
18
+ }
19
+
20
+ return 'default';
21
+ }
22
+
23
+ type PhaseDependenciesWithGates = HarnessConfigV2['phaseDependencies'] & {
24
+ readonly gates?: readonly unknown[];
25
+ };
26
+
27
+ export function toPhaseConfigSection(resolvedConfig: HarnessConfigV2) {
28
+ const phaseDependencies = resolvedConfig.phaseDependencies as PhaseDependenciesWithGates;
29
+
30
+ return {
31
+ planningMode: resolvedConfig.planningMode,
32
+ customization: {
33
+ preset: toPhasePreset(phaseDependencies.preset),
34
+ overrideEnabled: phaseDependencies.override,
35
+ rules: phaseDependencies.customRules.map((rule) => ({
36
+ targetPhase: rule.phase,
37
+ condition: 'requires' as const,
38
+ action: rule.requires,
39
+ })),
40
+ gates: phaseDependencies.gates ?? [],
41
+ },
42
+ reportingOutputDir: resolvedConfig.reporting.outputDir,
43
+ };
44
+ }
@@ -42,7 +42,26 @@ function formatHarnessErrors(errors: readonly HarnessError[]): string {
42
42
  }
43
43
 
44
44
  function toSourceDocument(document: unknown): HarnessConfigSourceDocument {
45
- return document as HarnessConfigSourceDocument;
45
+ const sourceDocument = document as HarnessConfigSourceDocument & {
46
+ readonly phaseDependencies?: HarnessConfigSourceDocument['phaseDependencies'] & {
47
+ readonly gates?: readonly unknown[];
48
+ };
49
+ };
50
+
51
+ if (
52
+ sourceDocument.phaseDependencies === undefined
53
+ || sourceDocument.phaseDependencies.gates === undefined
54
+ ) {
55
+ return sourceDocument;
56
+ }
57
+
58
+ return {
59
+ ...sourceDocument,
60
+ phaseDependencies: {
61
+ ...sourceDocument.phaseDependencies,
62
+ gates: sourceDocument.phaseDependencies.gates ?? [],
63
+ },
64
+ } as HarnessConfigSourceDocument;
46
65
  }
47
66
 
48
67
  export function validateDocumentOrThrow(
@@ -23,6 +23,7 @@ import type { L4Config } from './value-objects/l4-config.js';
23
23
  export type LayerId = 'L1' | 'L2' | 'L3' | 'L4';
24
24
  export type PresetId = 'minimal' | 'standard' | 'strict';
25
25
  export type PlanningModeValue = 'interactive' | 'embedded-qa';
26
+ export type PhaseDependenciesPresetId = 'default' | 'full' | 'standard' | 'minimal' | 'custom';
26
27
  type DeepPartial<T> = {
27
28
  [K in keyof T]?: T[K] extends Array<infer TItem>
28
29
  ? Array<DeepPartial<TItem>>
@@ -76,7 +77,7 @@ export interface HarnessConfigResolvedDocument {
76
77
  relaxedGates: string[];
77
78
  };
78
79
  phaseDependencies: {
79
- preset: 'default' | 'custom';
80
+ preset: PhaseDependenciesPresetId;
80
81
  override: boolean;
81
82
  customRules: Array<{
82
83
  phase: string;
@@ -7,17 +7,20 @@
7
7
  */
8
8
  import { CustomPhaseRule } from './custom-phase-rule.js';
9
9
  import type { CustomPhaseRuleProps } from './custom-phase-rule.js';
10
+ import type { PhaseDependenciesPresetId } from '../harness-config.js';
10
11
 
11
12
  export interface PhaseDependenciesConfigProps {
12
- readonly preset: 'default' | 'custom';
13
+ readonly preset: PhaseDependenciesPresetId;
13
14
  readonly override: boolean;
14
15
  readonly customRules: readonly CustomPhaseRuleProps[];
16
+ readonly gates?: readonly unknown[];
15
17
  }
16
18
 
17
19
  export class PhaseDependenciesConfig {
18
- readonly preset: 'default' | 'custom';
20
+ readonly preset: PhaseDependenciesPresetId;
19
21
  readonly override: boolean;
20
22
  readonly customRules: readonly CustomPhaseRule[];
23
+ readonly gates: readonly unknown[];
21
24
 
22
25
  constructor(props: PhaseDependenciesConfigProps) {
23
26
  this.preset = props.preset;
@@ -25,6 +28,7 @@ export class PhaseDependenciesConfig {
25
28
  this.customRules = props.customRules.map(
26
29
  (rule) => new CustomPhaseRule(rule)
27
30
  );
31
+ this.gates = Object.freeze([...(props.gates ?? [])]);
28
32
  }
29
33
 
30
34
  static create(raw: PhaseDependenciesConfigProps): PhaseDependenciesConfig {
@@ -35,12 +39,22 @@ export class PhaseDependenciesConfig {
35
39
  return this.customRules.length > 0;
36
40
  }
37
41
 
42
+ hasCustomGates(): boolean {
43
+ return this.gates.length > 0;
44
+ }
45
+
38
46
  equals(other: PhaseDependenciesConfig): boolean {
39
47
  if (this.preset !== other.preset) return false;
40
48
  if (this.override !== other.override) return false;
41
49
  if (this.customRules.length !== other.customRules.length) return false;
42
- return this.customRules.every((rule, idx) =>
50
+ if (
51
+ !this.customRules.every((rule, idx) =>
43
52
  rule.equals(other.customRules[idx])
44
- );
53
+ )
54
+ ) {
55
+ return false;
56
+ }
57
+
58
+ return JSON.stringify(this.gates) === JSON.stringify(other.gates);
45
59
  }
46
60
  }