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 +12 -0
- package/docs/guide/cli-reference.md +1 -1
- package/docs/guide/configuration.md +5 -2
- package/docs/guide/hooks-integration.md +2 -1
- package/docs/guide/recipes.md +2 -1
- package/package.json +1 -1
- package/scripts/harness/agent-integration/application/dto/handle-pre-tool-use-dto.ts +1 -0
- package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +32 -3
- package/scripts/harness/agent-integration/infrastructure/adapters/child-process-cli-executor-adapter.ts +16 -2
- package/scripts/harness/agent-integration/presentation/pre-tool-use-hook.ts +19 -6
- package/scripts/harness/agent-integration/presentation/stop-hook.ts +17 -2
- package/scripts/harness/main.ts +17 -5
- package/scripts/harness/quick-mode/domain/services/quick-mode-judgment-engine.ts +10 -9
- package/scripts/harness/setup/skill-deployer.ts +1 -1
- package/skills/phasegate-config-doctor/SKILL.md +9 -2
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
|
|
package/docs/guide/recipes.md
CHANGED
|
@@ -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
package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts
CHANGED
|
@@ -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
|
|
243
|
-
const applyCommand = "phasegate config:plan --intent
|
|
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}\
|
|
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/
|
|
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 =
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
-
|
|
118
|
+
`${reason} — strict mode により turn を block します\n`,
|
|
104
119
|
);
|
|
105
120
|
process.exit(2);
|
|
106
121
|
}
|
package/scripts/harness/main.ts
CHANGED
|
@@ -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: ["
|
|
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: ["
|
|
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');
|
|
@@ -13,7 +13,7 @@ phasegate を導入した直後の config は単純な default で、実プロ
|
|
|
13
13
|
|
|
14
14
|
## 設計原則
|
|
15
15
|
|
|
16
|
-
1. **silent 書き換え禁止** — 提案は diff として提示し、`AskUserQuestion`
|
|
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
|
-
|
|
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
|
|