phasegate 0.116.0 → 0.117.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.117.0] - 2026-05-07
11
+
12
+ ### Fixed
13
+
14
+ - **WI-085: phase-gate validator (L2-001) が `paths.designDocs` / `paths.inceptionDocs` 設定を尊重しない問題を修正** — 外部レポーター nakataj-mti が GitHub Issue [#1](https://github.com/junpei-9898/phasegate/issues/1) で報告。`paths.designDocs` を `mydocs/...` などのデフォルト外パスに変更しても、L2-001 が要求する成果物パスは `docs/inception/...` / `docs/product/...` のままになる挙動を修正した。`docs/guide/configuration.md` の「`paths` を変えれば全 validator / hook が解決する」記述と実装の齟齬を解消。
15
+ - **新プレースホルダ**: `Artifact.path` で `{designDocsRoot}` / `{inceptionDocsRoot}` を許可。`Artifact.resolve(scope, pathRoots?)` が `paths.designDocs` / `paths.inceptionDocs` の値(未指定時はデフォルト `docs/product/construction` / `docs/inception`)でこれらを展開するよう拡張([ADR-016](docs/ADR/ADR-016-paths-config-placeholder.md))。
16
+ - **`docs/` 接頭辞バリデーション撤廃**: `Artifact.create()` の `path` が `docs/` 始まりでなければ `InvalidArtifactPathError` を投げる挙動を削除。プレースホルダ展開後の任意 root(`mydocs/...` 等)を許容するため。許可外プレースホルダの拒否は維持。
17
+ - **新ポートメソッド**: `PhaseConfigProviderPort.getPathRoots(): Promise<{ designDocsRoot, inceptionDocsRoot }>` を追加。`HarnessConfigPhaseConfigProvider` が `paths.designDocs` / `paths.inceptionDocs` 設定を読み(末尾スラッシュは trim)、未指定時はデフォルト値を返す。
18
+ - **流入経路**: `EvidenceBundleAssembler.assembleForLevel` 内で `getPathRoots()` を 1 回呼び、結果を `Artifact.resolve(scope, pathRoots)` および `ArtifactExistenceCheckerPort.checkAll(artifacts, scope, pathRoots)` に渡す。`PhaseInfoResolver.resolve(..., pathRoots?)` でブロッカー表示時にも root プレースホルダを実値に展開。
19
+ - **phase-nodes リテラル置換**: `STANDARD_PHASE_NODES` / `FULL_PHASE_NODES` / `MINIMAL_PHASE_NODES` の path リテラル `'docs/inception/...'` / `'docs/product/construction/...'` を `'{inceptionDocsRoot}/...'` / `'{designDocsRoot}/...'` に置換。
20
+ - **後方互換**: `paths` 未指定または既定値の場合、要求パスは v0.116.0 以前と完全同一。
21
+ - **スコープ外(Q1 (α) 採用)**: `docs/product/product_overview.md` / `docs/product/user_stories.md` / `docs/product/units/{unit}_unit.md` 等、`docs/product/` 直下の Level 1 product-wide 成果物はリテラル維持。`paths.designDocs`(= `docs/product/construction`)は construction subtree のみを管理する。
22
+ - **テスト追加**: ユニット 9 ケース(UT-PD-169〜177)と既存 5 ケースの意味反転 / シグネチャ拡張、IT 8 ケース(IT-PD-123〜130)を追加。`unit_test_design.md` / `it_test_design.md` / `coverage_report.md` 同期更新。
23
+ - **ドキュメント整合**: `docs/guide/configuration.md` §「Paths and the AIDLC Document Structure」に新プレースホルダ仕様と非 construction パスの取り扱いを明記。
24
+
10
25
  ## [0.116.0] - 2026-05-07
11
26
 
12
27
  ### Security
@@ -0,0 +1,155 @@
1
+ # ADR-016: phase-gate validator のパス解決を `paths` config プレースホルダ化する
2
+
3
+ ## Status
4
+
5
+ Accepted — 2026-05-07
6
+
7
+ ## Context
8
+
9
+ `phasegate.config.json` には `paths.designDocs` / `paths.inceptionDocs` という設定が存在し、`docs/guide/configuration.md:471` には次の記述がある:
10
+
11
+ > If you move your design documents to a non-default location, update `paths` accordingly so that all validators and hooks resolve files correctly.
12
+
13
+ しかし実装では、L2-001(phase-gate validator)の経路で `paths.*` 設定が反映されず、利用者が `paths.designDocs: "mydocs/product/construction"` 等を設定しても **要求される成果物パスは `docs/inception/...` / `docs/product/...` のまま** となる。外部レポーター(GitHub Issue [#1](https://github.com/junpei-9898/phasegate/issues/1), nakataj-mti, 2026-05-07)が報告し、phasegate チームによる再検証で報告内容が正確であることを確認した(WI-085)。
14
+
15
+ ### バグの実体(grep ベース、2026-05-07)
16
+
17
+ #### 1. ハードコードされたパスリテラル
18
+
19
+ `scripts/harness/phase-dependency-model/domain/definitions/standard-phase-nodes.ts` 全 18 か所の `path:` フィールドが `'docs/inception/...'` / `'docs/product/...'` 文字列リテラル。`full-phase-nodes.ts` / `minimal-phase-nodes.ts` も同様の構造。
20
+
21
+ #### 2. プレースホルダ展開の制約
22
+
23
+ `scripts/harness/phase-dependency-model/domain/values/artifact.ts:6`:
24
+
25
+ ```ts
26
+ const ALLOWED_PLACEHOLDERS = new Set(['unit', 'storyId']);
27
+ ```
28
+
29
+ `{designDocsRoot}` / `{inceptionDocsRoot}` のような **トップレベルパス展開は未実装**。
30
+
31
+ #### 3. `docs/` 接頭辞の強制バリデーション
32
+
33
+ `scripts/harness/phase-dependency-model/domain/values/artifact.ts:48`:
34
+
35
+ ```ts
36
+ if (normalizedPath.length === 0 || !normalizedPath.startsWith('docs/')) {
37
+ throw new InvalidArtifactPathError(args.path);
38
+ }
39
+ ```
40
+
41
+ 仮にユーザーが `phaseDependencies.preset: "custom"` + `gates[]` で迂回しようとしても、`mydocs/...` のような非 `docs/` 接頭辞は **Artifact 生成時点で拒否される**。
42
+
43
+ #### 4. `paths` config が `Artifact.resolve()` に到達していない
44
+
45
+ `scripts/harness/phase-dependency-model/application/services/evidence-bundle-assembler.ts:42-49` で `phaseConfigProvider` から `planningMode` のみ取得。`paths.designDocs` / `paths.inceptionDocs` は `Artifact.resolve(scope)` 呼び出しに渡されていない。`scope` 型も `{ unitId?: string; storyId?: string }` のみで、ルートパスを通す経路がない。
46
+
47
+ #### 5. 部分対応箇所との不整合
48
+
49
+ `paths.designDocs` を実際に参照している箇所は存在するが、いずれも phase-gate validator(L2-001)の経路ではない:
50
+
51
+ - `scripts/harness/harness-api/infrastructure/adapters/file-system-artifact-scanner-adapter.ts:118`(harness-api の scanner)
52
+ - `scripts/harness/agent-integration/infrastructure/adapters/phase-gate-query-adapter.ts:60`(agent runtime hook の `unitDir` 解決)
53
+
54
+ phase-gate validator 本体(`PhaseStructure` / `EvidenceBundleAssembler` / `Artifact`)には `paths.*` が一切流入していない。同一 config に対して validator 経路ごとに挙動が乖離している状態。
55
+
56
+ ### 検討した代替案
57
+
58
+ #### (A) `paths.designDocs` / `paths.inceptionDocs` でトップレベルパスを実際に置換する(**採用**)
59
+
60
+ phase-gate validator のパスリテラルを `{designDocsRoot}` / `{inceptionDocsRoot}` プレースホルダ化し、`paths` config の値で展開する。`Artifact` の `docs/` 接頭辞バリデーションはプレースホルダ展開後に実施するか撤廃する。
61
+
62
+ #### (B) ドキュメントを修正し「`paths.*` は storyReflection 専用」と明記する
63
+
64
+ `paths.designDocs` / `paths.inceptionDocs` は phase-gate ノードのパスには影響しないと documentation を改訂し、「ノードのパスを変えたい場合は `phaseDependencies.preset: "custom"` + `gates[]` で個別に上書きする」ことを推奨する。
65
+
66
+ ## Decision
67
+
68
+ **(A) を採用する。** phase-gate validator のパス解決にプレースホルダ展開機構を導入し、`paths.designDocs` / `paths.inceptionDocs` 設定を反映させる。
69
+
70
+ ### 採用理由
71
+
72
+ 1. **ドキュメントが利用者の合理的期待を形成済み** — `docs/guide/configuration.md:471` の記述により「`paths` を変えれば全 validator / hook が解決する」ことが約束されている。実装をドキュメントに合わせる方が修正の方向性として正しい
73
+ 2. **(B) は現状コードでは動作しない** — `Artifact.create()` に `docs/` 接頭辞バリデーション (artifact.ts:48) が存在するため、`gates[]` で `mydocs/...` を指定しても `InvalidArtifactPathError` で拒否される。(B) を採るには結局接頭辞バリデーションの緩和が必要で、(A) と実装コストの大半が重複する
74
+ 3. **UX 上の持続可能性** — 28 スキル × 多数の path フィールドを利用者が `gates[]` で個別に書き直す UX は持続不可能。デフォルトを変えるという軽量な操作で済む (A) の方が retrofit-adoption(既存 PJ への phasegate 導入)ユースケースに適合する
75
+ 4. **既存の部分対応箇所との整合** — `harness-api` / `agent-integration` 配下の adapter 群は既に `paths.designDocs` を読んでいる。phase-gate validator だけが取り残されている形であり、(A) は不整合を解消する
76
+
77
+ ### 新プレースホルダ仕様
78
+
79
+ | プレースホルダ | 解決元 | デフォルト | 既存挙動との関係 |
80
+ |----------------|--------|------------|---|
81
+ | `{designDocsRoot}` | `paths.designDocs` | `docs/product/construction` | 新設 |
82
+ | `{inceptionDocsRoot}` | `paths.inceptionDocs` | `docs/inception` | 新設 |
83
+ | `{unit}` | `scope.unitId` | (変更なし) | 既存 |
84
+ | `{storyId}` | `scope.storyId` | (変更なし) | 既存 |
85
+
86
+ ### `Artifact` バリデーションの取り扱い
87
+
88
+ `docs/` 接頭辞バリデーション (artifact.ts:48) は **撤廃する**。理由:
89
+
90
+ - `{designDocsRoot}` / `{inceptionDocsRoot}` のデフォルト値が `docs/...` なので、デフォルト挙動では引き続き `docs/` 配下に展開される
91
+ - 利用者が意図的に `mydocs/...` を選択した場合、それを拒否する正当性がない(`paths` 設定の存在意義そのもの)
92
+ - プレースホルダ展開後の文字列を再度 `docs/` 接頭辞で検査すると、`paths.*` の独自値が `Artifact` 生成時に未展開のまま検査される時系列問題が発生するため、**展開後の検査も行わない**
93
+ - 代替の防御として、`paths.designDocs` / `paths.inceptionDocs` 自体の妥当性は config-foundation の schema validation(既存)で担保する
94
+
95
+ ### `EvidenceBundleAssembler` の paths 流入経路
96
+
97
+ `PhaseConfigProviderPort` に新メソッド `getPathRoots()` を追加し、`paths.designDocs` / `paths.inceptionDocs` を取得する。`EvidenceBundleAssembler.assembleForLevel()` 内で paths を取得し、`scope` に追加するか別引数で `Artifact.resolve()` に渡す(具体形は logical-designer フェーズで確定)。
98
+
99
+ ### 後方互換戦略
100
+
101
+ - `paths` 未指定または既定値(`docs/product/construction` / `docs/inception`)の場合、要求パスは v0.115.0 以前と完全に同一
102
+ - 既存の `gates[]` でパスを上書きしているユーザーは、上書き値がそのまま使われる挙動を維持する(プレースホルダを含まないリテラルパスはそのまま resolve される)
103
+ - `Artifact` の `docs/` 接頭辞バリデーション撤廃は **緩和方向の変更**であり、既存の正常な config を破壊しない
104
+
105
+ ## Consequences
106
+
107
+ ### ポジティブ
108
+
109
+ - ドキュメント `configuration.md:471` の記述と実装が一致し、利用者の合理的期待が満たされる
110
+ - `paths.designDocs` / `paths.inceptionDocs` が validator 経路(L2-001 phase-gate, harness-api scanner, agent-integration hook)で一貫して解釈される
111
+ - retrofit-adoption(ISSUE-007)で既存 PJ がドキュメント配置規約を保ったまま phasegate を被せられるようになり、適用範囲が拡大
112
+ - 28 スキル × 多数の path フィールドに `gates[]` で介入させる UX 負担が解消される
113
+
114
+ ### ネガティブ / トレードオフ
115
+
116
+ - **ドメイン契約変更**: `Artifact.resolve()` のシグネチャが拡張されるため、phase-dependency-model の domain 層に破壊的変更が入る
117
+ - **緩和策**: 内部 API のみであり、phasegate-cli 利用者には影響しない。テストで後方互換を assert する
118
+ - **`docs/` 接頭辞バリデーション撤廃の緩み**: 任意の文字列パスを許容することで、typo 等の誤設定を `Artifact.create()` 段階で検出できなくなる
119
+ - **緩和策**: `paths.designDocs` / `paths.inceptionDocs` の妥当性は config-foundation の schema validation で検査済み。プレースホルダを含まないリテラル path(`gates[]` 経由)の typo は実行時の `artifactExistenceChecker` で「ファイルが存在しない」として検出される
120
+ - **新プレースホルダの命名選定**: `{designDocsRoot}` / `{inceptionDocsRoot}` は `paths` config キー名と微妙に異なる(`designDocs` vs `designDocsRoot`)
121
+ - **判断**: 既存プレースホルダ `{unit}` / `{storyId}` も config キー名そのままではないため整合的。`Root` suffix で「ルートディレクトリ」であることを明示する方が利用者にとって意味が明確
122
+
123
+ ### スコープ外(本 ADR で扱わない)
124
+
125
+ - L2 以外の validator(L1 / L3 / L4)への paths 設定の網羅的反映 — 既に各 validator が個別に `paths.*` を読んでいる箇所があるため、本 ADR は L2-001 phase-gate に閉じる
126
+ - `paths.designDocs` 直下の Unit ディレクトリ命名規約変更
127
+ - skill ファイル(`.claude/skills/`)内の path 参照の動的化
128
+ - `Artifact.path` のユーザー任意プレースホルダ拡張(`{customRoot}` 等)— 本 ADR で導入する 2 個のプレースホルダで現状のニーズは満たされる
129
+
130
+ ## Migration
131
+
132
+ 1. **ドメイン層**: `Artifact` の `ALLOWED_PLACEHOLDERS` に `designDocsRoot` / `inceptionDocsRoot` を追加し、`docs/` 接頭辞バリデーションを撤廃。`Artifact.resolve()` シグネチャに path roots を渡す経路を追加
133
+ 2. **ドメイン定義**: `STANDARD_PHASE_NODES` / `FULL_PHASE_NODES` / `MINIMAL_PHASE_NODES` の path リテラルを `{inceptionDocsRoot}/...` / `{designDocsRoot}/...` に書き換え
134
+ 3. **アプリケーション層**: `PhaseConfigProviderPort` に `getPathRoots()` を追加。`EvidenceBundleAssembler` 等で paths を取得して `Artifact.resolve()` に渡す
135
+ 4. **インフラ層**: `PhaseConfigProviderPort` 実装で `paths.designDocs` / `paths.inceptionDocs` を `getPathRoots()` から返すように接続
136
+ 5. **テスト**:
137
+ - default paths で従来通り `docs/inception/...` を要求すること
138
+ - `paths.inceptionDocs: "mydocs/inception"` で `mydocs/inception/...` を要求すること
139
+ - `Artifact` が `docs/` 以外で始まるパス(プレースホルダ展開後)を許容すること
140
+ 6. **ドキュメント**: `docs/guide/configuration.md` に新プレースホルダの動作を明記
141
+ 7. **CHANGELOG**: bug fix として記載(GitHub Issue [#1](https://github.com/junpei-9898/phasegate/issues/1) への参照付き)
142
+
143
+ 詳細な設計は WI-085 Phase 2(`logical-designer`)で `docs/product/construction/phase-dependency-model/logical_design.md` 等に記録する。
144
+
145
+ ## 関連
146
+
147
+ - **WI-085** — 本 ADR を駆動する Work Item。GitHub Issue [#1](https://github.com/junpei-9898/phasegate/issues/1) に対応
148
+ - **`scripts/harness/phase-dependency-model/domain/values/artifact.ts:6-80`** — プレースホルダ仕様 + `docs/` 接頭辞バリデーション、本 ADR の改修対象
149
+ - **`scripts/harness/phase-dependency-model/domain/definitions/standard-phase-nodes.ts:29-`** — ハードコード path(`full-phase-nodes.ts` / `minimal-phase-nodes.ts` も同様)
150
+ - **`scripts/harness/phase-dependency-model/application/services/evidence-bundle-assembler.ts:42-49`** — paths 流入欠落点、本 ADR の改修対象
151
+ - **`scripts/harness/phase-dependency-model/domain/ports/phase-config-provider-port.ts`** — `getPathRoots()` 追加対象
152
+ - **`scripts/harness/harness-api/infrastructure/adapters/file-system-artifact-scanner-adapter.ts:118`** — 既に paths を読んでいる先行箇所、整合確認用
153
+ - **`scripts/harness/agent-integration/infrastructure/adapters/phase-gate-query-adapter.ts:60`** — 同上
154
+ - **`docs/guide/configuration.md:445-471`** — 利用者の期待を形成しているドキュメント記述
155
+ - **ADR-007** — harness-config を Single Source of Truth とする決定。本 ADR は ADR-007 の精神(config から validator を駆動する)を phase-gate validator に拡張するもの
@@ -469,3 +469,26 @@ docs/
469
469
  ```
470
470
 
471
471
  If you move your design documents to a non-default location, update `paths` accordingly so that all validators and hooks resolve files correctly.
472
+
473
+ **How paths flow into the L2 phase-gate validator (since v0.117.0 / WI-085):**
474
+
475
+ `STANDARD_PHASE_NODES` / `FULL_PHASE_NODES` / `MINIMAL_PHASE_NODES` express artifact locations using two placeholders, expanded at validation time from `paths`:
476
+
477
+ | Placeholder | Resolved from | Default |
478
+ |---|---|---|
479
+ | `{designDocsRoot}` | `paths.designDocs` | `docs/product/construction` |
480
+ | `{inceptionDocsRoot}` | `paths.inceptionDocs` | `docs/inception` |
481
+
482
+ Setting `paths.designDocs` to `mydocs/product/construction` makes the L2 phase-gate require `mydocs/product/construction/{unit}/domain_model.md` instead of the default. Default values match the v0.115.0 layout for full backward compatibility.
483
+
484
+ **Out of scope (paths handled by literal references):**
485
+
486
+ A handful of Level 1 product-wide artifacts live at `docs/product/` directly (not under `construction/`) and remain literal:
487
+
488
+ - `docs/product/product_overview.md`
489
+ - `docs/product/user_stories.md`
490
+ - `docs/product/user_story_mapping.md`
491
+ - `docs/product/units/{unit}_unit.md`
492
+ - `docs/product/units/integration_contract.md`
493
+
494
+ These are not controlled by `paths.designDocs` (which targets the *construction* subtree). If your project moves the entire `docs/product/` tree, override these via `phaseDependencies.preset: "custom"` + `gates[]`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phasegate",
3
- "version": "0.116.0",
3
+ "version": "0.117.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": "MIT",
@@ -40,5 +40,6 @@ export function toPhaseConfigSection(resolvedConfig: HarnessConfigV2) {
40
40
  gates: phaseDependencies.gates ?? [],
41
41
  },
42
42
  reportingOutputDir: resolvedConfig.reporting.outputDir,
43
+ paths: resolvedConfig.paths,
43
44
  };
44
45
  }
@@ -6,6 +6,7 @@
6
6
  import type { ArtifactExistenceCheckerPort } from '../../domain/ports/artifact-existence-checker-port.js';
7
7
  import type { PhaseConfigProviderPort } from '../../domain/ports/phase-config-provider-port.js';
8
8
  import type { PlanDocumentReaderPort } from '../../domain/ports/plan-document-reader-port.js';
9
+ import type { PathRoots } from '../../domain/values/artifact.js';
9
10
  import { PhaseLevel } from '../../domain/values/phase-level.js';
10
11
  import type { PhaseNode } from '../../domain/values/phase-node.js';
11
12
  import type { PlanEvidence } from '../../domain/values/plan-evidence.js';
@@ -21,6 +22,7 @@ export interface EvidenceBundle {
21
22
  readonly artifactStatuses: ReadonlyMap<string, boolean>;
22
23
  readonly planEvidences: ReadonlyMap<string, PlanEvidence>;
23
24
  readonly planningMode: PlanningMode;
25
+ readonly pathRoots: PathRoots;
24
26
  }
25
27
 
26
28
  export class EvidenceBundleAssembler {
@@ -40,13 +42,21 @@ export class EvidenceBundleAssembler {
40
42
  scope: { unitId?: string; storyId?: string },
41
43
  ): Promise<EvidenceBundle> {
42
44
  const planningMode = await this.phaseConfigProvider.getPlanningMode(scope);
45
+ const pathRoots = await this.phaseConfigProvider.getPathRoots();
43
46
  const artifacts = nodes.flatMap((node) => node.artifacts);
44
- const resolvedStatuses = await this.artifactExistenceChecker.checkAll(artifacts, scope);
47
+ const resolvedStatuses = await this.artifactExistenceChecker.checkAll(
48
+ artifacts,
49
+ scope,
50
+ pathRoots,
51
+ );
45
52
  const artifactStatuses = new Map<string, boolean>();
46
53
 
47
54
  for (const artifact of artifacts) {
48
55
  try {
49
- artifactStatuses.set(artifact.path, resolvedStatuses.get(artifact.resolve(scope)) ?? false);
56
+ artifactStatuses.set(
57
+ artifact.path,
58
+ resolvedStatuses.get(artifact.resolve(scope, pathRoots)) ?? false,
59
+ );
50
60
  } catch {
51
61
  artifactStatuses.set(artifact.path, false);
52
62
  }
@@ -66,6 +76,7 @@ export class EvidenceBundleAssembler {
66
76
  artifactStatuses,
67
77
  planEvidences,
68
78
  planningMode,
79
+ pathRoots,
69
80
  };
70
81
  }
71
82
  }
@@ -7,6 +7,7 @@ import type { PhaseInfoDto } from '../dto/phase-info-dto.js';
7
7
  import type { PhaseDependency } from '../../domain/values/phase-dependency.js';
8
8
  import type { PhaseNode } from '../../domain/values/phase-node.js';
9
9
  import type { PlanEvidence } from '../../domain/values/plan-evidence.js';
10
+ import { DEFAULT_PATH_ROOTS, type PathRoots } from '../../domain/values/artifact.js';
10
11
 
11
12
  export interface ResolvedPhaseInfo
12
13
  extends Omit<PhaseInfoDto, 'unitId' | 'storyId'> {}
@@ -41,6 +42,7 @@ export class PhaseInfoResolver {
41
42
  },
42
43
  artifactStatuses: ReadonlyMap<string, boolean>,
43
44
  planEvidences: ReadonlyMap<string, PlanEvidence>,
45
+ pathRoots: PathRoots = DEFAULT_PATH_ROOTS,
44
46
  ): ResolvedPhaseInfo {
45
47
  const incomingDependencies = collectIncomingDependencies(graph.dependencies);
46
48
  const completedNodeKeys = new Set<string>();
@@ -108,7 +110,7 @@ export class PhaseInfoResolver {
108
110
 
109
111
  for (const artifact of node.requiredArtifacts()) {
110
112
  if (artifactStatuses.get(artifact.path) !== true) {
111
- blockers.push(`成果物が不足しています: ${artifact.path}`);
113
+ blockers.push(`成果物が不足しています: ${artifact.expandRoots(pathRoots)}`);
112
114
  }
113
115
  }
114
116
 
@@ -48,6 +48,7 @@ export class GetPhaseInfoUseCase {
48
48
  graph,
49
49
  evidenceBundle.artifactStatuses,
50
50
  evidenceBundle.planEvidences,
51
+ evidenceBundle.pathRoots,
51
52
  );
52
53
 
53
54
  return Object.freeze({
@@ -26,7 +26,7 @@ export const FULL_PHASE_NODES: readonly PhaseNode[] = Object.freeze([
26
26
  createNode(1, 'product-architect', [
27
27
  {
28
28
  name: 'product-overview-plan',
29
- path: 'docs/inception/_shared/product_overview_plan.md',
29
+ path: '{inceptionDocsRoot}/_shared/product_overview_plan.md',
30
30
  required: true,
31
31
  },
32
32
  {
@@ -38,7 +38,7 @@ export const FULL_PHASE_NODES: readonly PhaseNode[] = Object.freeze([
38
38
  createNode(1, 'story-writer', [
39
39
  {
40
40
  name: 'story-writer-plan',
41
- path: 'docs/inception/_shared/story_writer_plan.md',
41
+ path: '{inceptionDocsRoot}/_shared/story_writer_plan.md',
42
42
  required: true,
43
43
  },
44
44
  {
@@ -50,7 +50,7 @@ export const FULL_PHASE_NODES: readonly PhaseNode[] = Object.freeze([
50
50
  createNode(1, 'story-mapper', [
51
51
  {
52
52
  name: 'story-mapping-plan',
53
- path: 'docs/inception/_shared/story_mapping_plan.md',
53
+ path: '{inceptionDocsRoot}/_shared/story_mapping_plan.md',
54
54
  required: true,
55
55
  },
56
56
  {
@@ -62,7 +62,7 @@ export const FULL_PHASE_NODES: readonly PhaseNode[] = Object.freeze([
62
62
  createNode(1, 'unit-designer', [
63
63
  {
64
64
  name: 'unit-design-plan',
65
- path: 'docs/inception/_shared/unit_design_plan.md',
65
+ path: '{inceptionDocsRoot}/_shared/unit_design_plan.md',
66
66
  required: true,
67
67
  },
68
68
  {
@@ -79,108 +79,108 @@ export const FULL_PHASE_NODES: readonly PhaseNode[] = Object.freeze([
79
79
  createNode(2, 'domain-designer', [
80
80
  {
81
81
  name: 'domain-model-plan',
82
- path: 'docs/inception/{unit}/domain_model_plan.md',
82
+ path: '{inceptionDocsRoot}/{unit}/domain_model_plan.md',
83
83
  required: true,
84
84
  },
85
85
  {
86
86
  name: 'domain-model',
87
- path: 'docs/product/construction/{unit}/domain_model.md',
87
+ path: '{designDocsRoot}/{unit}/domain_model.md',
88
88
  required: true,
89
89
  },
90
90
  ]),
91
91
  createNode(2, 'logical-designer', [
92
92
  {
93
93
  name: 'logical-design-plan',
94
- path: 'docs/inception/{unit}/logical_design_plan.md',
94
+ path: '{inceptionDocsRoot}/{unit}/logical_design_plan.md',
95
95
  required: true,
96
96
  },
97
97
  {
98
98
  name: 'logical-design',
99
- path: 'docs/product/construction/{unit}/logical_design.md',
99
+ path: '{designDocsRoot}/{unit}/logical_design.md',
100
100
  required: true,
101
101
  },
102
102
  ]),
103
103
  createNode(2, 'it-test-designer', [
104
104
  {
105
105
  name: 'it-test-design-plan',
106
- path: 'docs/inception/{unit}/it_test_design_plan.md',
106
+ path: '{inceptionDocsRoot}/{unit}/it_test_design_plan.md',
107
107
  required: true,
108
108
  },
109
109
  {
110
110
  name: 'it-test-design',
111
- path: 'docs/product/construction/{unit}/it_test_design.md',
111
+ path: '{designDocsRoot}/{unit}/it_test_design.md',
112
112
  required: true,
113
113
  },
114
114
  ]),
115
115
  createNode(2, 'unit-test-designer', [
116
116
  {
117
117
  name: 'unit-test-design-plan',
118
- path: 'docs/inception/{unit}/unit_test_design_plan.md',
118
+ path: '{inceptionDocsRoot}/{unit}/unit_test_design_plan.md',
119
119
  required: true,
120
120
  },
121
121
  {
122
122
  name: 'unit-test-design',
123
- path: 'docs/product/construction/{unit}/unit_test_design.md',
123
+ path: '{designDocsRoot}/{unit}/unit_test_design.md',
124
124
  required: true,
125
125
  },
126
126
  ]),
127
127
  createNode(2, 'it-test-logic-designer', [
128
128
  {
129
129
  name: 'it-test-logic-plan',
130
- path: 'docs/inception/{unit}/it_test_logic_plan.md',
130
+ path: '{inceptionDocsRoot}/{unit}/it_test_logic_plan.md',
131
131
  required: true,
132
132
  },
133
133
  {
134
134
  name: 'it-test-logic',
135
- path: 'docs/product/construction/{unit}/it_test_logic.md',
135
+ path: '{designDocsRoot}/{unit}/it_test_logic.md',
136
136
  required: true,
137
137
  },
138
138
  ]),
139
139
  createNode(2, 'unit-test-logic-designer', [
140
140
  {
141
141
  name: 'unit-test-logic-plan',
142
- path: 'docs/inception/{unit}/unit_test_logic_plan.md',
142
+ path: '{inceptionDocsRoot}/{unit}/unit_test_logic_plan.md',
143
143
  required: true,
144
144
  },
145
145
  {
146
146
  name: 'unit-test-logic',
147
- path: 'docs/product/construction/{unit}/unit_test_logic.md',
147
+ path: '{designDocsRoot}/{unit}/unit_test_logic.md',
148
148
  required: true,
149
149
  },
150
150
  ]),
151
151
  createNode(3, 'logical-designer', [
152
152
  {
153
153
  name: 'story-logical-design-plan',
154
- path: 'docs/inception/{unit}/{storyId}/logical_design_plan.md',
154
+ path: '{inceptionDocsRoot}/{unit}/{storyId}/logical_design_plan.md',
155
155
  required: false,
156
156
  },
157
157
  {
158
158
  name: 'story-logical-design',
159
- path: 'docs/inception/{unit}/{storyId}/logical_design.md',
159
+ path: '{inceptionDocsRoot}/{unit}/{storyId}/logical_design.md',
160
160
  required: false,
161
161
  },
162
162
  ]),
163
163
  createNode(3, 'scenario-test-designer', [
164
164
  {
165
165
  name: 'scenario-test-plan',
166
- path: 'docs/inception/{unit}/{storyId}/scenario_test_plan.md',
166
+ path: '{inceptionDocsRoot}/{unit}/{storyId}/scenario_test_plan.md',
167
167
  required: false,
168
168
  },
169
169
  {
170
170
  name: 'scenario-test-design',
171
- path: 'docs/inception/{unit}/{storyId}/scenario_test_design.md',
171
+ path: '{inceptionDocsRoot}/{unit}/{storyId}/scenario_test_design.md',
172
172
  required: false,
173
173
  },
174
174
  ]),
175
175
  createNode(3, 'scenario-test-logic-designer', [
176
176
  {
177
177
  name: 'scenario-test-logic-plan',
178
- path: 'docs/inception/{unit}/{storyId}/scenario_test_logic_plan.md',
178
+ path: '{inceptionDocsRoot}/{unit}/{storyId}/scenario_test_logic_plan.md',
179
179
  required: false,
180
180
  },
181
181
  {
182
182
  name: 'scenario-test-logic',
183
- path: 'docs/inception/{unit}/{storyId}/scenario_test_logic.md',
183
+ path: '{inceptionDocsRoot}/{unit}/{storyId}/scenario_test_logic.md',
184
184
  required: false,
185
185
  },
186
186
  ]),
@@ -188,7 +188,7 @@ export const FULL_PHASE_NODES: readonly PhaseNode[] = Object.freeze([
188
188
  createNode(3, 'story-implementor', [
189
189
  {
190
190
  name: 'tdd-implementation-plan',
191
- path: 'docs/inception/{unit}/{storyId}/tdd_implementation_plan.md',
191
+ path: '{inceptionDocsRoot}/{unit}/{storyId}/tdd_implementation_plan.md',
192
192
  required: false,
193
193
  },
194
194
  ]),
@@ -26,7 +26,7 @@ export const MINIMAL_PHASE_NODES: readonly PhaseNode[] = Object.freeze([
26
26
  createNode(1, 'product-architect', [
27
27
  {
28
28
  name: 'product-overview-plan',
29
- path: 'docs/inception/_shared/product_overview_plan.md',
29
+ path: '{inceptionDocsRoot}/_shared/product_overview_plan.md',
30
30
  required: true,
31
31
  },
32
32
  {
@@ -38,12 +38,12 @@ export const MINIMAL_PHASE_NODES: readonly PhaseNode[] = Object.freeze([
38
38
  createNode(2, 'logical-designer', [
39
39
  {
40
40
  name: 'logical-design-plan',
41
- path: 'docs/inception/{unit}/logical_design_plan.md',
41
+ path: '{inceptionDocsRoot}/{unit}/logical_design_plan.md',
42
42
  required: true,
43
43
  },
44
44
  {
45
45
  name: 'logical-design',
46
- path: 'docs/product/construction/{unit}/logical_design.md',
46
+ path: '{designDocsRoot}/{unit}/logical_design.md',
47
47
  required: true,
48
48
  },
49
49
  ]),
@@ -26,7 +26,7 @@ export const STANDARD_PHASE_NODES: readonly PhaseNode[] = Object.freeze([
26
26
  createNode(1, 'product-architect', [
27
27
  {
28
28
  name: 'product-overview-plan',
29
- path: 'docs/inception/_shared/product_overview_plan.md',
29
+ path: '{inceptionDocsRoot}/_shared/product_overview_plan.md',
30
30
  required: true,
31
31
  },
32
32
  {
@@ -38,7 +38,7 @@ export const STANDARD_PHASE_NODES: readonly PhaseNode[] = Object.freeze([
38
38
  createNode(1, 'story-writer', [
39
39
  {
40
40
  name: 'story-writer-plan',
41
- path: 'docs/inception/_shared/story_writer_plan.md',
41
+ path: '{inceptionDocsRoot}/_shared/story_writer_plan.md',
42
42
  required: true,
43
43
  },
44
44
  {
@@ -50,60 +50,60 @@ export const STANDARD_PHASE_NODES: readonly PhaseNode[] = Object.freeze([
50
50
  createNode(2, 'domain-designer', [
51
51
  {
52
52
  name: 'domain-model-plan',
53
- path: 'docs/inception/{unit}/domain_model_plan.md',
53
+ path: '{inceptionDocsRoot}/{unit}/domain_model_plan.md',
54
54
  required: true,
55
55
  },
56
56
  {
57
57
  name: 'domain-model',
58
- path: 'docs/product/construction/{unit}/domain_model.md',
58
+ path: '{designDocsRoot}/{unit}/domain_model.md',
59
59
  required: true,
60
60
  },
61
61
  ]),
62
62
  createNode(2, 'logical-designer', [
63
63
  {
64
64
  name: 'logical-design-plan',
65
- path: 'docs/inception/{unit}/logical_design_plan.md',
65
+ path: '{inceptionDocsRoot}/{unit}/logical_design_plan.md',
66
66
  required: true,
67
67
  },
68
68
  {
69
69
  name: 'logical-design',
70
- path: 'docs/product/construction/{unit}/logical_design.md',
70
+ path: '{designDocsRoot}/{unit}/logical_design.md',
71
71
  required: true,
72
72
  },
73
73
  ]),
74
74
  createNode(3, 'logical-designer', [
75
75
  {
76
76
  name: 'story-logical-design-plan',
77
- path: 'docs/inception/{unit}/{storyId}/logical_design_plan.md',
77
+ path: '{inceptionDocsRoot}/{unit}/{storyId}/logical_design_plan.md',
78
78
  required: false,
79
79
  },
80
80
  {
81
81
  name: 'story-logical-design',
82
- path: 'docs/inception/{unit}/{storyId}/logical_design.md',
82
+ path: '{inceptionDocsRoot}/{unit}/{storyId}/logical_design.md',
83
83
  required: false,
84
84
  },
85
85
  ]),
86
86
  createNode(3, 'scenario-test-designer', [
87
87
  {
88
88
  name: 'scenario-test-plan',
89
- path: 'docs/inception/{unit}/{storyId}/scenario_test_plan.md',
89
+ path: '{inceptionDocsRoot}/{unit}/{storyId}/scenario_test_plan.md',
90
90
  required: false,
91
91
  },
92
92
  {
93
93
  name: 'scenario-test-design',
94
- path: 'docs/inception/{unit}/{storyId}/scenario_test_design.md',
94
+ path: '{inceptionDocsRoot}/{unit}/{storyId}/scenario_test_design.md',
95
95
  required: false,
96
96
  },
97
97
  ]),
98
98
  createNode(3, 'scenario-test-logic-designer', [
99
99
  {
100
100
  name: 'scenario-test-logic-plan',
101
- path: 'docs/inception/{unit}/{storyId}/scenario_test_logic_plan.md',
101
+ path: '{inceptionDocsRoot}/{unit}/{storyId}/scenario_test_logic_plan.md',
102
102
  required: false,
103
103
  },
104
104
  {
105
105
  name: 'scenario-test-logic',
106
- path: 'docs/inception/{unit}/{storyId}/scenario_test_logic.md',
106
+ path: '{inceptionDocsRoot}/{unit}/{storyId}/scenario_test_logic.md',
107
107
  required: false,
108
108
  },
109
109
  ]),
@@ -111,7 +111,7 @@ export const STANDARD_PHASE_NODES: readonly PhaseNode[] = Object.freeze([
111
111
  createNode(3, 'story-implementor', [
112
112
  {
113
113
  name: 'tdd-implementation-plan',
114
- path: 'docs/inception/{unit}/{storyId}/tdd_implementation_plan.md',
114
+ path: '{inceptionDocsRoot}/{unit}/{storyId}/tdd_implementation_plan.md',
115
115
  required: false,
116
116
  },
117
117
  ]),
@@ -10,7 +10,7 @@ import { MINIMAL_PHASE_NODES } from '../definitions/minimal-phase-nodes.js';
10
10
  import { STANDARD_PHASE_DEPENDENCIES } from '../definitions/standard-phase-dependencies.js';
11
11
  import { STANDARD_PHASE_NODES } from '../definitions/standard-phase-nodes.js';
12
12
  import { GateGraph } from '../services/gate-graph.js';
13
- import { Artifact } from '../values/artifact.js';
13
+ import { Artifact, DEFAULT_PATH_ROOTS, type PathRoots } from '../values/artifact.js';
14
14
  import { CustomRule, InvalidCustomRuleError } from '../values/custom-rule.js';
15
15
  import { GateDefinition } from '../values/gate-definition.js';
16
16
  import { PhaseCustomizationPolicy } from '../values/phase-customization-policy.js';
@@ -76,13 +76,14 @@ const buildNodeIndex = (nodes: readonly PhaseNode[]): ReadonlyMap<string, PhaseN
76
76
  const collectMissingArtifactBlockers = (
77
77
  nodes: readonly PhaseNode[],
78
78
  artifactStatuses: ReadonlyMap<string, boolean>,
79
+ pathRoots: PathRoots,
79
80
  ): readonly string[] => {
80
81
  const blockers: string[] = [];
81
82
 
82
83
  for (const node of nodes) {
83
84
  for (const artifact of node.requiredArtifacts()) {
84
85
  if (!artifactStatuses.get(artifact.path)) {
85
- blockers.push(`成果物が不足しています: ${artifact.path}`);
86
+ blockers.push(`成果物が不足しています: ${artifact.expandRoots(pathRoots)}`);
86
87
  }
87
88
  }
88
89
  }
@@ -220,6 +221,7 @@ export class PhaseStructure {
220
221
  artifactStatuses: ReadonlyMap<string, boolean>;
221
222
  planEvidences: ReadonlyMap<string, PlanEvidence>;
222
223
  planningMode: PlanningMode;
224
+ pathRoots?: PathRoots;
223
225
  },
224
226
  scope?: {
225
227
  unitId?: string;
@@ -237,13 +239,14 @@ export class PhaseStructure {
237
239
  });
238
240
  }
239
241
 
242
+ const pathRoots = evidence.pathRoots ?? DEFAULT_PATH_ROOTS;
240
243
  const prerequisiteNodes = [1, 2, 3]
241
244
  .map((value) => PhaseLevel.create(value))
242
245
  .filter((level) => level.isPrerequisiteOf(targetLevel))
243
246
  .flatMap((level) => this.getPhaseNodes(level));
244
247
 
245
248
  const blockers = [
246
- ...collectMissingArtifactBlockers(prerequisiteNodes, evidence.artifactStatuses),
249
+ ...collectMissingArtifactBlockers(prerequisiteNodes, evidence.artifactStatuses, pathRoots),
247
250
  ];
248
251
  const completedNodeKeys = new Set<string>();
249
252
  const warnings: string[] = [];
@@ -289,7 +292,7 @@ export class PhaseStructure {
289
292
  let nodeComplete = true;
290
293
  for (const artifact of node.artifacts) {
291
294
  // Evidence map may be keyed by unresolved path (artifact.path) or resolved path
292
- const resolvedPath = artifact.resolve(scope);
295
+ const resolvedPath = artifact.resolve(scope, pathRoots);
293
296
  const exists = evidence.artifactStatuses.get(artifact.path)
294
297
  ?? evidence.artifactStatuses.get(resolvedPath)
295
298
  ?? false;
@@ -3,11 +3,12 @@
3
3
  * @unit phase-dependency-model
4
4
  */
5
5
 
6
- import type { Artifact } from '../values/artifact.js';
6
+ import type { Artifact, PathRoots } from '../values/artifact.js';
7
7
 
8
8
  export interface ArtifactExistenceCheckerPort {
9
9
  checkAll(
10
10
  artifacts: readonly Artifact[],
11
11
  scope: { unitId?: string; storyId?: string },
12
+ pathRoots?: PathRoots,
12
13
  ): Promise<ReadonlyMap<string, boolean>>;
13
14
  }
@@ -6,10 +6,12 @@
6
6
  import type { PhaseCustomizationPolicy } from '../values/phase-customization-policy.js';
7
7
  import type { PlanningMode } from '../values/planning-mode.js';
8
8
  import type { StoryReflectionConfig } from '../values/story-reflection-config.js';
9
+ import type { PathRoots } from '../values/artifact.js';
9
10
 
10
11
  export interface PhaseConfigProviderPort {
11
12
  getPlanningMode(scope: { unitId?: string; storyId?: string }): Promise<PlanningMode>;
12
13
  getCustomizationPolicy(): Promise<PhaseCustomizationPolicy>;
13
14
  getReportingOutputDir(): Promise<string>;
14
15
  getStoryReflectionConfig(): Promise<StoryReflectionConfig>;
16
+ getPathRoots(): Promise<PathRoots>;
15
17
  }
@@ -3,10 +3,25 @@
3
3
  * @unit phase-dependency-model
4
4
  */
5
5
 
6
- const ALLOWED_PLACEHOLDERS = new Set(['unit', 'storyId']);
6
+ const ALLOWED_PLACEHOLDERS = new Set([
7
+ 'unit',
8
+ 'storyId',
9
+ 'designDocsRoot',
10
+ 'inceptionDocsRoot',
11
+ ]);
7
12
  const PLACEHOLDER_PATTERN = /\{([^}]+)\}/g;
8
13
  const UNRESOLVED_PLACEHOLDER_PATTERN = /\{[^}]+\}/;
9
14
 
15
+ export interface PathRoots {
16
+ readonly designDocsRoot: string;
17
+ readonly inceptionDocsRoot: string;
18
+ }
19
+
20
+ export const DEFAULT_PATH_ROOTS: PathRoots = Object.freeze({
21
+ designDocsRoot: 'docs/product/construction',
22
+ inceptionDocsRoot: 'docs/inception',
23
+ });
24
+
10
25
  export interface ArtifactCreateArgs {
11
26
  readonly name: string;
12
27
  readonly path: string;
@@ -45,7 +60,7 @@ export class Artifact {
45
60
  static create(args: ArtifactCreateArgs): Artifact {
46
61
  const normalizedPath = args.path.trim();
47
62
 
48
- if (normalizedPath.length === 0 || !normalizedPath.startsWith('docs/')) {
63
+ if (normalizedPath.length === 0) {
49
64
  throw new InvalidArtifactPathError(args.path);
50
65
  }
51
66
 
@@ -66,11 +81,25 @@ export class Artifact {
66
81
  return this.path.endsWith('_plan.md');
67
82
  }
68
83
 
69
- resolve(scope: { unitId?: string; storyId?: string }): string {
84
+ resolve(
85
+ scope: { unitId?: string; storyId?: string },
86
+ pathRoots: PathRoots = DEFAULT_PATH_ROOTS,
87
+ ): string {
70
88
  let resolvedPath = this.path;
71
89
 
90
+ resolvedPath = resolvedPath.replaceAll(
91
+ '{designDocsRoot}',
92
+ pathRoots.designDocsRoot,
93
+ );
94
+ resolvedPath = resolvedPath.replaceAll(
95
+ '{inceptionDocsRoot}',
96
+ pathRoots.inceptionDocsRoot,
97
+ );
72
98
  resolvedPath = resolvedPath.replaceAll('{unit}', scope.unitId ?? '{unit}');
73
- resolvedPath = resolvedPath.replaceAll('{storyId}', scope.storyId ?? '{storyId}');
99
+ resolvedPath = resolvedPath.replaceAll(
100
+ '{storyId}',
101
+ scope.storyId ?? '{storyId}',
102
+ );
74
103
 
75
104
  if (this.required && UNRESOLVED_PLACEHOLDER_PATTERN.test(resolvedPath)) {
76
105
  throw new InvalidArtifactPathError(this.path);
@@ -79,6 +108,12 @@ export class Artifact {
79
108
  return resolvedPath;
80
109
  }
81
110
 
111
+ expandRoots(pathRoots: PathRoots = DEFAULT_PATH_ROOTS): string {
112
+ return this.path
113
+ .replaceAll('{designDocsRoot}', pathRoots.designDocsRoot)
114
+ .replaceAll('{inceptionDocsRoot}', pathRoots.inceptionDocsRoot);
115
+ }
116
+
82
117
  equals(other: Artifact): boolean {
83
118
  return (
84
119
  this.name === other.name &&
@@ -12,6 +12,7 @@ import {
12
12
  import { CustomRule } from '../../domain/values/custom-rule.js';
13
13
  import { StoryReflectionConfig } from '../../domain/values/story-reflection-config.js';
14
14
  import { StoryReflectionMapping } from '../../domain/values/story-reflection-mapping.js';
15
+ import { DEFAULT_PATH_ROOTS, type PathRoots } from '../../domain/values/artifact.js';
15
16
  import { FULL_STORY_REFLECTION_DEFAULTS } from '../../domain/definitions/full-story-reflection-defaults.js';
16
17
  import { STANDARD_STORY_REFLECTION_DEFAULTS } from '../../domain/definitions/standard-story-reflection-defaults.js';
17
18
  import { MINIMAL_STORY_REFLECTION_DEFAULTS } from '../../domain/definitions/minimal-story-reflection-defaults.js';
@@ -54,8 +55,14 @@ export interface PhaseConfigSection {
54
55
  };
55
56
  readonly storyReflection?: StoryReflectionSectionInput;
56
57
  readonly reportingOutputDir?: string;
58
+ readonly paths?: {
59
+ readonly designDocs?: string;
60
+ readonly inceptionDocs?: string;
61
+ };
57
62
  }
58
63
 
64
+ const trimTrailingSlash = (value: string): string => value.replace(/\/+$/, '');
65
+
59
66
  /**
60
67
  * プリセットごとの storyReflection デフォルト定義。
61
68
  * `default` は v1.0 で `full` に統合されたため同一値にエイリアスする。
@@ -135,6 +142,17 @@ export class HarnessConfigPhaseConfigProvider
135
142
  return this.config.reportingOutputDir ?? this.defaultOutputDir;
136
143
  }
137
144
 
145
+ async getPathRoots(): Promise<PathRoots> {
146
+ const paths = this.config.paths;
147
+ const designDocsRoot = paths?.designDocs
148
+ ? trimTrailingSlash(paths.designDocs)
149
+ : DEFAULT_PATH_ROOTS.designDocsRoot;
150
+ const inceptionDocsRoot = paths?.inceptionDocs
151
+ ? trimTrailingSlash(paths.inceptionDocs)
152
+ : DEFAULT_PATH_ROOTS.inceptionDocsRoot;
153
+ return { designDocsRoot, inceptionDocsRoot };
154
+ }
155
+
138
156
  async getStoryReflectionConfig(): Promise<StoryReflectionConfig> {
139
157
  const policy = await this.getCustomizationPolicy();
140
158
  const presetDefault = PRESET_STORY_REFLECTION_DEFAULTS[policy.preset];
@@ -6,7 +6,7 @@
6
6
  import { access } from 'node:fs/promises';
7
7
  import * as path from 'node:path';
8
8
  import type { ArtifactExistenceCheckerPort } from '../../domain/ports/artifact-existence-checker-port.js';
9
- import type { Artifact } from '../../domain/values/artifact.js';
9
+ import type { Artifact, PathRoots } from '../../domain/values/artifact.js';
10
10
 
11
11
  export interface FileSystemArtifactExistenceCheckerDeps {
12
12
  readonly rootDir: string;
@@ -24,13 +24,14 @@ export class FileSystemArtifactExistenceChecker
24
24
  async checkAll(
25
25
  artifacts: readonly Artifact[],
26
26
  scope: { unitId?: string; storyId?: string },
27
+ pathRoots?: PathRoots,
27
28
  ): Promise<ReadonlyMap<string, boolean>> {
28
29
  const results = new Map<string, boolean>();
29
30
 
30
31
  for (const artifact of artifacts) {
31
32
  let resolvedPath: string;
32
33
  try {
33
- resolvedPath = artifact.resolve(scope);
34
+ resolvedPath = artifact.resolve(scope, pathRoots);
34
35
  } catch {
35
36
  results.set(artifact.path, false);
36
37
  continue;