phasegate 0.315.0 → 0.335.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 (56) hide show
  1. package/docs/ADR/039-hook-observable-state-as-authorization-unit.md +74 -0
  2. package/docs/ADR/040-quick-mode-config-via-preset-resolution.md +66 -0
  3. package/docs/guide/cli-reference.md +89 -7
  4. package/docs/guide/configuration.md +43 -2
  5. package/docs/guide/quick-vs-full-mode.md +36 -1
  6. package/package.json +2 -2
  7. package/scripts/harness/agent-integration/application/dto/handle-pre-tool-use-dto.ts +5 -1
  8. package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +16 -8
  9. package/scripts/harness/agent-integration/domain/services/bash-write-target-extractor.ts +190 -2
  10. package/scripts/harness/agent-integration/domain/value-objects/protected-file-list.ts +5 -0
  11. package/scripts/harness/agent-integration/infrastructure/adapters/quick-mode-full-mode-requirement-adapter.ts +15 -1
  12. package/scripts/harness/agent-integration/presentation/pre-tool-use-hook.ts +6 -3
  13. package/scripts/harness/ci-governance/application/dto/scaffold-inception-input.ts +9 -0
  14. package/scripts/harness/ci-governance/application/dto/scaffold-inception-output.ts +13 -0
  15. package/scripts/harness/ci-governance/application/usecases/list-templates-usecase.ts +30 -0
  16. package/scripts/harness/ci-governance/application/usecases/scaffold-inception-usecase.ts +63 -0
  17. package/scripts/harness/ci-governance/application/usecases/show-template-usecase.ts +39 -0
  18. package/scripts/harness/ci-governance/composition-root.ts +55 -3
  19. package/scripts/harness/ci-governance/domain/ports/inception-doc-writer-port.ts +19 -0
  20. package/scripts/harness/ci-governance/domain/ports/inception-template-repository-port.ts +16 -0
  21. package/scripts/harness/ci-governance/domain/ports/template-catalog-port.ts +23 -0
  22. package/scripts/harness/ci-governance/domain/value-objects/inception-doc-kind.ts +109 -0
  23. package/scripts/harness/ci-governance/domain/value-objects/template-catalog-entry.ts +53 -0
  24. package/scripts/harness/ci-governance/domain/value-objects/template-name.ts +50 -0
  25. package/scripts/harness/ci-governance/infrastructure/adapters/file-system-inception-doc-writer-adapter.ts +42 -0
  26. package/scripts/harness/ci-governance/infrastructure/adapters/file-system-inception-template-repository-adapter.ts +32 -0
  27. package/scripts/harness/ci-governance/infrastructure/adapters/file-system-template-catalog-adapter.ts +60 -0
  28. package/scripts/harness/ci-governance/presentation/handlers/scaffold-inception-handler.ts +101 -0
  29. package/scripts/harness/ci-governance/presentation/handlers/templates-handler.ts +103 -0
  30. package/scripts/harness/config-foundation/domain/harness-config.ts +12 -0
  31. package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +2 -2
  32. package/scripts/harness/config-foundation/infrastructure/presets/standard.json +2 -2
  33. package/scripts/harness/config-foundation/infrastructure/presets/strict.json +2 -2
  34. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +44 -1
  35. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +42 -1
  36. package/scripts/harness/harness-api/domain/value-objects/known-harness-commands.ts +3 -1
  37. package/scripts/harness/main.ts +102 -12
  38. package/scripts/harness/phase-dependency-model/application/services/evidence-bundle-assembler.ts +2 -1
  39. package/scripts/harness/phase-dependency-model/domain/ports/plan-document-reader-port.ts +12 -0
  40. package/scripts/harness/phase-dependency-model/infrastructure/filesystem/markdown-plan-document-reader.ts +4 -1
  41. package/scripts/harness/quick-mode/domain/errors/quick-mode-config-error.ts +19 -0
  42. package/scripts/harness/quick-mode/domain/services/quick-mode-judgment-engine.ts +46 -17
  43. package/scripts/harness/quick-mode/domain/value-objects/category-override-rules.ts +147 -0
  44. package/scripts/harness/quick-mode/domain/value-objects/change-category.ts +33 -0
  45. package/scripts/harness/quick-mode/domain/value-objects/quick-mode-config.ts +31 -10
  46. package/scripts/harness/quick-mode/index.ts +7 -1
  47. package/scripts/harness/quick-mode/infrastructure/adapters/harness-config-quick-mode-config-adapter.ts +83 -32
  48. package/skills/product-architect/SKILL.md +19 -0
  49. package/skills/story-mapper/SKILL.md +11 -0
  50. package/skills/story-writer/SKILL.md +11 -0
  51. package/skills/unit-designer/SKILL.md +11 -0
  52. package/templates/product_overview.template.md +85 -0
  53. package/templates/product_overview_plan.template.md +55 -0
  54. package/templates/story_mapping_plan.template.md +61 -0
  55. package/templates/story_writer_plan.template.md +61 -0
  56. package/templates/unit_design_plan.template.md +63 -0
@@ -9,7 +9,8 @@
9
9
  *
10
10
  * 副作用なし・純粋関数相当。対応パターンは以下:
11
11
  * - リダイレクト `>` / `>>` / `>|` (clobber)
12
- * - heredoc (`<<EOF > path`)
12
+ * - heredoc (`<<EOF > path`) — ヒアドキュメント**本文**はシェル構文解析の対象外
13
+ * (本文中の `<...>` をリダイレクトと誤認しないため。WI-362)
13
14
  * - `tee` / `tee -a`
14
15
  * - `sed -i` / `sed -i ''` (BSD)
15
16
  * - `cp` / `mv` (宛先のみ)
@@ -111,6 +112,191 @@ function tokenize(input: string): Token[] {
111
112
  return tokens;
112
113
  }
113
114
 
115
+ /** ヒアドキュメント開始演算子で宣言された終端デリミタ */
116
+ type HeredocDelimiter = {
117
+ /** クォート・エスケープを解除した終端語 */
118
+ readonly word: string;
119
+ /** `<<-` 形式か (終端行の先頭タブを無視する) */
120
+ readonly allowIndent: boolean;
121
+ };
122
+
123
+ /** デリミタ語の終端となる文字 (空白およびシェル演算子) */
124
+ const DELIMITER_TERMINATORS = new Set([' ', '\t', ';', '&', '|', '<', '>', '(', ')']);
125
+
126
+ /**
127
+ * 1 行から ヒアドキュメント開始演算子 (`<<WORD` / `<<-WORD` / `<<'WORD'`) を検出し、
128
+ * 宣言順に終端デリミタを返す。
129
+ *
130
+ * クォート状態を追跡し、クォート内の `<<` は演算子として扱わない
131
+ * (例: `echo "a << b" > out.txt` の `<<` はヒアドキュメントではない)。
132
+ * `$( ... )` コマンド置換の内側ではクォート文脈がリセットされるため、
133
+ * `git commit -m "$(cat <<'EOF'` のような入れ子も検出できる。
134
+ * ヒアストリング `<<<` は本文を持たないため対象外。
135
+ */
136
+ function findHeredocDelimiters(line: string): HeredocDelimiter[] {
137
+ const found: HeredocDelimiter[] = [];
138
+ const substitutionStack: ('none' | 'single' | 'double')[] = [];
139
+ let quote: 'none' | 'single' | 'double' = 'none';
140
+ let i = 0;
141
+
142
+ while (i < line.length) {
143
+ const ch = line[i];
144
+
145
+ if (quote === 'single') {
146
+ if (ch === "'") quote = 'none';
147
+ i += 1;
148
+ continue;
149
+ }
150
+
151
+ if (ch === '\\') {
152
+ i += 2;
153
+ continue;
154
+ }
155
+
156
+ if (ch === '$' && line[i + 1] === '(') {
157
+ substitutionStack.push(quote);
158
+ quote = 'none';
159
+ i += 2;
160
+ continue;
161
+ }
162
+
163
+ if (quote === 'double') {
164
+ if (ch === '"') quote = 'none';
165
+ i += 1;
166
+ continue;
167
+ }
168
+
169
+ if (ch === "'") {
170
+ quote = 'single';
171
+ i += 1;
172
+ continue;
173
+ }
174
+
175
+ if (ch === '"') {
176
+ quote = 'double';
177
+ i += 1;
178
+ continue;
179
+ }
180
+
181
+ if (ch === ')' && substitutionStack.length > 0) {
182
+ quote = substitutionStack.pop() ?? 'none';
183
+ i += 1;
184
+ continue;
185
+ }
186
+
187
+ if (ch === '<' && line[i + 1] === '<') {
188
+ if (line[i + 2] === '<') {
189
+ // ヒアストリング `<<<` は本文行を持たない
190
+ i += 3;
191
+ continue;
192
+ }
193
+ i += 2;
194
+ let allowIndent = false;
195
+ if (line[i] === '-') {
196
+ allowIndent = true;
197
+ i += 1;
198
+ }
199
+ while (i < line.length && (line[i] === ' ' || line[i] === '\t')) i += 1;
200
+ const word = readHeredocDelimiterWord(line, i);
201
+ i = word.nextIndex;
202
+ if (word.value.length > 0) {
203
+ found.push({ word: word.value, allowIndent });
204
+ }
205
+ continue;
206
+ }
207
+
208
+ i += 1;
209
+ }
210
+
211
+ return found;
212
+ }
213
+
214
+ /** `<<` 直後のデリミタ語を読む。クォート/バックスラッシュを解除した値を返す。 */
215
+ function readHeredocDelimiterWord(line: string, start: number): { value: string; nextIndex: number } {
216
+ let value = '';
217
+ let i = start;
218
+
219
+ while (i < line.length) {
220
+ const ch = line[i];
221
+
222
+ if (ch === "'") {
223
+ i += 1;
224
+ while (i < line.length && line[i] !== "'") {
225
+ value += line[i];
226
+ i += 1;
227
+ }
228
+ i += 1;
229
+ continue;
230
+ }
231
+
232
+ if (ch === '"') {
233
+ i += 1;
234
+ while (i < line.length && line[i] !== '"') {
235
+ if (line[i] === '\\' && i + 1 < line.length) {
236
+ value += line[i + 1];
237
+ i += 2;
238
+ continue;
239
+ }
240
+ value += line[i];
241
+ i += 1;
242
+ }
243
+ i += 1;
244
+ continue;
245
+ }
246
+
247
+ if (ch === '\\' && i + 1 < line.length) {
248
+ value += line[i + 1];
249
+ i += 2;
250
+ continue;
251
+ }
252
+
253
+ if (DELIMITER_TERMINATORS.has(ch)) break;
254
+
255
+ value += ch;
256
+ i += 1;
257
+ }
258
+
259
+ return { value, nextIndex: i };
260
+ }
261
+
262
+ /** 終端行判定 (`<<-` は先頭タブを無視し、行末の空白・CR は許容する) */
263
+ function isHeredocTerminator(line: string, delimiter: HeredocDelimiter): boolean {
264
+ const candidate = delimiter.allowIndent ? line.replace(/^\t+/, '') : line;
265
+ return candidate.trimEnd() === delimiter.word;
266
+ }
267
+
268
+ /**
269
+ * ヒアドキュメント本文行を除去する。
270
+ *
271
+ * 本文はシェル構文ではなくデータであり、`Co-Authored-By: Claude <noreply@example.com>`
272
+ * のような `<...>` をリダイレクトと誤解釈すると無関係なコマンドが誤ブロックされる (WI-362)。
273
+ * 除去対象は**本文行と終端行のみ**で、開始演算子のある行はそのまま残すため
274
+ * `cat <<EOF > path` の `> path` は従来どおり検出される (fail-closed 維持)。
275
+ */
276
+ function stripHeredocBodies(command: string): string {
277
+ if (!command.includes('<<')) return command;
278
+
279
+ const lines = command.split('\n');
280
+ const kept: string[] = [];
281
+ const pending: HeredocDelimiter[] = [];
282
+
283
+ for (const line of lines) {
284
+ if (pending.length > 0) {
285
+ // 本文行 / 終端行はいずれも構文解析対象から外す
286
+ if (isHeredocTerminator(line, pending[0])) {
287
+ pending.shift();
288
+ }
289
+ continue;
290
+ }
291
+ kept.push(line);
292
+ for (const delimiter of findHeredocDelimiters(line)) {
293
+ pending.push(delimiter);
294
+ }
295
+ }
296
+
297
+ return kept.join('\n');
298
+ }
299
+
114
300
  const OPERATOR_SEPARATORS = new Set(['&&', '||', ';', '|']);
115
301
 
116
302
  /** トークン列を複合・パイプ境界で分割 */
@@ -311,7 +497,9 @@ function collectPositionals(tokens: Token[]): Token[] {
311
497
 
312
498
  /** コマンド文字列を token 化 → 演算子分割 → 各コマンドから書き込み先を抽出する内部関数。 */
313
499
  function extractFromCommandString(command: string): string[] {
314
- const tokens = tokenize(command);
500
+ // ヒアドキュメント本文はデータなのでシェル構文解析の対象外にする (WI-362)
501
+ // apply_patch 抽出は元の command 文字列を直接走査するため影響を受けない。
502
+ const tokens = tokenize(stripHeredocBodies(command));
315
503
  const groups = splitByOperators(tokens);
316
504
  const collected: string[] = [];
317
505
  for (const group of groups) {
@@ -25,6 +25,11 @@ const DEFAULT_PATTERNS = [
25
25
  // grandfather bypass を防ぐため、書き込み自体を保護対象とする。
26
26
  '.phasegate/baseline.json',
27
27
  '**/.phasegate/baseline.json',
28
+ // WI-363: .husky/ 配下は L0 runtime の実施点(pre-commit / commit-msg / pre-push)。
29
+ // WI-352 で config カテゴリに分類されるようになり Quick Mode の書き込み許可対象に
30
+ // 入ったため、防御機構そのものの書き換えを protected file として明示的に止める。
31
+ '.husky/**',
32
+ '**/.husky/**',
28
33
  ];
29
34
 
30
35
  /**
@@ -1,5 +1,6 @@
1
1
  // @unit agent-integration
2
2
  // @layer infrastructure
3
+ // @work-item-id WI-373
3
4
 
4
5
  import type {
5
6
  FullModeRequirementQueryPort,
@@ -7,6 +8,7 @@ import type {
7
8
  FullModeTargetChange,
8
9
  } from '../../domain/ports/full-mode-requirement-query-port.js';
9
10
  import type { ClassifyChangeCategoryUseCase } from '../../../quick-mode/application/usecases/classify-change-category-usecase.js';
11
+ import { QuickModeConfigError } from '../../../quick-mode/domain/errors/quick-mode-config-error.js';
10
12
 
11
13
  export interface QuickModeFullModeRequirementAdapterDeps {
12
14
  classifyUseCaseFactory: () => ClassifyChangeCategoryUseCase;
@@ -45,7 +47,19 @@ export class QuickModeFullModeRequirementAdapter implements FullModeRequirementQ
45
47
  rejectionReason: contract.rejectionReason,
46
48
  dominantCategory: contract.dominantCategory ?? undefined,
47
49
  };
48
- } catch {
50
+ } catch (error) {
51
+ // WI-373: quickMode 設定そのものが不正な場合は fail-closed。
52
+ // allowedCategories / categoryOverrides の enum 検証が入ったことで
53
+ // config の typo が例外になるため、ここで一律 fail-open のままだと
54
+ // 「設定を壊すと全書き込みが素通りする」という最悪の穴になる。
55
+ // config 不在・IO エラー等その他の例外は WI-333 の fail-open を維持する。
56
+ if (error instanceof QuickModeConfigError) {
57
+ return {
58
+ requiresFullMode: true,
59
+ rejectionRule: 'MIXED_CHANGES',
60
+ rejectionReason: `quickMode 設定が不正なため Quick Mode 判定を実行できません: ${error.message}`,
61
+ };
62
+ }
49
63
  return { requiresFullMode: false };
50
64
  }
51
65
  }
@@ -6,6 +6,7 @@
6
6
  * @work-item-id WI-208
7
7
  * @work-item-id WI-345
8
8
  * @work-item-id WI-347
9
+ * @work-item-id WI-376
9
10
  *
10
11
  * PreToolUse Hook Adapter
11
12
  * Claude Code の PreToolUse Hook エントリポイント
@@ -25,10 +26,14 @@ import { createQuickModeCompositionRoot } from '../../quick-mode/composition-roo
25
26
  import * as path from 'node:path';
26
27
  import * as fs from 'node:fs/promises';
27
28
 
29
+ /**
30
+ * WI-376 (ADR-039): 呼び出し元 skill 名を受け取るフィールド(caller_skill)は持たない。
31
+ * Claude Code の PreToolUse payload に skill 情報は無く、エージェントの自己申告値は
32
+ * authorization / guidance の入力にしない。未知キーが来ても単に無視される。
33
+ */
28
34
  interface PreToolUseHookInput {
29
35
  cwd?: string;
30
36
  tool_name?: string;
31
- caller_skill?: string;
32
37
  tool_input?: {
33
38
  path?: string;
34
39
  file_path?: string;
@@ -199,11 +204,9 @@ async function main(): Promise<void> {
199
204
  fullModeSessionQueryPort,
200
205
  });
201
206
 
202
- const callerSkill = input.caller_skill ?? process.env.PHASEGATE_CALLER_SKILL;
203
207
  const output = await useCase.execute({
204
208
  toolName: effectiveToolName,
205
209
  targetFilePaths: projectTargetFilePaths,
206
- callerSkill,
207
210
  targetChanges: projectTargetChanges,
208
211
  });
209
212
 
@@ -0,0 +1,9 @@
1
+ // @unit ci-governance
2
+ // @layer application
3
+ // @work-item-id WI-368
4
+
5
+ export interface ScaffoldInceptionInput {
6
+ readonly kind: string;
7
+ readonly dryRun?: boolean;
8
+ readonly force?: boolean;
9
+ }
@@ -0,0 +1,13 @@
1
+ // @unit ci-governance
2
+ // @layer application
3
+ // @work-item-id WI-368
4
+
5
+ export interface ScaffoldInceptionOutput {
6
+ readonly targetPath: string;
7
+ readonly templatePath: string;
8
+ readonly kind: string;
9
+ readonly dryRun: boolean;
10
+ readonly written: boolean;
11
+ readonly alreadyExists: boolean;
12
+ readonly overwritten: boolean;
13
+ }
@@ -0,0 +1,30 @@
1
+ // @unit ci-governance
2
+ // @layer application
3
+ // @work-item-id WI-367
4
+
5
+ import type { TemplateCatalogPort } from "../../domain/ports/template-catalog-port.js";
6
+
7
+ export interface ListTemplatesOutputEntry {
8
+ readonly name: string;
9
+ readonly fileName: string;
10
+ }
11
+
12
+ export interface ListTemplatesOutput {
13
+ readonly directoryPath: string;
14
+ readonly templates: readonly ListTemplatesOutputEntry[];
15
+ }
16
+
17
+ export class ListTemplatesUseCase {
18
+ constructor(private readonly catalog: TemplateCatalogPort) {}
19
+
20
+ async execute(): Promise<ListTemplatesOutput> {
21
+ const entries = await this.catalog.list();
22
+ return {
23
+ directoryPath: this.catalog.directoryPath(),
24
+ templates: entries.map((entry) => ({
25
+ name: entry.name.value,
26
+ fileName: entry.fileName,
27
+ })),
28
+ };
29
+ }
30
+ }
@@ -0,0 +1,63 @@
1
+ // @unit ci-governance
2
+ // @layer application
3
+ // @work-item-id WI-368
4
+
5
+ import type { InceptionDocWriterPort } from "../../domain/ports/inception-doc-writer-port.js";
6
+ import type { InceptionTemplateRepositoryPort } from "../../domain/ports/inception-template-repository-port.js";
7
+ import { InceptionDocKind } from "../../domain/value-objects/inception-doc-kind.js";
8
+ import type { ScaffoldInceptionInput } from "../dto/scaffold-inception-input.js";
9
+ import type { ScaffoldInceptionOutput } from "../dto/scaffold-inception-output.js";
10
+
11
+ export class ScaffoldInceptionUseCase {
12
+ constructor(
13
+ private readonly templates: InceptionTemplateRepositoryPort,
14
+ private readonly writer: InceptionDocWriterPort,
15
+ ) {}
16
+
17
+ async execute(input: ScaffoldInceptionInput): Promise<ScaffoldInceptionOutput> {
18
+ const kind = InceptionDocKind.create(input.kind);
19
+ const dryRun = input.dryRun === true;
20
+ const force = input.force === true;
21
+
22
+ const templatePath = this.templates.resolvePath(kind);
23
+ const targetPath = this.writer.resolvePath(kind);
24
+ const alreadyExists = await this.writer.exists(kind);
25
+
26
+ if (dryRun) {
27
+ return {
28
+ targetPath,
29
+ templatePath,
30
+ kind: kind.value,
31
+ dryRun: true,
32
+ written: false,
33
+ alreadyExists,
34
+ overwritten: false,
35
+ };
36
+ }
37
+
38
+ if (alreadyExists && !force) {
39
+ return {
40
+ targetPath,
41
+ templatePath,
42
+ kind: kind.value,
43
+ dryRun: false,
44
+ written: false,
45
+ alreadyExists: true,
46
+ overwritten: false,
47
+ };
48
+ }
49
+
50
+ const raw = await this.templates.read(kind);
51
+ await this.writer.write(kind, raw);
52
+
53
+ return {
54
+ targetPath,
55
+ templatePath,
56
+ kind: kind.value,
57
+ dryRun: false,
58
+ written: true,
59
+ alreadyExists,
60
+ overwritten: alreadyExists && force,
61
+ };
62
+ }
63
+ }
@@ -0,0 +1,39 @@
1
+ // @unit ci-governance
2
+ // @layer application
3
+ // @work-item-id WI-367
4
+
5
+ import type { TemplateCatalogPort } from "../../domain/ports/template-catalog-port.js";
6
+ import { TemplateName } from "../../domain/value-objects/template-name.js";
7
+
8
+ export type ShowTemplateOutput =
9
+ | { readonly found: true; readonly name: string; readonly content: string }
10
+ | {
11
+ readonly found: false;
12
+ readonly reason: "invalid-name" | "not-found";
13
+ readonly availableNames: readonly string[];
14
+ };
15
+
16
+ export class ShowTemplateUseCase {
17
+ constructor(private readonly catalog: TemplateCatalogPort) {}
18
+
19
+ async execute(rawName: string): Promise<ShowTemplateOutput> {
20
+ if (!TemplateName.isValid(rawName)) {
21
+ // 不正名では catalog を引かない。パス由来の情報を一切露出させない。
22
+ return { found: false, reason: "invalid-name", availableNames: [] };
23
+ }
24
+
25
+ const name = TemplateName.create(rawName);
26
+ const content = await this.catalog.read(name);
27
+
28
+ if (content === null) {
29
+ const entries = await this.catalog.list();
30
+ return {
31
+ found: false,
32
+ reason: "not-found",
33
+ availableNames: entries.map((entry) => entry.name.value),
34
+ };
35
+ }
36
+
37
+ return { found: true, name: name.value, content };
38
+ }
39
+ }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @layer infrastructure
3
3
  * @unit ci-governance
4
+ * @work-item-id WI-367, WI-368, WI-369
4
5
  *
5
6
  * Composition Root - 依存性の組み立て
6
7
  */
@@ -10,6 +11,7 @@ import { CheckAgentContextUseCase } from "./application/usecases/check-agent-con
10
11
  import { CheckEscalationUseCase } from "./application/usecases/check-escalation-usecase.js";
11
12
  import { CreateBaselineUseCase } from "./application/usecases/create-baseline-usecase.js";
12
13
  import { GenerateCiTemplateUseCase } from "./application/usecases/generate-ci-template-usecase.js";
14
+ import { ListTemplatesUseCase } from "./application/usecases/list-templates-usecase.js";
13
15
  import { MigrateAgentsMdUseCase } from "./application/usecases/migrate-agents-md-usecase.js";
14
16
  import { PinIntegrityUseCase } from "./application/usecases/pin-integrity-usecase.js";
15
17
  import { RecordErrorOccurrenceUseCase } from "./application/usecases/record-error-occurrence-usecase.js";
@@ -18,6 +20,8 @@ import { RefreshClaudeMdUseCase } from "./application/usecases/refresh-claude-md
18
20
  import { RenderCiTemplateUseCase } from "./application/usecases/render-ci-template-usecase.js";
19
21
  import { ResetRepetitionUseCase } from "./application/usecases/reset-repetition-usecase.js";
20
22
  import { ScaffoldDesignUseCase } from "./application/usecases/scaffold-design-usecase.js";
23
+ import { ScaffoldInceptionUseCase } from "./application/usecases/scaffold-inception-usecase.js";
24
+ import { ShowTemplateUseCase } from "./application/usecases/show-template-usecase.js";
21
25
  import { ValidatePointersUseCase } from "./application/usecases/validate-pointers-usecase.js";
22
26
  import { VerifyIntegrityUseCase } from "./application/usecases/verify-integrity-usecase.js";
23
27
  import { ClaudeMdComposer } from "./domain/services/claude-md-composer.js";
@@ -34,8 +38,11 @@ import { ErrorRepetitionJsonRepository } from "./infrastructure/adapters/error-r
34
38
  import { EscalationLogExecutorAdapter } from "./infrastructure/adapters/escalation-log-executor-adapter.js";
35
39
  import { FileSystemDesignDocWriterAdapter } from "./infrastructure/adapters/file-system-design-doc-writer-adapter.js";
36
40
  import { FileSystemExistenceAdapter } from "./infrastructure/adapters/file-system-existence-adapter.js";
41
+ import { FileSystemInceptionDocWriterAdapter } from "./infrastructure/adapters/file-system-inception-doc-writer-adapter.js";
42
+ import { FileSystemInceptionTemplateRepositoryAdapter } from "./infrastructure/adapters/file-system-inception-template-repository-adapter.js";
37
43
  import { FileSystemSha1HasherAdapter } from "./infrastructure/adapters/file-system-sha1-hasher-adapter.js";
38
44
  import { FileSystemSha256HasherAdapter } from "./infrastructure/adapters/file-system-sha256-hasher-adapter.js";
45
+ import { FileSystemTemplateCatalogAdapter } from "./infrastructure/adapters/file-system-template-catalog-adapter.js";
39
46
  import { FileSystemTemplateRepositoryAdapter } from "./infrastructure/adapters/file-system-template-repository-adapter.js";
40
47
  import { GlobFileScannerAdapter } from "./infrastructure/adapters/glob-file-scanner-adapter.js";
41
48
  import { HarnessApiCommandExistenceAdapter } from "./infrastructure/adapters/harness-api-command-existence-adapter.js";
@@ -53,6 +60,8 @@ import { MigrateAgentsMdHandler } from "./presentation/handlers/migrate-agents-m
53
60
  import { RefreshAgentContextHandler } from "./presentation/handlers/refresh-agent-context-handler.js";
54
61
  import { RefreshClaudeMdHandler } from "./presentation/handlers/refresh-claude-md-handler.js";
55
62
  import { ScaffoldDesignHandler } from "./presentation/handlers/scaffold-design-handler.js";
63
+ import { ScaffoldInceptionHandler } from "./presentation/handlers/scaffold-inception-handler.js";
64
+ import { TemplatesHandler } from "./presentation/handlers/templates-handler.js";
56
65
 
57
66
  export interface CiGovernanceCompositionRoot {
58
67
  generateCiTemplateHandler: GenerateCiTemplateHandler;
@@ -63,6 +72,8 @@ export interface CiGovernanceCompositionRoot {
63
72
  checkRepetitionHandler: CheckRepetitionHandler;
64
73
  createBaselineHandler: CreateBaselineHandler;
65
74
  scaffoldDesignHandler: ScaffoldDesignHandler;
75
+ scaffoldInceptionHandler: ScaffoldInceptionHandler;
76
+ templatesHandler: TemplatesHandler;
66
77
  // Use cases exposed for direct access
67
78
  recordErrorOccurrenceUseCase: RecordErrorOccurrenceUseCase;
68
79
  checkEscalationUseCase: CheckEscalationUseCase;
@@ -71,10 +82,33 @@ export interface CiGovernanceCompositionRoot {
71
82
  validatePointersUseCase: ValidatePointersUseCase;
72
83
  createBaselineUseCase: CreateBaselineUseCase;
73
84
  scaffoldDesignUseCase: ScaffoldDesignUseCase;
85
+ scaffoldInceptionUseCase: ScaffoldInceptionUseCase;
74
86
  integrityHandler: IntegrityHandler;
75
87
  }
76
88
 
77
- export function buildCiGovernance(baseDir: string, harnessRoot: string = baseDir): CiGovernanceCompositionRoot {
89
+ /**
90
+ * 解決済みの設計文書ルート(`phasegate.config.json` の `paths`)。
91
+ * 省略時は従来どおり `docs/product/construction` / `docs/inception` を使う。
92
+ *
93
+ * WI-369: これを渡さないと `scaffold-design` の書き込み先が
94
+ * `docs/product/construction` に固定され、`paths.designDocs` を移設した PJ で
95
+ * scaffold 先とフェーズゲートの検査先がズレる。
96
+ */
97
+ export interface CiGovernanceDocPaths {
98
+ readonly designDocs?: string;
99
+ readonly inceptionDocs?: string;
100
+ }
101
+
102
+ const DEFAULT_DESIGN_DOCS_ROOT = "docs/product/construction";
103
+ const DEFAULT_INCEPTION_DOCS_ROOT = "docs/inception";
104
+
105
+ export function buildCiGovernance(
106
+ baseDir: string,
107
+ harnessRoot: string = baseDir,
108
+ docPaths: CiGovernanceDocPaths = {},
109
+ ): CiGovernanceCompositionRoot {
110
+ const designDocsRoot = docPaths.designDocs ?? DEFAULT_DESIGN_DOCS_ROOT;
111
+ const inceptionDocsRoot = docPaths.inceptionDocs ?? DEFAULT_INCEPTION_DOCS_ROOT;
78
112
  // Infrastructure adapters
79
113
  const validatorIdRegistryAdapter = new ValidatorIdRegistryAdapter();
80
114
  const presetConfigAdapter = new PresetConfigAdapter();
@@ -119,11 +153,24 @@ export function buildCiGovernance(baseDir: string, harnessRoot: string = baseDir
119
153
  const baselineRepository = new BaselineJsonRepositoryAdapter(baseDir);
120
154
  const createBaselineUseCase = new CreateBaselineUseCase(fileScanner, fileHasher, baselineRepository);
121
155
 
122
- // Scaffold design adapters & use case (ISSUE-007 Wave 4)
156
+ // Scaffold design adapters & use case (ISSUE-007 Wave 4 / WI-369 paths 追従)
123
157
  const templateRepository = new FileSystemTemplateRepositoryAdapter(harnessRoot);
124
- const designDocWriter = new FileSystemDesignDocWriterAdapter(baseDir);
158
+ const designDocWriter = new FileSystemDesignDocWriterAdapter(baseDir, designDocsRoot);
125
159
  const scaffoldDesignUseCase = new ScaffoldDesignUseCase(templateRepository, designDocWriter);
126
160
 
161
+ // Scaffold inception adapters & use case (WI-368)
162
+ const inceptionTemplateRepository = new FileSystemInceptionTemplateRepositoryAdapter(harnessRoot);
163
+ const inceptionDocWriter = new FileSystemInceptionDocWriterAdapter(baseDir, {
164
+ designDocsRoot,
165
+ inceptionDocsRoot,
166
+ });
167
+ const scaffoldInceptionUseCase = new ScaffoldInceptionUseCase(inceptionTemplateRepository, inceptionDocWriter);
168
+
169
+ // Template catalog adapters & use cases (WI-367)
170
+ const templateCatalog = new FileSystemTemplateCatalogAdapter(harnessRoot);
171
+ const listTemplatesUseCase = new ListTemplatesUseCase(templateCatalog);
172
+ const showTemplateUseCase = new ShowTemplateUseCase(templateCatalog);
173
+
127
174
  // Handlers
128
175
  const generateCiTemplateHandler = new GenerateCiTemplateHandler(generateCiTemplateUseCase, renderCiTemplateUseCase);
129
176
  const migrateAgentsMdHandler = new MigrateAgentsMdHandler(migrateAgentsMdUseCase, validatePointersUseCase);
@@ -133,6 +180,8 @@ export function buildCiGovernance(baseDir: string, harnessRoot: string = baseDir
133
180
  const checkRepetitionHandler = new CheckRepetitionHandler(checkEscalationUseCase, resetRepetitionUseCase);
134
181
  const createBaselineHandler = new CreateBaselineHandler(createBaselineUseCase);
135
182
  const scaffoldDesignHandler = new ScaffoldDesignHandler(scaffoldDesignUseCase);
183
+ const scaffoldInceptionHandler = new ScaffoldInceptionHandler(scaffoldInceptionUseCase);
184
+ const templatesHandler = new TemplatesHandler(listTemplatesUseCase, showTemplateUseCase);
136
185
 
137
186
  // Integrity pin adapters & handler (WI-254 / ADR-030 §Decision.3.①)
138
187
  const integrityChecker = new IntegrityChecker();
@@ -156,6 +205,8 @@ export function buildCiGovernance(baseDir: string, harnessRoot: string = baseDir
156
205
  checkRepetitionHandler,
157
206
  createBaselineHandler,
158
207
  scaffoldDesignHandler,
208
+ scaffoldInceptionHandler,
209
+ templatesHandler,
159
210
  recordErrorOccurrenceUseCase,
160
211
  checkEscalationUseCase,
161
212
  resetRepetitionUseCase,
@@ -163,6 +214,7 @@ export function buildCiGovernance(baseDir: string, harnessRoot: string = baseDir
163
214
  validatePointersUseCase,
164
215
  createBaselineUseCase,
165
216
  scaffoldDesignUseCase,
217
+ scaffoldInceptionUseCase,
166
218
  integrityHandler,
167
219
  };
168
220
  }
@@ -0,0 +1,19 @@
1
+ // @unit ci-governance
2
+ // @layer domain
3
+ // @work-item-id WI-368
4
+
5
+ import type { InceptionDocKind } from "../value-objects/inception-doc-kind.js";
6
+
7
+ export interface InceptionDocWriterPort {
8
+ /** 書き込み先の絶対パスを返す(副作用なし) */
9
+ resolvePath(kind: InceptionDocKind): string;
10
+
11
+ /** 書き込み先が既に存在するか */
12
+ exists(kind: InceptionDocKind): Promise<boolean>;
13
+
14
+ /**
15
+ * inception / product 文書を書き込む(親ディレクトリが無ければ作成)。
16
+ * 書き込んだ絶対パスを返す。
17
+ */
18
+ write(kind: InceptionDocKind, content: string): Promise<string>;
19
+ }
@@ -0,0 +1,16 @@
1
+ // @unit ci-governance
2
+ // @layer domain
3
+ // @work-item-id WI-368
4
+
5
+ import type { InceptionDocKind } from "../value-objects/inception-doc-kind.js";
6
+
7
+ export interface InceptionTemplateRepositoryPort {
8
+ /** テンプレートの絶対パスを返す(UI 出力用) */
9
+ resolvePath(kind: InceptionDocKind): string;
10
+
11
+ /**
12
+ * kind に対応するテンプレートファイルを読み込む。
13
+ * 存在しない場合は例外を投げる。
14
+ */
15
+ read(kind: InceptionDocKind): Promise<string>;
16
+ }
@@ -0,0 +1,23 @@
1
+ // @unit ci-governance
2
+ // @layer domain
3
+ // @work-item-id WI-367
4
+
5
+ import type { TemplateCatalogEntry } from "../value-objects/template-catalog-entry.js";
6
+ import type { TemplateName } from "../value-objects/template-name.js";
7
+
8
+ export interface TemplateCatalogPort {
9
+ /** テンプレートディレクトリの絶対パス(案内表示用) */
10
+ directoryPath(): string;
11
+
12
+ /**
13
+ * 実在するテンプレートを name 昇順で列挙する。
14
+ * ディレクトリが存在しない場合は空配列を返す(例外にしない)。
15
+ */
16
+ list(): Promise<readonly TemplateCatalogEntry[]>;
17
+
18
+ /**
19
+ * catalog に name 完全一致するテンプレート本文を読む。
20
+ * 一致するエントリが無い場合は null を返す。
21
+ */
22
+ read(name: TemplateName): Promise<string | null>;
23
+ }