phasegate 0.72.0 → 0.74.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,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.74.0] - 2026-04-22
11
+
12
+ ### Fixed
13
+
14
+ - ISSUE-007 Wave 9 — phase-gate ブロック時に出力される `scaffold:` 行に含まれる `<unit-id>` プレースホルダを、実 unit ID に置換するようにした。従来は L2-001 registry (`l2-error-definitions.ts`) の `defaultScaffoldCommand` が静的文字列 `npx phasegate scaffold-design --unit <unit-id> --phase logical` のまま出力されていたため、ユーザーが手で unit 名に書き換える必要があった。PHASE_GATE の場合は `metadata.unitId`、FULL_MODE_REQUIRED の場合は `targetFilePaths` から `WriteTargetScope.fromPath` で導出した unit ID を使って置換する。
15
+ - IT-AI-GUIDE-UID-001 / IT-AI-GUIDE-UID-002 を追加(PHASE_GATE と FULL_MODE_REQUIRED の両経路で `<unit-id>` が実 unit に置換されることを検証)。
16
+
17
+ ## [0.73.0] - 2026-04-22
18
+
19
+ ### Added
20
+
21
+ - ISSUE-007 Wave 8 — `phasegate.config.json` の `project.paths` セクションを schema (`harness-config-v2.schema.json`) に追加。`project.paths.source` (array, minItems: 1) で phase-gate の監視ディレクトリを override できるようになった。`project.paths.docs.construction` / `project.paths.docs.inception` も optional で指定可能。
22
+ - IT-CF-PP-001a..d を追加(`project.paths.source` の valid/invalid パターン)。
23
+
24
+ ### Fixed
25
+
26
+ - ISSUE-007 Wave 8 dogfood で発覚した retrofit blocker を解消 — 従来は adapter (`harness-config-config-query-adapter.ts`) が `config.project?.paths?.source` を読む設計だったにも関わらず schema がそれを additionalProperties として reject していたため、`src/` 配下を使う一般 Node.js プロジェクトでは phase-gate が実質無効化されていた。
27
+
28
+ ### Retrofit ガイド追補
29
+
30
+ - `docs/guide/retrofit-adoption.md` に「source path の指定」セクションを追加。`src/` 系プロジェクトでは `project.paths.source: ["src"]` の明示が必須である旨を記載。
31
+
10
32
  ## [0.72.0] - 2026-04-22
11
33
 
12
34
  ### Changed
@@ -63,6 +63,42 @@ grandfather をオフにしたい場合のみ `phasegate.config.json` に明示:
63
63
  `path` も省略時は `.phasegate/baseline.json` が使われる。retrofit 用途では
64
64
  この既定値のまま触らないのが最短。
65
65
 
66
+ ### source path の指定(v0.73.0 以降、`src/` 系 retrofit に必須)
67
+
68
+ pre-tool-use hook の phase-gate は `project.paths.source` に記載されたディレクトリ
69
+ 配下のファイルのみを監視対象にする。既定値は `["scripts/harness"]` で、これは
70
+ phasegate 本体のレイアウトに合わせたもの。**一般的な Node.js プロジェクト
71
+ (`src/` 配下) では必ず上書きすること**。
72
+
73
+ ```json
74
+ {
75
+ "project": {
76
+ "name": "your-project",
77
+ "preset": "standard",
78
+ "paths": {
79
+ "source": ["src"]
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
85
+ monorepo など複数ディレクトリを持つ場合は配列で列挙:
86
+
87
+ ```json
88
+ {
89
+ "project": {
90
+ "paths": {
91
+ "source": ["apps/web/src", "apps/api/src", "packages/shared/src"]
92
+ }
93
+ }
94
+ }
95
+ ```
96
+
97
+ この設定を忘れると、phase-gate は `scripts/harness/` 配下しか見ないため、
98
+ retrofit 対象の新規コード作成が block されず、結果として **phasegate が
99
+ 無効化された運用** になってしまう(ISSUE-007 Wave 8 で schema 上 overridable
100
+ になるまで、この設定は config validator で拒否されていた)。
101
+
66
102
  ---
67
103
 
68
104
  ## Step 2: 既存コードを baseline に登録
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phasegate",
3
- "version": "0.72.0",
3
+ "version": "0.74.0",
4
4
  "packageManager": "pnpm@10.30.1",
5
5
  "description": "Phasegate — AI-agnostic quality defense toolkit. Enforces structural integrity between design intent and code.",
6
6
  "license": "Apache-2.0",
@@ -103,7 +103,14 @@ export class HandlePreToolUseUseCase {
103
103
  // fallthrough: continue to full-mode / story-reflection checks (which may also grandfather)
104
104
  } else {
105
105
  const guidance = await this.resolveGuidance('L2-001');
106
- return HandlePreToolUseUseCase.buildPhaseGateBlockOutput(blockedFilePath, metadata, guidance);
106
+ const unitIdForGuidance =
107
+ metadata?.unitId ?? this.deriveUnitIdFromPaths(input.targetFilePaths);
108
+ return HandlePreToolUseUseCase.buildPhaseGateBlockOutput(
109
+ blockedFilePath,
110
+ metadata,
111
+ guidance,
112
+ unitIdForGuidance,
113
+ );
107
114
  }
108
115
  } else {
109
116
  return {
@@ -125,10 +132,12 @@ export class HandlePreToolUseUseCase {
125
132
  const fullModeResult = await this.fullModeRequirementQueryPort.check(input.targetFilePaths);
126
133
  if (fullModeResult.requiresFullMode) {
127
134
  const guidance = await this.resolveGuidance('L2-001');
135
+ const unitIdForGuidance = this.deriveUnitIdFromPaths(input.targetFilePaths);
128
136
  return HandlePreToolUseUseCase.buildFullModeRequiredBlockOutput(
129
137
  input.targetFilePaths[0],
130
138
  fullModeResult,
131
139
  guidance,
140
+ unitIdForGuidance,
132
141
  );
133
142
  }
134
143
  }
@@ -198,6 +207,7 @@ export class HandlePreToolUseUseCase {
198
207
  dominantCategory?: string;
199
208
  },
200
209
  guidance: ErrorGuidance | null,
210
+ unitId: string | undefined,
201
211
  ): HandlePreToolUseOutput {
202
212
  const fp = blockedFilePath ?? '不明なファイル';
203
213
  const lines: string[] = [
@@ -214,7 +224,7 @@ export class HandlePreToolUseUseCase {
214
224
  }
215
225
  const suggestedSkill = guidance?.suggestedSkill ?? '/story-implementor';
216
226
  lines.push(`次のアクション: ${suggestedSkill} スキルを使用して設計フェーズから開始してください。`);
217
- HandlePreToolUseUseCase.appendGuidanceLines(lines, guidance);
227
+ HandlePreToolUseUseCase.appendGuidanceLines(lines, guidance, unitId);
218
228
 
219
229
  return {
220
230
  shouldBlock: true,
@@ -227,16 +237,34 @@ export class HandlePreToolUseUseCase {
227
237
  };
228
238
  }
229
239
 
230
- private static appendGuidanceLines(lines: string[], guidance: ErrorGuidance | null): void {
240
+ private static appendGuidanceLines(
241
+ lines: string[],
242
+ guidance: ErrorGuidance | null,
243
+ unitId?: string,
244
+ ): void {
231
245
  if (guidance === null) return;
232
246
  if (guidance.scaffoldCommand !== null) {
233
- lines.push(` scaffold: ${guidance.scaffoldCommand}`);
247
+ const command = unitId !== undefined && unitId !== ''
248
+ ? guidance.scaffoldCommand.replaceAll('<unit-id>', unitId)
249
+ : guidance.scaffoldCommand;
250
+ lines.push(` scaffold: ${command}`);
234
251
  }
235
252
  if (guidance.templatePath !== null) {
236
253
  lines.push(` テンプレ: ${guidance.templatePath}`);
237
254
  }
238
255
  }
239
256
 
257
+ private deriveUnitIdFromPaths(targetFilePaths: readonly string[]): string | undefined {
258
+ const projectPaths = this.configQueryPort.getProjectPaths();
259
+ for (const targetFilePath of targetFilePaths) {
260
+ const scope = WriteTargetScope.fromPath(targetFilePath, projectPaths);
261
+ if (scope?.unitId !== undefined) {
262
+ return scope.unitId;
263
+ }
264
+ }
265
+ return undefined;
266
+ }
267
+
240
268
  private resolveStoryReflectionScope(input: HandlePreToolUseInput): WriteTargetScope | null {
241
269
  if (!HandlePreToolUseUseCase.WRITE_TOOLS.has(input.toolName)) {
242
270
  return null;
@@ -264,6 +292,7 @@ export class HandlePreToolUseUseCase {
264
292
  blockedFilePath: string | undefined,
265
293
  metadata: BlockMetadata,
266
294
  guidance: ErrorGuidance | null,
295
+ unitId: string | undefined,
267
296
  ): HandlePreToolUseOutput {
268
297
  const levelLabel = metadata.scopeLevel
269
298
  ? HandlePreToolUseUseCase.LEVEL_LABELS[metadata.scopeLevel] ?? `Level ${metadata.scopeLevel}`
@@ -286,7 +315,7 @@ export class HandlePreToolUseUseCase {
286
315
  if (metadata.unitId) {
287
316
  lines.push(` 実行例: ${suggestedSkill} --unit ${metadata.unitId}`);
288
317
  }
289
- HandlePreToolUseUseCase.appendGuidanceLines(lines, guidance);
318
+ HandlePreToolUseUseCase.appendGuidanceLines(lines, guidance, unitId);
290
319
 
291
320
  return {
292
321
  shouldBlock: true,
@@ -32,6 +32,35 @@
32
32
  "standard",
33
33
  "strict"
34
34
  ]
35
+ },
36
+ "paths": {
37
+ "type": "object",
38
+ "additionalProperties": false,
39
+ "properties": {
40
+ "source": {
41
+ "type": "array",
42
+ "items": {
43
+ "type": "string",
44
+ "minLength": 1
45
+ },
46
+ "minItems": 1,
47
+ "uniqueItems": true
48
+ },
49
+ "docs": {
50
+ "type": "object",
51
+ "additionalProperties": false,
52
+ "properties": {
53
+ "construction": {
54
+ "type": "string",
55
+ "minLength": 1
56
+ },
57
+ "inception": {
58
+ "type": "string",
59
+ "minLength": 1
60
+ }
61
+ }
62
+ }
63
+ }
35
64
  }
36
65
  }
37
66
  },