phasegate 0.160.9 → 0.160.11

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.160.11] - 2026-05-17
11
+
12
+ ### Fixed
13
+
14
+ - **WI-202 / WI-204 — Quick Mode strict recovery** — aligns strict init Quick Mode categories with quick-implementor scope, adds `config:plan --intent quick-mode-relax` for managed `allowedCategories` recovery, updates config edit block guidance to the matching recovery intent, and excludes CWD-external runtime paths from project-local hook policy.
15
+
16
+ ## [0.160.10] - 2026-05-16
17
+
18
+ ### Fixed
19
+
20
+ - **WI-203 — Stop hook Complete Check execution** — resolves built-in `phasegate:*` hook commands through the packaged CLI entrypoint instead of unmanaged downstream wrapper files, distinguishes Complete Check validation failures from command wiring failures in strict Stop hook output, and documents that `scripts/harness/cli/complete-check.ts` is not required by standard install/reconcile.
21
+
10
22
  ## [0.160.9] - 2026-05-15
11
23
 
12
24
  ### Fixed
@@ -28,7 +28,7 @@ Command names in this document are split into three surfaces:
28
28
  | `uninstall --dry-run` / `--apply` | Remove PhaseGate-managed files and managed blocks using `.phasegate/manifest.json`; `--force` handles managed conflict cases. |
29
29
  | `reconcile --dry-run` / `--apply` | Update PhaseGate-managed files to current package templates and refresh manifest hashes; `--force` allows managed-file replacement with backup. |
30
30
  | `setup:agent` | Agent-readable setup planner and optional apply path. Options: `--intent <minimal\|recommended\|strict\|ci-only\|agent-hooks\|retrofit>`, `--agent <claude\|codex\|both>`, `--workflow <standard\|strict>`, `--with-husky`, `--with-ci`, `--dry-run`, `--apply`, `--json`. <!-- @work-item-id WI-172 --> |
31
- | `config:plan` | Agent-readable configuration change planner. Options: `--intent <l4-strict\|codex-hooks\|ci-fail-on-warning\|baseline-reset\|quick-mode-strict\|retrofit-bootstrap\|planning-mode-relax>`, `--dry-run`, `--apply`, `--json`. <!-- @work-item-id WI-173 --> <!-- @work-item-id WI-201 --> |
31
+ | `config:plan` | Agent-readable configuration change planner. Options: `--intent <l4-strict\|codex-hooks\|ci-fail-on-warning\|baseline-reset\|quick-mode-strict\|quick-mode-relax\|retrofit-bootstrap\|planning-mode-relax>`, `--dry-run`, `--apply`, `--json`. <!-- @work-item-id WI-173 --> <!-- @work-item-id WI-201 --> <!-- @work-item-id WI-204 --> |
32
32
  | `update-skills` | Compatibility alias for `reconcile`; use `reconcile` for new automation. |
33
33
  | `scaffold-wi <unit> <type>` | Create `docs/inception/{unit}/WI-XXX/description.md` using the next free WI number. |
34
34
  | `emit-agent-rules` | Print the AGENTS.md / CLAUDE.md WI workflow rules block. |
@@ -13,9 +13,10 @@ For agent-assisted changes, preview the intent before editing the config:
13
13
  ```bash
14
14
  npx phasegate config:plan --intent l4-strict --dry-run --json
15
15
  npx phasegate config:plan --intent quick-mode-strict --dry-run --json
16
+ npx phasegate config:plan --intent quick-mode-relax --dry-run --json
16
17
  ```
17
18
 
18
- The plan identifies target fields, managed artifacts, commands, validation, risks, and rollback so an agent can explain the change before applying it. <!-- @work-item-id WI-173 -->
19
+ The plan identifies target fields, managed artifacts, commands, validation, risks, and rollback so an agent can explain the change before applying it. Use `quick-mode-relax` to restore the supported Quick Mode category set after an over-narrowed strict configuration. <!-- @work-item-id WI-173 --> <!-- @work-item-id WI-204 -->
19
20
 
20
21
  ### Full Reference
21
22
 
@@ -548,12 +549,14 @@ Controls how phasegate's agent-side hooks integrate with Claude Code. Currently
548
549
 
549
550
  | Sub-field | Type | Default | Description |
550
551
  |--------------------|-----------|---------|----------------------------------------------------------------------------------------------------------------------|
551
- | `stopHook.enforce` | `boolean` | `false` | When `true`, a non-zero exit from `phasegate:complete-check` causes the Stop hook to emit `{"decision":"block","reason":"Complete Check failed (exitCode=N)"}` on stdout and exit with code 2, blocking Claude Code's turn. When `false` (default), the hook exits with the inner CLI's exit code, which Claude Code surfaces only as a transcript warning. |
552
+ | `stopHook.enforce` | `boolean` | `false` | When `true`, a non-zero exit from `phasegate:complete-check` causes the Stop hook to emit `{"decision":"block","reason":"Complete Check failed (exitCode=N)"}` on stdout and exit with code 2, blocking Claude Code's turn. If command invocation itself fails, the reason is `Complete Check execution failed (exitCode=N)`. When `false` (default), the hook exits with the inner CLI's exit code, which Claude Code surfaces only as a transcript warning. |
552
553
 
553
554
  Use `enforce: true` when your team treats Complete Check failures as hard gates (e.g., disallow ending a session with failing tests or lint). Leave it as default `false` for an opt-in / advisory experience.
554
555
 
555
556
  Reentry-detection cases (`REENTRY_DETECTED`) always exit with code 0 regardless of this setting; strict mode applies only to actual Complete Check failures.
556
557
 
558
+ The built-in Stop hook invokes the packaged PhaseGate CLI command directly; projects initialized or reconciled by PhaseGate do not need a local `scripts/harness/cli/complete-check.ts` wrapper for this setting. <!-- @work-item-id WI-203 -->
559
+
557
560
  ---
558
561
 
559
562
  ### Quick Mode
@@ -93,8 +93,9 @@ Use /quick-implementor skill for version changes in package.json.
93
93
 
94
94
  ### Stop (before session end)
95
95
  - Runs `phasegate:complete-check` (L2-L4 full validation)
96
+ - The built-in Stop hook runs the packaged PhaseGate CLI command; downstream projects do not need to provide `scripts/harness/cli/complete-check.ts`.
96
97
  - By default, the hook exits with the inner CLI's exit code, which Claude Code shows as a transcript warning but does not turn-block on.
97
- - Set `agentIntegration.stopHook.enforce: true` in `phasegate.config.json` to enable **strict mode**: on Complete Check failure, the hook emits `{"decision":"block","reason":"Complete Check failed (exitCode=N)"}` on stdout and exits with code 2, hard-blocking Claude Code's turn end. Reentry-detection still exits 0 regardless of this setting. See `docs/guide/configuration.md` `agentIntegration` section for details.
98
+ - Set `agentIntegration.stopHook.enforce: true` in `phasegate.config.json` to enable **strict mode**: on Complete Check failure, the hook emits `{"decision":"block","reason":"Complete Check failed (exitCode=N)"}` on stdout and exits with code 2, hard-blocking Claude Code's turn end. If the command cannot be invoked at all, strict mode reports `Complete Check execution failed (exitCode=N)` instead. Reentry-detection still exits 0 regardless of this setting. See `docs/guide/configuration.md` `agentIntegration` section for details. <!-- @work-item-id WI-203 -->
98
99
 
99
100
  ## Git hook metadata validation
100
101
 
@@ -58,8 +58,9 @@ Only enable fail-on-warning after reviewing the drift, consistency, dead-code, p
58
58
 
59
59
  ```bash
60
60
  npx phasegate config:plan --intent quick-mode-strict --dry-run --json
61
+ npx phasegate config:plan --intent quick-mode-relax --dry-run --json
61
62
  npx phasegate check-change-category --paths <changed-files> --format json
62
63
  npx phasegate ci-check --quick --dry-run
63
64
  ```
64
65
 
65
- Use `config:plan` before changing `phasegate.config.json`. It separates repo-managed artifacts from user-level settings, previews the local config before/after for config-backed intents, and lists the checks needed after the change. <!-- @work-item-id WI-175 -->
66
+ Use `config:plan` before changing `phasegate.config.json`. It separates repo-managed artifacts from user-level settings, previews the local config before/after for config-backed intents, and lists the checks needed after the change. `quick-mode-relax` is the managed recovery path when strict category narrowing blocks docs/test/config changes. <!-- @work-item-id WI-175 --> <!-- @work-item-id WI-204 -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phasegate",
3
- "version": "0.160.9",
3
+ "version": "0.160.11",
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",
@@ -6,6 +6,7 @@
6
6
  export interface HandlePreToolUseInput {
7
7
  toolName: string;
8
8
  targetFilePaths: string[];
9
+ callerSkill?: string;
9
10
  targetChanges?: {
10
11
  filePath: string;
11
12
  beforeContent?: string | null;
@@ -160,6 +160,7 @@ export class HandlePreToolUseUseCase {
160
160
  fullModeResult,
161
161
  guidance,
162
162
  unitIdForGuidance,
163
+ input.callerSkill,
163
164
  );
164
165
  }
165
166
  } else {
@@ -236,11 +237,12 @@ export class HandlePreToolUseUseCase {
236
237
  },
237
238
  guidance: ErrorGuidance | null,
238
239
  unitId: string | undefined,
240
+ callerSkill?: string,
239
241
  ): HandlePreToolUseOutput {
240
242
  const fp = blockedFilePath ?? "不明なファイル";
241
243
  if (result.dominantCategory === "config" && /(?:^|\/)phasegate\.config\.json$/.test(fp)) {
242
- const dryRunCommand = "phasegate config:plan --intent retrofit-bootstrap --dry-run --json";
243
- const applyCommand = "phasegate config:plan --intent retrofit-bootstrap --apply --json";
244
+ const dryRunCommand = "phasegate config:plan --intent quick-mode-relax --dry-run --json";
245
+ const applyCommand = "phasegate config:plan --intent quick-mode-relax --apply --json";
244
246
  const lines = [
245
247
  `Full mode 必須変更が検出されました: ${fp}`,
246
248
  "カテゴリ: config",
@@ -263,6 +265,33 @@ export class HandlePreToolUseUseCase {
263
265
  nextAction: `${dryRunCommand} && ${applyCommand}`,
264
266
  };
265
267
  }
268
+ if (
269
+ callerSkill === "quick-implementor" &&
270
+ result.dominantCategory !== undefined &&
271
+ ["bugfix", "docs", "test", "config"].includes(result.dominantCategory)
272
+ ) {
273
+ const dryRunCommand = "phasegate config:plan --intent quick-mode-relax --dry-run --json";
274
+ const applyCommand = "phasegate config:plan --intent quick-mode-relax --apply --json";
275
+ const lines: string[] = [`Full mode 必須変更が検出されました: ${fp}`];
276
+ lines.push(`カテゴリ: ${result.dominantCategory}`);
277
+ if (result.rejectionRule) {
278
+ lines.push(`判定ルール: ${result.rejectionRule}`);
279
+ }
280
+ if (result.rejectionReason) {
281
+ lines.push(`理由: ${result.rejectionReason}`);
282
+ }
283
+ lines.push(`次のアクション: Quick Mode の許可カテゴリを確認してください。緩和する場合は ${dryRunCommand} で差分を確認し、承認後に ${applyCommand} を実行してください。`);
284
+
285
+ return {
286
+ shouldBlock: true,
287
+ blockedFilePath,
288
+ blockReason: "FULL_MODE_REQUIRED",
289
+ error: { message: lines.join("\n") },
290
+ fullModeRejectionRule: result.rejectionRule,
291
+ fullModeDominantCategory: result.dominantCategory,
292
+ nextAction: `${dryRunCommand} && ${applyCommand}`,
293
+ };
294
+ }
266
295
  const lines: string[] = [`Full mode 必須変更が検出されました: ${fp}`];
267
296
  if (result.dominantCategory) {
268
297
  lines.push(`カテゴリ: ${result.dominantCategory}`);
@@ -402,7 +431,7 @@ export class HandlePreToolUseUseCase {
402
431
  {
403
432
  pattern: /(?:^|\/)phasegate\.config\.json$/,
404
433
  message: (fp) =>
405
- `保護ファイルへの書き込みがブロックされました: ${fp}\n設定変更は CLI 経由で計画・適用してください: phasegate config:plan --intent retrofit-bootstrap --dry-run --json / phasegate config:plan --intent retrofit-bootstrap --apply --json`,
434
+ `保護ファイルへの書き込みがブロックされました: ${fp}\nQuick Mode 設定の復旧は CLI 経由で計画・適用してください: phasegate config:plan --intent quick-mode-relax --dry-run --json / phasegate config:plan --intent quick-mode-relax --apply --json`,
406
435
  },
407
436
  {
408
437
  pattern: /(?:^|\/)harness\.config\.json$/,
@@ -1,22 +1,36 @@
1
1
  /**
2
2
  * @layer infrastructure
3
3
  * @unit agent-integration
4
+ * @work-item-id WI-203
4
5
  *
5
6
  * ChildProcessCliExecutorAdapter
6
7
  * CliExecutorPort の実装。子プロセスで CLI コマンドを実行する
7
8
  */
8
9
 
9
10
  import { spawn } from 'node:child_process';
11
+ import { dirname, resolve } from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
10
13
  import type { CliExecutorPort, CliExecutionResult } from '../../application/ports/cli-executor-port.js';
11
14
  import { TimeoutError } from '../../application/ports/cli-executor-port.js';
12
15
 
16
+ function getHarnessMainPath(): string {
17
+ return resolve(dirname(fileURLToPath(import.meta.url)), '../../../main.ts');
18
+ }
19
+
13
20
  /**
14
21
  * CommandName を実行可能なコマンドに変換する
15
- * 例: 'phasegate:lint' → ['npx', 'tsx', 'scripts/harness/cli/lint.ts']
22
+ * 例: 'phasegate:lint' → ['npx', 'tsx', '<package>/scripts/harness/main.ts', 'phasegate:lint']
16
23
  * テスト時は直接スクリプトパスで execute を呼ぶことも可能
17
24
  */
18
25
  function resolveCommand(commandName: string): { cmd: string; args: string[] } {
19
- // コマンド名をファイルパスに変換
26
+ if (commandName.startsWith('phasegate:')) {
27
+ return {
28
+ cmd: 'npx',
29
+ args: ['tsx', getHarnessMainPath(), commandName],
30
+ };
31
+ }
32
+
33
+ // Legacy extension commands may still be provided as project-local wrappers.
20
34
  const slug = commandName.replace('phasegate:', '');
21
35
  return {
22
36
  cmd: 'npx',
@@ -22,6 +22,7 @@ import * as fs from 'node:fs/promises';
22
22
  interface PreToolUseHookInput {
23
23
  cwd?: string;
24
24
  tool_name?: string;
25
+ caller_skill?: string;
25
26
  tool_input?: {
26
27
  path?: string;
27
28
  file_path?: string;
@@ -50,8 +51,8 @@ async function readStdin(): Promise<string> {
50
51
  return Buffer.concat(chunks).toString('utf8');
51
52
  }
52
53
 
53
- async function findConfigPath(): Promise<string> {
54
- let dir = process.cwd();
54
+ async function findConfigPath(startDir: string): Promise<string> {
55
+ let dir = startDir;
55
56
  while (true) {
56
57
  const candidate = path.join(dir, 'phasegate.config.json');
57
58
  try {
@@ -63,7 +64,11 @@ async function findConfigPath(): Promise<string> {
63
64
  dir = parent;
64
65
  }
65
66
  }
66
- return path.join(process.cwd(), 'phasegate.config.json');
67
+ return path.join(startDir, 'phasegate.config.json');
68
+ }
69
+
70
+ function isProjectExternalAbsolutePath(filePath: string): boolean {
71
+ return path.isAbsolute(filePath);
67
72
  }
68
73
 
69
74
  async function main(): Promise<void> {
@@ -89,7 +94,7 @@ async function main(): Promise<void> {
89
94
  process.exit(2);
90
95
  }
91
96
 
92
- const cwd = input.cwd ?? process.cwd();
97
+ const cwd = path.resolve(input.cwd ?? process.cwd());
93
98
  const toRelative = (p: string): string => {
94
99
  if (path.isAbsolute(p)) {
95
100
  const rel = path.relative(cwd, p);
@@ -128,7 +133,9 @@ async function main(): Promise<void> {
128
133
  }
129
134
 
130
135
  try {
131
- const configPath = await findConfigPath();
136
+ const configPath = await findConfigPath(cwd);
137
+ const projectTargetFilePaths = targetFilePaths.filter((filePath) => !isProjectExternalAbsolutePath(filePath));
138
+ const projectTargetChanges = targetChanges.filter((change) => !isProjectExternalAbsolutePath(change.filePath));
132
139
  const configQueryPort = new HarnessConfigConfigQueryAdapter(configPath);
133
140
  const phaseGateQueryPort = new PhaseGateQueryAdapter();
134
141
  const storyReflectionQueryPort = new FileSystemStoryReflectionQueryAdapter({
@@ -154,7 +161,13 @@ async function main(): Promise<void> {
154
161
  errorGuidanceQueryPort,
155
162
  });
156
163
 
157
- const output = await useCase.execute({ toolName: effectiveToolName, targetFilePaths, targetChanges });
164
+ const callerSkill = input.caller_skill ?? process.env.PHASEGATE_CALLER_SKILL;
165
+ const output = await useCase.execute({
166
+ toolName: effectiveToolName,
167
+ targetFilePaths: projectTargetFilePaths,
168
+ callerSkill,
169
+ targetChanges: projectTargetChanges,
170
+ });
158
171
 
159
172
  if (output.shouldBlock) {
160
173
  const msg = output.error?.message
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @layer presentation
3
3
  * @unit agent-integration
4
+ * @work-item-id WI-203
4
5
  *
5
6
  * Stop Hook Adapter
6
7
  * Claude Code の Stop Hook エントリポイント
@@ -19,6 +20,20 @@ interface StopHookInput {
19
20
  session_id?: string;
20
21
  }
21
22
 
23
+ function isCompleteCheckExecutionWiringFailure(stderr: string): boolean {
24
+ return (
25
+ /scripts\/harness\/cli\/complete-check\.ts/.test(stderr) ||
26
+ /ERR_MODULE_NOT_FOUND|Cannot find module/i.test(stderr)
27
+ );
28
+ }
29
+
30
+ function formatCompleteCheckFailureReason(exitCode: number, stderr: string): string {
31
+ if (isCompleteCheckExecutionWiringFailure(stderr)) {
32
+ return `Complete Check execution failed (exitCode=${exitCode})`;
33
+ }
34
+ return `Complete Check failed (exitCode=${exitCode})`;
35
+ }
36
+
22
37
  async function readStdin(): Promise<string> {
23
38
  const chunks: Buffer[] = [];
24
39
  for await (const chunk of process.stdin) {
@@ -97,10 +112,10 @@ async function main(): Promise<void> {
97
112
  if (output.cliResult.exitCode !== 0) {
98
113
  // WI-087 finding #4: enforce=true なら exit 2 + decision JSON で turn block
99
114
  if (output.shouldEnforceFailure === true) {
100
- const reason = `Complete Check failed (exitCode=${output.cliResult.exitCode})`;
115
+ const reason = formatCompleteCheckFailureReason(output.cliResult.exitCode, output.cliResult.stderr);
101
116
  process.stdout.write(`${JSON.stringify({ decision: 'block', reason })}\n`);
102
117
  process.stderr.write(
103
- `Complete Check失敗 (exitCode=${output.cliResult.exitCode}) — strict mode により turn を block します\n`,
118
+ `${reason} — strict mode により turn を block します\n`,
104
119
  );
105
120
  process.exit(2);
106
121
  }
@@ -219,7 +219,7 @@ Gate semantics:
219
219
  refresh-claude-md Refresh CLAUDE.md standard sections (--dry-run, --apply, --json)
220
220
  p2:check-agent-context Check AGENTS.md / CLAUDE.md freshness (--threshold-days <n>, --json)
221
221
  setup:agent Plan agent-driven setup (--intent <minimal|recommended|strict|ci-only|agent-hooks|retrofit>, --agent <claude|codex|both>, --dry-run|--apply, --json)
222
- config:plan Plan safe config changes (--intent <l4-strict|codex-hooks|ci-fail-on-warning|baseline-reset|quick-mode-strict|retrofit-bootstrap|planning-mode-relax>, --dry-run|--apply, --json)
222
+ config:plan Plan safe config changes (--intent <l4-strict|codex-hooks|ci-fail-on-warning|baseline-reset|quick-mode-strict|quick-mode-relax|retrofit-bootstrap|planning-mode-relax>, --dry-run|--apply, --json)
223
223
  ci:check-repetition Check error repetition (--code <errorCode>, --reset, --json)
224
224
  baseline Create retrofit baseline snapshot (--dry-run, --force, --paths <glob,glob,...>, --json)
225
225
  scaffold-design Scaffold a design doc (--unit <id>, --phase <logical|domain|uiux|unit-test|it-test>, --dry-run|--apply, --force, --json)
@@ -536,7 +536,7 @@ Options:
536
536
  Produce an agent-readable configuration change plan.
537
537
 
538
538
  Intents:
539
- l4-strict, codex-hooks, ci-fail-on-warning, baseline-reset, quick-mode-strict, retrofit-bootstrap, planning-mode-relax
539
+ l4-strict, codex-hooks, ci-fail-on-warning, baseline-reset, quick-mode-strict, quick-mode-relax, retrofit-bootstrap, planning-mode-relax
540
540
 
541
541
  Options:
542
542
  --dry-run
@@ -779,7 +779,7 @@ function parseCoverageThreshold(raw: string | undefined): number {
779
779
  type InitPhasePreset = "full" | "standard" | "minimal" | "custom";
780
780
  type AgentTarget = "claude" | "codex" | "both";
781
781
  type SetupIntent = "minimal" | "recommended" | "strict" | "ci-only" | "agent-hooks" | "retrofit";
782
- type ConfigChangeIntent = "l4-strict" | "codex-hooks" | "ci-fail-on-warning" | "baseline-reset" | "quick-mode-strict" | "retrofit-bootstrap" | "planning-mode-relax";
782
+ type ConfigChangeIntent = "l4-strict" | "codex-hooks" | "ci-fail-on-warning" | "baseline-reset" | "quick-mode-strict" | "quick-mode-relax" | "retrofit-bootstrap" | "planning-mode-relax";
783
783
  type SetupCompletenessStatus = "configured" | "planned" | "manual" | "not-applicable" | "unknown";
784
784
 
785
785
  interface SetupCompletenessEntry {
@@ -864,6 +864,7 @@ function parseConfigChangeIntent(value: string | undefined): ConfigChangeIntent
864
864
  value === "ci-fail-on-warning" ||
865
865
  value === "baseline-reset" ||
866
866
  value === "quick-mode-strict" ||
867
+ value === "quick-mode-relax" ||
867
868
  value === "retrofit-bootstrap" ||
868
869
  value === "planning-mode-relax"
869
870
  ) {
@@ -1229,9 +1230,12 @@ function buildConfigPatchPreview(intent: ConfigChangeIntent, before: unknown | n
1229
1230
  { pointer: "/layers/L4/failOnWarning", path: ["layers", "L4", "failOnWarning"], value: true },
1230
1231
  ],
1231
1232
  "quick-mode-strict": [
1232
- { pointer: "/quickMode/allowedCategories", path: ["quickMode", "allowedCategories"], value: ["chore"] },
1233
+ { pointer: "/quickMode/allowedCategories", path: ["quickMode", "allowedCategories"], value: ["bugfix"] },
1233
1234
  { pointer: "/quickMode/relaxedGates", path: ["quickMode", "relaxedGates"], value: [] },
1234
1235
  ],
1236
+ "quick-mode-relax": [
1237
+ { pointer: "/quickMode/allowedCategories", path: ["quickMode", "allowedCategories"], value: ["bugfix", "docs", "test", "config"] },
1238
+ ],
1235
1239
  "codex-hooks": [],
1236
1240
  "baseline-reset": [],
1237
1241
  "retrofit-bootstrap": [
@@ -1363,6 +1367,14 @@ async function buildConfigChangePlan(rootDir: string, intent: ConfigChangeIntent
1363
1367
  validations: ["phasegate ci-check --quick --dry-run", "phasegate phasegate:check-ready"],
1364
1368
  risks: ["More changes will require Full Mode validation before commit."],
1365
1369
  },
1370
+ "quick-mode-relax": {
1371
+ targets: ["phasegate.config.json: quickMode.allowedCategories"],
1372
+ managedTargets: ["phasegate.config.json"],
1373
+ externalActions: [],
1374
+ commands: ["phasegate config:plan --intent quick-mode-relax --json", "phasegate config:plan --intent quick-mode-relax --apply --json", "phasegate check-change-category --paths <changed-files> --format json"],
1375
+ validations: ["phasegate ci-check --quick --dry-run", "phasegate phasegate:check-ready"],
1376
+ risks: ["Small bugfix/docs/test/config changes can proceed through Quick Mode again; protected files remain governed by hook and managed command policies."],
1377
+ },
1366
1378
  "retrofit-bootstrap": {
1367
1379
  targets: ["phasegate.config.json: planningMode.default", "phasegate.config.json: phaseDependencies.override", "phasegate.config.json: quickMode.relaxedGates"],
1368
1380
  managedTargets: ["phasegate.config.json"],
@@ -1722,7 +1734,7 @@ async function main(): Promise<void> {
1722
1734
  if (configResult.created) {
1723
1735
  console.log(`✓ phasegate.config.json created`);
1724
1736
  if (workflow === "strict") {
1725
- console.log(`✓ strict workflow configured (quickMode.relaxedGates: [], allowedCategories: ["chore"])`);
1737
+ console.log(`✓ strict workflow configured (quickMode.relaxedGates: [], allowedCategories: ["bugfix","docs","test","config"])`);
1726
1738
  }
1727
1739
  } else {
1728
1740
  console.log(` phasegate.config.json already exists, skipped`);
@@ -26,6 +26,16 @@ const RISK_PRIORITY: Record<string, number> = {
26
26
  function categorizeFile(file: ChangedFile): ChangeCategory {
27
27
  const { filePath, changeKind } = file;
28
28
 
29
+ // Config files must stay config even when Edit payload snippets look like
30
+ // comment/whitespace-only diffs. Config recovery guidance depends on this.
31
+ if (
32
+ filePath.endsWith('.config.json') ||
33
+ filePath.endsWith('.config.ts') ||
34
+ filePath.endsWith('phasegate.config.json')
35
+ ) {
36
+ return ChangeCategory.fromString('config');
37
+ }
38
+
29
39
  if (isCommentOnlyDiff(file)) {
30
40
  return ChangeCategory.fromString('docs');
31
41
  }
@@ -54,15 +64,6 @@ function categorizeFile(file: ChangedFile): ChangeCategory {
54
64
  return ChangeCategory.fromString('docs');
55
65
  }
56
66
 
57
- // config: *.config.json / *.config.ts / phasegate.config.json
58
- if (
59
- filePath.endsWith('.config.json') ||
60
- filePath.endsWith('.config.ts') ||
61
- filePath.endsWith('phasegate.config.json')
62
- ) {
63
- return ChangeCategory.fromString('config');
64
- }
65
-
66
67
  // feature: domain/ 以外の CREATE
67
68
  if (changeKind === 'CREATE') {
68
69
  return ChangeCategory.fromString('feature');
@@ -481,7 +481,7 @@ export async function initHarnessConfig(
481
481
  layers: {},
482
482
  quickMode: strictWorkflow
483
483
  ? {
484
- allowedCategories: ["chore"],
484
+ allowedCategories: ["bugfix", "docs", "test", "config"],
485
485
  relaxedGates: [],
486
486
  }
487
487
  : {},
@@ -13,7 +13,7 @@ phasegate を導入した直後の config は単純な default で、実プロ
13
13
 
14
14
  ## 設計原則
15
15
 
16
- 1. **silent 書き換え禁止** — 提案は diff として提示し、`AskUserQuestion` で承認を取ってから Edit
16
+ 1. **silent 書き換え禁止** — 提案は diff として提示し、`AskUserQuestion` で承認を取ってから適用する。`config:plan` で表現できる変更は managed command を優先し、直接 `Edit` は hook が許可する範囲に限定する
17
17
  2. **検出結果を優先** — 機械的に決定可能な部分 (workspace 構造、formatter、bash 互換性) は AI 推論ではなく検出結果を採用
18
18
  3. **AI 推論は判断要素のみ** — architecture preset 選定、relaxedGates 推奨値などは AI が判断するが根拠を必ず示す
19
19
  4. **schema は enum 違反確認時のみ Read** — 日常診断は本 SKILL 内の判定基準で十分。schema 全文 Read は値域不明時に限定する
@@ -200,7 +200,14 @@ options:
200
200
  - label: "適用しない (情報のみ受け取る)"
201
201
  ```
202
202
 
203
- ユーザーが適用対象を確定したら `Edit` `phasegate.config.json` を変更。
203
+ ユーザーが適用対象を確定したら、対応する managed intent がある場合は `config:plan` で適用する。
204
+
205
+ ```bash
206
+ npx phasegate config:plan --intent quick-mode-relax --dry-run --json
207
+ npx phasegate config:plan --intent quick-mode-relax --apply --json
208
+ ```
209
+
210
+ managed intent がない個別提案のみ、hook が `config` category を許可していることを確認したうえで `Edit` で `phasegate.config.json` を変更する。
204
211
 
205
212
  **変更後の検証**:
206
213