phasegate 0.120.0 → 0.121.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,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.121.0] - 2026-05-07
11
+
12
+ ### Fixed
13
+
14
+ - **WI-087 finding #3 + WI-086 docs: PreToolUse の Quick Mode 通過時の visibility 改善 + hook 責務分離をドキュメント化** — 外部レポーター nakataj-mti / junpei-9898 が GitHub Issue [#3](https://github.com/junpei-9898/phasegate/issues/3) finding #3 / [#2](https://github.com/junpei-9898/phasegate/issues/2) で報告した「Quick Mode で write が allow されるとき hook が完全 silent で、初見ユーザーが "hook が走っていない" と誤認する」問題と「pre-tool-use が L1 lint をしないことが暗黙仕様化していてレポーター期待と齟齬している」問題を解消。
15
+ - **Quick Mode visibility notice** (`scripts/harness/agent-integration/presentation/pre-tool-use-hook.ts`): `HandlePreToolUseOutput.quickModeAllowed` がセットされた場合、stderr に `phasegate: write allowed (Quick Mode, category=<dominantCategory>)` を出力。exit code は 0 維持で **semantics は不変**、可視性のみ向上。
16
+ - **DTO 拡張** (`scripts/harness/agent-integration/application/dto/handle-pre-tool-use-dto.ts`): `quickModeAllowed?: { dominantCategory?: string }` を追加。`HandlePreToolUseUseCase.execute` 内で `WRITE_TOOLS + fullModeRequirementQueryPort + requiresFullMode=false` の経路で populate。block 経路や WRITE_TOOLS 外 (`Bash` 等) では出力されず、後方互換維持。
17
+ - **テスト追加** (`scripts/harness/__tests__/integration/agent-integration/handle-pre-tool-use-usecase.test.ts`): 4 ケース — `quickModeAllowed` が `dominantCategory` 付きで返る / `dominantCategory` 未設定で返る / `fullModeRequirementQueryPort` 未指定時は出力なし / WRITE_TOOLS 外 (`Bash`) では出力なし。全 49 ケース (前回 45 + 新規 4) グリーン。
18
+ - **責務分離のドキュメント明文化** (`docs/guide/hooks-integration.md`): "Responsibility Separation" セクションを冒頭に追加し、pre = フェーズゲート / post = lint / Stop = complete-check の役割分担表と「pre-tool-use は意図的に lint を実行しない (lint は書き込み後の content が必要)」旨を明記。WI-086 で指摘された「`pre-tool-use` で違反 Write を exit 2 でブロックしてほしい」期待が現行設計と乖離する理由を ユーザー視点で説明。
19
+ - **互換性**: 既存テスト全てグリーン(`expect(actual).toEqual({ shouldBlock: false })` 形式の既存 assert は `quickModeAllowed: undefined` を含む結果でも一致)。`HandlePreToolUseOutput` への field 追加は外部 API ではなく application/presentation 内部 DTO のみで、外部消費者なし。
20
+ - **スコープ外** (Phase C-2 で対応予定): WI-087 finding #4 — Stop hook `--enforce` flag (`agentIntegration.stopHook.enforce` config 追加で Complete Check 失敗時に exit 2 + decision JSON `"deny"` を返す strict mode)。config schema 拡張のため story-implementor で別リリース予定。
21
+
10
22
  ## [0.120.0] - 2026-05-07
11
23
 
12
24
  ### Fixed
@@ -42,9 +42,27 @@ Add the following to `.claude/settings.json`:
42
42
 
43
43
  ## Hook Behaviors
44
44
 
45
+ ### Responsibility Separation (important)
46
+
47
+ Phasegate's hooks are **strictly partitioned by phase**:
48
+
49
+ | Hook | What it checks | What it does NOT check |
50
+ |------|----------------|------------------------|
51
+ | **PreToolUse** | Phase Gate, protected files, Quick Mode category gating, story-reflection | L1 lint (`@unit` / `@layer` / `any` abuse) — these are post-write concerns |
52
+ | **PostToolUse** | L1 Biome AST rules, formatter, type-check (via `analyze-errors-hook.sh`) | Phase Gate (already checked before write) |
53
+ | **Stop** | Complete-check (L2-L4 full validation suite) | Per-edit lint (already done by PostToolUse) |
54
+
55
+ This separation is intentional:
56
+ - **Pre = "should this write happen at all?"** — concerns the caller's intent and design state.
57
+ - **Post = "is what was written valid?"** — concerns the resulting code's quality.
58
+ - **Stop = "is the session ready to end?"** — concerns the cumulative state across the session.
59
+
60
+ If you expect L1 lint (e.g., missing `@unit` annotation) to **block** a Write before it happens, that is by design **not** the case. The PreToolUse hook intentionally does not run lint, because lint requires the resulting file content (which only exists after the write). Lint violations surface as **PostToolUse** decision JSON (`decision: "block"`) and trigger Claude Code to retry.
61
+
45
62
  ### PreToolUse (before file write)
46
63
  - Enforces Phase Gate: blocks writing to source files if required design documents don't exist
47
64
  - Blocks writes to protected files (package.json, biome.json, tsconfig.json)
65
+ - Quick Mode: when a write is allowed because the change is classified within `quickMode.allowedCategories`, an informational notice is emitted to stderr (`phasegate: write allowed (Quick Mode, category=<...>)`) — exit code remains 0
48
66
  - Returns actionable error messages with:
49
67
  - Violation reason
50
68
  - Missing artifacts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phasegate",
3
- "version": "0.120.0",
3
+ "version": "0.121.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",
@@ -19,4 +19,9 @@ export interface HandlePreToolUseOutput {
19
19
  fullModeRejectionRule?: 'MIXED_CHANGES' | 'NEW_DOMAIN' | 'API_CONTRACT';
20
20
  fullModeDominantCategory?: string;
21
21
  nextAction?: string;
22
+ // Quick Mode が write を許可した際に dominant カテゴリを presentation 層に伝える。
23
+ // shouldBlock=false のときのみセットされる。WI-087 finding #3。
24
+ quickModeAllowed?: {
25
+ dominantCategory?: string;
26
+ };
22
27
  }
@@ -134,6 +134,7 @@ export class HandlePreToolUseUseCase {
134
134
  }
135
135
  }
136
136
 
137
+ let quickModeAllowed: HandlePreToolUseOutput["quickModeAllowed"];
137
138
  if (
138
139
  HandlePreToolUseUseCase.WRITE_TOOLS.has(input.toolName) &&
139
140
  this.fullModeRequirementQueryPort !== undefined &&
@@ -157,29 +158,31 @@ export class HandlePreToolUseUseCase {
157
158
  unitIdForGuidance,
158
159
  );
159
160
  }
161
+ } else {
162
+ quickModeAllowed = { dominantCategory: fullModeResult.dominantCategory };
160
163
  }
161
164
  }
162
165
  }
163
166
 
164
167
  const scope = this.resolveStoryReflectionScope(input);
165
168
  if (scope === null || this.storyReflectionQueryPort === undefined) {
166
- return { shouldBlock: false };
169
+ return { shouldBlock: false, quickModeAllowed };
167
170
  }
168
171
 
169
172
  if (grandfather.allGrandfathered) {
170
173
  this.grandfatherLogger("story-reflection", input.targetFilePaths);
171
- return { shouldBlock: false };
174
+ return { shouldBlock: false, quickModeAllowed };
172
175
  }
173
176
 
174
177
  const unitId = scope.unitId;
175
178
  if (unitId === undefined) {
176
- return { shouldBlock: false };
179
+ return { shouldBlock: false, quickModeAllowed };
177
180
  }
178
181
 
179
182
  const reflectionResult = await this.storyReflectionQueryPort.checkReflection(unitId);
180
183
 
181
184
  if (reflectionResult.skipped || reflectionResult.passed) {
182
- return { shouldBlock: false };
185
+ return { shouldBlock: false, quickModeAllowed };
183
186
  }
184
187
 
185
188
  return HandlePreToolUseUseCase.buildStoryReflectionBlockOutput(
@@ -150,6 +150,14 @@ async function main(): Promise<void> {
150
150
  process.exit(2);
151
151
  }
152
152
 
153
+ // Quick Mode が write を許可した場合に visibility を上げる informational notice。
154
+ // exit 0 は維持し semantics は変えない。WI-087 finding #3。
155
+ if (output.quickModeAllowed !== undefined) {
156
+ const cat = output.quickModeAllowed.dominantCategory;
157
+ const suffix = cat !== undefined && cat !== '' ? `, category=${cat}` : '';
158
+ process.stderr.write(`phasegate: write allowed (Quick Mode${suffix})\n`);
159
+ }
160
+
153
161
  process.exit(0);
154
162
  } catch (error) {
155
163
  process.stderr.write(`実行エラー: ${String(error)}\n`);