phasegate 0.160.9 → 0.160.10

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,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.160.10] - 2026-05-16
11
+
12
+ ### Fixed
13
+
14
+ - **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.
15
+
10
16
  ## [0.160.9] - 2026-05-15
11
17
 
12
18
  ### Fixed
@@ -548,12 +548,14 @@ Controls how phasegate's agent-side hooks integrate with Claude Code. Currently
548
548
 
549
549
  | Sub-field | Type | Default | Description |
550
550
  |--------------------|-----------|---------|----------------------------------------------------------------------------------------------------------------------|
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. |
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. 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
552
 
553
553
  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
554
 
555
555
  Reentry-detection cases (`REENTRY_DETECTED`) always exit with code 0 regardless of this setting; strict mode applies only to actual Complete Check failures.
556
556
 
557
+ 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 -->
558
+
557
559
  ---
558
560
 
559
561
  ### 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phasegate",
3
- "version": "0.160.9",
3
+ "version": "0.160.10",
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",
@@ -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',
@@ -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
  }