phasegate 0.148.0 → 0.150.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.
Files changed (28) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.ja.md +6 -5
  3. package/README.md +6 -5
  4. package/docs/guide/cli-reference.md +2 -2
  5. package/docs/guide/installation.md +1 -1
  6. package/docs/guide/layer-model.md +11 -2
  7. package/docs/guide/quick-vs-full-mode.md +1 -1
  8. package/docs/guide/retrofit-adoption.md +1 -1
  9. package/docs/guide/skills-overview.md +8 -1
  10. package/docs/templates/ci/consistency-check.yml +6 -6
  11. package/package.json +1 -1
  12. package/scripts/harness/agent-integration/presentation/hook-skip-event-recorder.ts +31 -0
  13. package/scripts/harness/agent-integration/presentation/post-tool-use-hook.ts +7 -0
  14. package/scripts/harness/agent-integration/presentation/stop-hook.ts +7 -0
  15. package/scripts/harness/ci-governance/domain/ports/validator-id-registry-port.ts +1 -0
  16. package/scripts/harness/ci-governance/domain/services/template-generator.ts +2 -1
  17. package/scripts/harness/ci-governance/infrastructure/adapters/validator-id-registry-adapter.ts +31 -3
  18. package/scripts/harness/harness-api/domain/services/command-dispatch-service.ts +49 -2
  19. package/scripts/harness/harness-api/domain/services/status-derivation-service.ts +8 -1
  20. package/scripts/harness/harness-api/domain/value-objects/drift-report-summary.ts +1 -0
  21. package/scripts/harness/harness-api/domain/value-objects/harness-status-summary.ts +43 -0
  22. package/scripts/harness/harness-api/domain/value-objects/layer-health.ts +1 -0
  23. package/scripts/harness/harness-api/infrastructure/adapters/harness-config-query-adapter.ts +91 -1
  24. package/scripts/harness/main.ts +1 -1
  25. package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.ts +1 -0
  26. package/scripts/harness/validator-system/composition-root.ts +1 -1
  27. package/scripts/harness/validator-system/domain/value-objects/validator-id.ts +1 -0
  28. package/scripts/harness/validator-system/presentation/handlers/run-validators-handler.ts +1 -0
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.150.0] - 2026-05-12
11
+
12
+ ### Added
13
+
14
+ - **G1 / WI-123 / WI-124 / WI-127 / WI-128 — Gate reliability and operational transparency** — `phasegate:status --json` now reports hook/baseline operational health, CI template validator IDs are derived from the live validator-system registry, L4 scheduled audit docs/templates use canonical L4 commands, and README/guide feature inventory matches the shipped 30 skills.
15
+
10
16
  ## [0.147.0] - 2026-05-12
11
17
 
12
18
  ### Added
package/README.ja.md CHANGED
@@ -80,7 +80,7 @@ claude
80
80
  `init` は初期 bootstrap として以下を生成します:
81
81
 
82
82
  - `phasegate.config.json` — 品質設定の Single Source of Truth
83
- - `skills/` — 28 の AIDLC スキル一式
83
+ - `skills/` — 30 の AIDLC スキル一式
84
84
  - `.claude/skills/` ・ `.codex/skills/` — agent 向けの skill symlink
85
85
  - `.claude/settings.json` — PreToolUse / PostToolUse / Stop hook
86
86
  - `.codex/hooks.json` — Codex CLI hooks 設定(`--agent codex|both` 時)
@@ -152,7 +152,7 @@ npx phasegate reconcile --apply
152
152
  |---|---|
153
153
  | **フェーズゲート** | 設計文書がないと実装ファイルへの Write/Edit/Bash をブロック。AIDLC 準拠 / カスタム gate の両方をサポート |
154
154
  | **5 層バリデーション (L0-L4)** | エディタ保存 → pre-commit → CI → 週次まで段階的に品質チェック |
155
- | **28 AIDLC スキル** | 要求定義 → ドメイン設計 → テスト設計 → TDD 実装をスキルとして提供 |
155
+ | **30 AIDLC スキル** | 要求定義 → ドメイン設計 → テスト設計 → TDD 実装をスキルとして提供 |
156
156
  | **Quick Mode** | バグ修正・docs・テスト追加など軽微変更ではゲートを緩和して高速化 |
157
157
  | **Claude Code / Codex Hooks** | Write/Edit/Bash 時に自動でゲートチェック・lint を実行 |
158
158
  | **HarnessError 形式** | 全エラーに ADR 参照 + 修正例が含まれ、AI が自己修正できる |
@@ -198,13 +198,13 @@ npx phasegate reconcile --apply
198
198
 
199
199
  ---
200
200
 
201
- ## 28 AIDLC スキル
201
+ ## 30 AIDLC スキル
202
202
 
203
203
  AIDLC (AI-Driven Development Life Cycle) は **要求定義 → 設計 → テスト設計 → TDD 実装** の順序を強制するプロセスです。各スキルは前のレベルの成果物を入力にします。
204
204
 
205
205
  **最初の一歩**: Claude Code / Codex 内で `/product-architect` を実行。
206
206
 
207
- ### 5 グループ(28 スキル)
207
+ ### 6 グループ(30 スキル)
208
208
 
209
209
  | グループ | スキル |
210
210
  |---|---|
@@ -213,6 +213,7 @@ AIDLC (AI-Driven Development Life Cycle) は **要求定義 → 設計 → テ
213
213
  | **Test Engineering (7)** | `/unit-test-designer` `/it-test-designer` `/scenario-test-designer` `/unit-test-logic-designer` `/it-test-logic-designer` `/scenario-test-logic-designer` `/test-coverage-checker` |
214
214
  | **Implementation (4)** | `/story-implementor` `/quick-implementor` `/implementation-planner` `/implementation-readiness-checker` |
215
215
  | **Verification (8)** | `/consistency-checker` `/cascade-updater` `/codex-delegator` `/codebase-mapper` `/doc-freshness-checker` `/pointer-validator` `/engineering-perspective` `/skill-creator` |
216
+ | **Operations (2)** | `/phasegate-config-doctor` `/phasegate-toolkit-guide` |
216
217
 
217
218
  各スキルの詳細・成果物・前提条件: [Skills Overview](docs/guide/skills-overview.md)
218
219
 
@@ -506,7 +507,7 @@ L3 Nyquist Validation の `requirement-test-matrix.json` 自動生成はまだ
506
507
  - [Installation](docs/guide/installation.md) — 詳細インストール手順
507
508
  - [Configuration](docs/guide/configuration.md) — `phasegate.config.json` 完全リファレンス
508
509
  - [CLI Reference](docs/guide/cli-reference.md) — 全 CLI コマンド・オプション
509
- - [Skills Overview](docs/guide/skills-overview.md) — 28 スキルの実行順序と成果物
510
+ - [Skills Overview](docs/guide/skills-overview.md) — 30 スキルの実行順序と成果物
510
511
  - [5-Layer Defense Model](docs/guide/layer-model.md) — L0-L4 詳細・HarnessError 形式
511
512
  - [Hooks Integration](docs/guide/hooks-integration.md) — Claude Code Hooks 設定
512
513
  - [Codex Integration](docs/guide/codex-integration.md) — Codex CLI セットアップ・カバレッジ
package/README.md CHANGED
@@ -81,7 +81,7 @@ claude
81
81
  `init` creates the initial project-local harness files:
82
82
 
83
83
  - `phasegate.config.json` as the quality settings source of truth
84
- - `skills/` with 28 AIDLC skills
84
+ - `skills/` with 30 AIDLC skills
85
85
  - `.claude/skills` and/or `.codex/skills` links for agent use
86
86
  - `.claude/settings.json` and/or `.codex/hooks.json` hook configuration
87
87
  - `docs/principles/*.md` and `docs/folder_management_rules.md`
@@ -145,7 +145,7 @@ npx phasegate reconcile --apply
145
145
  |---|---|
146
146
  | **Phase gates** | Blocks implementation writes until required design documents exist and have been reflected into product docs |
147
147
  | **5-layer validation** | Runs checks from agent runtime and editor time through pre-commit, CI, and scheduled audits |
148
- | **28 AIDLC skills** | Guides AI agents through product architecture, story writing, domain design, test design, and TDD implementation |
148
+ | **30 AIDLC skills** | Guides AI agents through product architecture, story writing, domain design, test design, and TDD implementation |
149
149
  | **Quick Mode** | Keeps bugfixes, docs, test-only changes, and config changes lightweight while preserving traceability |
150
150
  | **Claude Code / Codex hooks** | Runs checks around Write/Edit/Bash operations and session boundaries |
151
151
  | **Agent-readable HarnessError output** | Gives AI agents the reason, missing artifacts, references, and examples needed to self-correct |
@@ -177,7 +177,7 @@ npx phasegate reconcile --apply
177
177
  +------------------------------------------------------------------+
178
178
  | L4 SCHEDULED Validators (default off) |
179
179
  | drift-detection, consistency-check, dead-code analysis, |
180
- | phase2 freshness/pointer checks via standalone p2:* commands |
180
+ | doc-freshness and pointer-validation via L4 + p2:* compatibility|
181
181
  +------------------------------------------------------------------+
182
182
  ```
183
183
 
@@ -191,7 +191,7 @@ npx phasegate reconcile --apply
191
191
 
192
192
  ---
193
193
 
194
- ## 28 Skills
194
+ ## 30 Skills
195
195
 
196
196
  Skills cover the full **AIDLC (AI-Driven Development Life Cycle)**: product definition, design, test design, and TDD implementation. Each skill consumes the artifacts from the previous phase.
197
197
 
@@ -204,6 +204,7 @@ Skills cover the full **AIDLC (AI-Driven Development Life Cycle)**: product defi
204
204
  | **Test Engineering (7)** | `/unit-test-designer` `/it-test-designer` `/scenario-test-designer` `/unit-test-logic-designer` `/it-test-logic-designer` `/scenario-test-logic-designer` `/test-coverage-checker` |
205
205
  | **Implementation (4)** | `/story-implementor` `/quick-implementor` `/implementation-planner` `/implementation-readiness-checker` |
206
206
  | **Verification (8)** | `/consistency-checker` `/cascade-updater` `/codex-delegator` `/codebase-mapper` `/doc-freshness-checker` `/pointer-validator` `/engineering-perspective` `/skill-creator` |
207
+ | **Operations (2)** | `/phasegate-config-doctor` `/phasegate-toolkit-guide` |
207
208
 
208
209
  Details, prerequisites, and generated artifacts: [Skills Overview](docs/guide/skills-overview.md)
209
210
 
@@ -535,7 +536,7 @@ Detailed guides are available under `docs/guide/`:
535
536
  - [Installation](docs/guide/installation.md) -- Detailed install and setup instructions
536
537
  - [Configuration](docs/guide/configuration.md) -- `phasegate.config.json` full reference
537
538
  - [CLI Reference](docs/guide/cli-reference.md) -- All CLI commands and options
538
- - [Skills Overview](docs/guide/skills-overview.md) -- 28 skills with AIDLC execution order
539
+ - [Skills Overview](docs/guide/skills-overview.md) -- 30 skills with AIDLC execution order
539
540
  - [5-Layer Defense Model](docs/guide/layer-model.md) -- L0-L4 layer details and HarnessError format
540
541
  - [Hooks Integration](docs/guide/hooks-integration.md) -- Claude Code Hooks setup and behavior
541
542
  - [Codex Integration](docs/guide/codex-integration.md) -- Codex CLI setup, coverage matrix, and native `apply_patch` limitation
@@ -359,6 +359,6 @@ ISSUE-005 P3-10 で明確化された境界:
359
359
 
360
360
  | Command | Options | Description |
361
361
  |---|---|---|
362
- | `p2:check-freshness` | `--pattern <glob>` `--dry-run` `--format text\|json` | Design doc freshness check |
363
- | `p2:validate-pointers` | `--include-urls` `--format text\|json` | Validate file pointers in docs |
362
+ | `p2:check-freshness` | `--pattern <glob>` `--dry-run` `--format text\|json` | Compatibility entry point for L4-004 doc freshness; canonical L4 execution is `validate --layer L4` |
363
+ | `p2:validate-pointers` | `--include-urls` `--format text\|json` | Compatibility entry point for L4-005 pointer validation; canonical L4 execution is `validate --layer L4` |
364
364
  | `p2:generate-e2e-template` | `--phase <phase>` `--output <path>` | Generate E2E test template |
@@ -36,7 +36,7 @@ npm install
36
36
  npx phasegate init --name <project-name>
37
37
  ```
38
38
 
39
- This deploys 28 skills to `skills/`, creates the agent-facing skill links (for example `.claude/skills/` or `.codex/skills/`), and generates `phasegate.config.json`.
39
+ This deploys 30 skills to `skills/`, creates the agent-facing skill links (for example `.claude/skills/` or `.codex/skills/`), and generates `phasegate.config.json`.
40
40
 
41
41
  `init` is the legacy-compatible bootstrap path for new projects. Use `install` when the project may already have hooks, package scripts, or CI files that should be preserved.
42
42
 
@@ -141,7 +141,7 @@ npx phasegate validate --layer L3
141
141
 
142
142
  L4 validators are designed to run on a weekly schedule and detect slow-moving drift that accumulates over time.
143
143
 
144
- > **Status**: L4 is **disabled by default** (`layers.L4.enabled: false` in `phasegate.config.json`). Projects opt in by flipping the flag and scheduling the command via CI cron (see `ci:generate-template --type consistency-check`). Implementation-wise the validators listed below are functional; the default-off state is a conservative rollout choice, not a missing feature.
144
+ > **Status**: L4 is **disabled by default** (`layers.L4.enabled: false` in `phasegate.config.json`). Projects opt in by flipping the flag and scheduling the command via CI cron (see `ci:generate-template --type consistency-check --render`). Implementation-wise the validators listed below are functional; the default-off state is a conservative rollout choice, not a missing feature. @work-item-id WI-128
145
145
 
146
146
  | Validator | ID | Description |
147
147
  |-----------|-----|-------------|
@@ -152,7 +152,16 @@ L4 validators are designed to run on a weekly schedule and detect slow-moving dr
152
152
  | **pointer-validation** | L4-005 | Resolves and validates design document pointers. Also available through the `p2:validate-pointers` compatibility command. |
153
153
 
154
154
  <!-- @work-item-id WI-116 -->
155
- `doc-freshness` and `pointer-validation` are registered as L4-004/L4-005. The standalone `p2:check-freshness` and `p2:validate-pointers` commands remain as compatibility entry points; WI-033's remaining scope is operational rollout, not validator registration.
155
+ `doc-freshness` and `pointer-validation` are registered as L4-004/L4-005. The standalone `p2:check-freshness` and `p2:validate-pointers` commands remain as compatibility entry points; canonical L4 execution is `validate --layer L4`. WI-033 remains closed as validator registration work; WI-128 tracks scheduling/default/policy rollout.
156
+
157
+ Recommended weekly audit:
158
+
159
+ ```bash
160
+ npx phasegate ci:generate-template --type consistency-check --render
161
+ npx phasegate validate --layer L4
162
+ ```
163
+
164
+ Use a weekly cron such as `0 9 * * 1` for the generated consistency-check workflow. Standard projects normally keep L4 default-off and run the scheduled audit as advisory. Strict projects may opt into `layers.L4.enabled: true` and `failOnWarning` behavior when L4 warnings should block promotion. @work-item-id WI-128
156
165
 
157
166
  ### Drift-detect design pointers
158
167
 
@@ -165,7 +165,7 @@ No. `allowedCategories` is a fixed enum (`bugfix`, `docs`, `test`, `config`). If
165
165
 
166
166
  ## Related
167
167
 
168
- - [Skills Overview](skills-overview.md) — full catalogue of 28 skills
168
+ - [Skills Overview](skills-overview.md) — full catalogue of 30 skills
169
169
  - [Layer Model](layer-model.md) — L0 through L4 defence layers
170
170
  - [Configuration](configuration.md) — `quickMode` configuration reference
171
171
  - `skills/quick-implementor/SKILL.md` — the Quick Mode skill definition
@@ -44,7 +44,7 @@ Step 4: 新規 Unit / 構造変更は scaffold-design で設計文書を起こ
44
44
  npx phasegate init --name <project-name>
45
45
  ```
46
46
 
47
- - `skills/` に 28 スキルを配置し、有効な agent 用に `.claude/skills` / `.codex/skills` を作成
47
+ - `skills/` に 30 スキルを配置し、有効な agent 用に `.claude/skills` / `.codex/skills` を作成
48
48
  - `phasegate.config.json` を生成
49
49
  - `phasegate.config.json` に `baseline` セクションが未記載でも、v0.71.0 以降は
50
50
  **`baseline.enabled` の default が `true`** のため grandfather は既定で有効
@@ -1,6 +1,6 @@
1
1
  # Skills Overview
2
2
 
3
- Phasegate provides 28 skills covering the full AIDLC (AI-Driven Development Life Cycle). `npx phasegate init` deploys the skill bodies to `skills/` and exposes them to enabled agents through `.claude/skills/` / `.codex/skills/` links.
3
+ Phasegate provides 30 skills covering the full AIDLC (AI-Driven Development Life Cycle). `npx phasegate init` deploys the skill bodies to `skills/` and exposes them to enabled agents through `.claude/skills/` / `.codex/skills/` links.
4
4
 
5
5
  ## AIDLC Process — Skill Execution Order
6
6
 
@@ -105,3 +105,10 @@ Skills run per User Story:
105
105
  | `/pointer-validator` | Validate file pointers (relative path references) in design documents. Detects broken links. |
106
106
  | `/engineering-perspective` | Design review from Kent Beck, Martin Fowler, Uncle Bob, and Eric Evans perspectives. Multi-angle quality evaluation. |
107
107
  | `/skill-creator` | Create and update Agent Skills. Packaging, scripting, references, and asset management. |
108
+
109
+ ### Operations (2 skills)
110
+
111
+ | Skill | Description |
112
+ |---|---|
113
+ | `/phasegate-config-doctor` | Diagnose and explain PhaseGate configuration, installation, and hook setup issues. |
114
+ | `/phasegate-toolkit-guide` | Operator guide for PhaseGate toolkit usage, rollout choices, and command selection. |
@@ -5,8 +5,8 @@
5
5
  #
6
6
  # 実行タイミング: 毎週月曜 09:00 UTC(手動トリガーも可)
7
7
  # 実行内容:
8
- # 1. harness:detect-drift — L4 設計⇔コード乖離検出(drift-detect)
9
- # 2. harness:complete-check — L4 全バリデータ(drift-detect / consistency-check / dead-code)
8
+ # 1. phasegate:detect-drift — L4 設計⇔コード乖離検出(drift-detect)
9
+ # 2. validate --layer L4 — L4 全バリデータ(drift-detect / consistency-check / dead-code / doc-freshness / pointer-validation
10
10
  # 3. 乖離検出時: GitHub Issue を自動作成
11
11
 
12
12
  name: AIDLC Consistency Check (Weekly)
@@ -46,7 +46,7 @@ jobs:
46
46
  id: drift
47
47
  run: |
48
48
  set +e
49
- RESULT=$(pnpm run harness harness:detect-drift --json 2>&1)
49
+ RESULT=$(npx phasegate phasegate:detect-drift --json 2>&1)
50
50
  EXIT_CODE=$?
51
51
  echo "result<<EOF" >> $GITHUB_OUTPUT
52
52
  echo "$RESULT" >> $GITHUB_OUTPUT
@@ -54,12 +54,12 @@ jobs:
54
54
  echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
55
55
  exit 0 # Issue作成のため常に続行
56
56
 
57
- # L4: 全バリデータ(drift-detect + consistency-check + dead-code)
57
+ # L4: 全バリデータ(drift-detect + consistency-check + dead-code + doc-freshness + pointer-validation
58
58
  - name: L4 Complete Check
59
59
  id: complete
60
60
  run: |
61
61
  set +e
62
- RESULT=$(pnpm run harness harness:complete-check --json 2>&1)
62
+ RESULT=$(npx phasegate validate --layer L4 --format ci 2>&1)
63
63
  EXIT_CODE=$?
64
64
  echo "result<<EOF" >> $GITHUB_OUTPUT
65
65
  echo "$RESULT" >> $GITHUB_OUTPUT
@@ -95,7 +95,7 @@ jobs:
95
95
  body += '### 対処方法\n';
96
96
  body += '1. 乖離の内容を確認し、設計文書またはコードのどちらを修正すべきか判断\n';
97
97
  body += '2. `cascade-updater` スキルで設計文書を更新、または実装を修正\n';
98
- body += '3. `pnpm run harness harness:detect-drift` で再検証して乖離が解消されたことを確認\n';
98
+ body += '3. `npx phasegate validate --layer L4` で再検証して乖離が解消されたことを確認\n';
99
99
 
100
100
  github.rest.issues.create({
101
101
  owner: context.repo.owner,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phasegate",
3
- "version": "0.148.0",
3
+ "version": "0.150.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",
@@ -0,0 +1,31 @@
1
+ // @layer presentation
2
+ // @unit agent-integration
3
+ // @work-item-id WI-123
4
+
5
+ import * as fs from 'node:fs/promises';
6
+ import * as path from 'node:path';
7
+
8
+ export async function recordHookSkipEvent(input: {
9
+ readonly projectRoot: string;
10
+ readonly hookType: string;
11
+ readonly reason: string;
12
+ readonly targetPaths: readonly string[];
13
+ }): Promise<void> {
14
+ try {
15
+ const phasegateDir = path.join(input.projectRoot, '.phasegate');
16
+ await fs.mkdir(phasegateDir, { recursive: true });
17
+ const record = {
18
+ hookType: input.hookType,
19
+ reason: input.reason,
20
+ targetPaths: [...input.targetPaths],
21
+ observedAt: new Date().toISOString(),
22
+ };
23
+ await fs.appendFile(
24
+ path.join(phasegateDir, 'hook-skip-events.jsonl'),
25
+ `${JSON.stringify(record)}\n`,
26
+ 'utf-8',
27
+ );
28
+ } catch {
29
+ // Hook skip recording is best-effort and must not change hook semantics.
30
+ }
31
+ }
@@ -10,6 +10,7 @@ import { HandlePostToolUseUseCase } from '../application/usecases/handle-post-to
10
10
  import { HarnessConfigConfigQueryAdapter } from '../infrastructure/adapters/harness-config-config-query-adapter.js';
11
11
  import { HarnessApiCliCommandRegistryAdapter } from '../infrastructure/adapters/harness-api-cli-command-registry-adapter.js';
12
12
  import { ChildProcessCliExecutorAdapter } from '../infrastructure/adapters/child-process-cli-executor-adapter.js';
13
+ import { recordHookSkipEvent } from './hook-skip-event-recorder.js';
13
14
  import * as path from 'node:path';
14
15
  import * as fs from 'node:fs/promises';
15
16
 
@@ -80,6 +81,12 @@ async function main(): Promise<void> {
80
81
  const output = await useCase.execute({ toolName, affectedFilePaths: [] });
81
82
 
82
83
  if (output.skipReason) {
84
+ await recordHookSkipEvent({
85
+ projectRoot: path.dirname(configPath),
86
+ hookType: 'post-tool-use',
87
+ reason: output.skipReason,
88
+ targetPaths: [],
89
+ });
83
90
  process.stderr.write(`スキップ: ${output.skipReason}\n`);
84
91
  process.exit(0);
85
92
  }
@@ -11,6 +11,7 @@ import { EnvFileReentryGuardStateAdapter } from '../infrastructure/adapters/env-
11
11
  import { HarnessConfigConfigQueryAdapter } from '../infrastructure/adapters/harness-config-config-query-adapter.js';
12
12
  import { HarnessApiCliCommandRegistryAdapter } from '../infrastructure/adapters/harness-api-cli-command-registry-adapter.js';
13
13
  import { ChildProcessCliExecutorAdapter } from '../infrastructure/adapters/child-process-cli-executor-adapter.js';
14
+ import { recordHookSkipEvent } from './hook-skip-event-recorder.js';
14
15
  import * as path from 'node:path';
15
16
  import * as fs from 'node:fs/promises';
16
17
 
@@ -82,6 +83,12 @@ async function main(): Promise<void> {
82
83
  const output = await useCase.execute({ sessionId });
83
84
 
84
85
  if (output.skipReason === 'REENTRY_DETECTED') {
86
+ await recordHookSkipEvent({
87
+ projectRoot: path.dirname(configPath),
88
+ hookType: 'stop',
89
+ reason: output.skipReason,
90
+ targetPaths: [],
91
+ });
85
92
  process.stderr.write('ReentryGuard: 再入検出によりスキップ\n');
86
93
  process.exit(0);
87
94
  }
@@ -5,4 +5,5 @@
5
5
 
6
6
  export interface ValidatorIdRegistryPort {
7
7
  listAll(): Promise<string[]>;
8
+ listForPreset?(presetId: string, templateType: string): Promise<string[]>;
8
9
  }
@@ -31,8 +31,9 @@ export class TemplateGenerator {
31
31
  templateType: TemplateType,
32
32
  ): Promise<Result<TemplateConfig, Array<{ code: string; message: string }>>> {
33
33
  try {
34
+ const listValidatorIds = this.validatorIdRegistryPort.listForPreset ?? this.validatorIdRegistryPort.listAll;
34
35
  const [validatorIds, presetConfig] = await Promise.all([
35
- this.validatorIdRegistryPort.listAll(),
36
+ listValidatorIds.call(this.validatorIdRegistryPort, presetId, templateType),
36
37
  this.presetConfigPort.getPreset(presetId),
37
38
  ]);
38
39
 
@@ -1,14 +1,42 @@
1
1
  // @unit ci-governance
2
2
  // @layer infrastructure
3
+ // @work-item-id WI-124
3
4
 
4
5
  import type { ValidatorIdRegistryPort } from '../../domain/ports/validator-id-registry-port.js';
6
+ import { buildDefaultRegistry } from '../../../validator-system/composition-root.js';
7
+ import type { ValidatorDefinition } from '../../../validator-system/domain/value-objects/validator-definition.js';
8
+
9
+ type PresetId = 'minimal' | 'standard' | 'strict';
5
10
 
6
11
  export class ValidatorIdRegistryAdapter implements ValidatorIdRegistryPort {
7
12
  constructor(private readonly registryUrl?: string) {}
8
13
 
9
14
  async listAll(): Promise<string[]> {
10
- // In a real implementation, would fetch from validator-system
11
- // For now, returns a stub list
12
- return ['L1-001', 'L2-001', 'L2-002', 'L3-001', 'L4-001'];
15
+ void this.registryUrl;
16
+ return buildDefaultRegistry().getAllDefinitions().map((definition) => definition.validatorId.value);
17
+ }
18
+
19
+ async listForPreset(presetId: string, templateType: string): Promise<string[]> {
20
+ const preset = normalizePreset(presetId);
21
+ const definitions = buildDefaultRegistry().getAllDefinitions();
22
+ return definitions
23
+ .filter((definition) => isIncludedForPreset(definition, preset, templateType))
24
+ .map((definition) => definition.validatorId.value);
25
+ }
26
+ }
27
+
28
+ function normalizePreset(presetId: string): PresetId {
29
+ return presetId === 'minimal' || presetId === 'strict' ? presetId : 'standard';
30
+ }
31
+
32
+ function isIncludedForPreset(definition: ValidatorDefinition, preset: PresetId, templateType: string): boolean {
33
+ if (preset === 'minimal') return false;
34
+ if (definition.layer === 'L4') {
35
+ if (templateType === 'consistency-check') return true;
36
+ if (preset !== 'strict') return false;
37
+ }
38
+ if (definition.enabledCondition === 'strictOnly') {
39
+ return preset === 'strict' || (definition.layer === 'L4' && templateType === 'consistency-check');
13
40
  }
41
+ return true;
14
42
  }
@@ -1,6 +1,6 @@
1
1
  // @layer domain
2
2
  // @unit harness-api
3
- // @work-item-id WI-108
3
+ // @work-item-id WI-108 / WI-114
4
4
  // command-dispatch-service.ts — CommandDispatchService Domain Service
5
5
 
6
6
  import { CommandRegistry } from './command-registry.js';
@@ -16,6 +16,7 @@ import type { ImpactAnalysisPort } from '../ports/impact-analysis-port.js';
16
16
  import type { ArtifactScannerPort } from '../ports/artifact-scanner-port.js';
17
17
  import type { ConfigQueryPort } from '../ports/config-query-port.js';
18
18
  import type { LayerId } from '../value-objects/layer-health.js';
19
+ import type { BaselineHealth, HookHealth, OperationalWarning } from '../value-objects/harness-status-summary.js';
19
20
 
20
21
  export interface CommandDispatchPorts {
21
22
  validatorExecutionPort: ValidatorExecutionPort;
@@ -23,7 +24,13 @@ export interface CommandDispatchPorts {
23
24
  biomeLintPort: BiomeLintPort;
24
25
  impactAnalysisPort: ImpactAnalysisPort;
25
26
  artifactScannerPort: ArtifactScannerPort;
26
- configQueryPort: { getConfig?: () => Promise<unknown>; getPresetInfo?: () => Promise<unknown>; getConfigSummary?: () => Promise<unknown> };
27
+ configQueryPort: {
28
+ getConfig?: () => Promise<unknown>;
29
+ getPresetInfo?: () => Promise<unknown>;
30
+ getConfigSummary?: () => Promise<unknown>;
31
+ getHookHealth?: () => Promise<HookHealth>;
32
+ getBaselineHealth?: () => Promise<BaselineHealth>;
33
+ };
27
34
  }
28
35
 
29
36
  export interface DispatchResult<T = unknown> {
@@ -61,6 +68,38 @@ function summarizeLayerResults(items: readonly { validatorId: string; passed: bo
61
68
  return result;
62
69
  }
63
70
 
71
+ function buildOperationalWarnings(
72
+ hookHealth: HookHealth | undefined,
73
+ baselineHealth: BaselineHealth | undefined,
74
+ ): OperationalWarning[] {
75
+ const warnings: OperationalWarning[] = [];
76
+ const skipCount = hookHealth === undefined
77
+ ? 0
78
+ : Object.values(hookHealth.skipCountsByReason).reduce((sum, count) => sum + count, 0);
79
+ if (hookHealth !== undefined && skipCount > 0) {
80
+ warnings.push({
81
+ code: 'HOOK_SKIP_OBSERVED',
82
+ message: `Hook skip events observed: ${skipCount}`,
83
+ nextAction: 'Inspect hookHealth.latestSkip and re-enable hooks or resolve reentry/timeout causes.',
84
+ });
85
+ }
86
+ if (baselineHealth !== undefined && baselineHealth.shaMismatchCount > 0) {
87
+ warnings.push({
88
+ code: 'BASELINE_SHA_MISMATCH',
89
+ message: `${baselineHealth.shaMismatchCount} baseline files changed since the snapshot.`,
90
+ nextAction: 'Add or update design coverage for changed files and remove resolved entries from the baseline.',
91
+ });
92
+ }
93
+ if (baselineHealth !== undefined && baselineHealth.grandfatheredFileCount > 50 && baselineHealth.removalRate < 0.5) {
94
+ warnings.push({
95
+ code: 'BASELINE_DEBT_HIGH',
96
+ message: `Baseline grandfather debt remains high: ${baselineHealth.grandfatheredFileCount} files.`,
97
+ nextAction: 'Plan a retrofit cleanup batch and reduce the baseline snapshot.',
98
+ });
99
+ }
100
+ return warnings;
101
+ }
102
+
64
103
  const KNOWN_COMMANDS = new Set([
65
104
  'phasegate:check-ready',
66
105
  'phasegate:check-phase',
@@ -203,12 +242,20 @@ export class CommandDispatchService {
203
242
  } else if (configPort.getConfig) {
204
243
  await configPort.getConfig();
205
244
  }
245
+ const [hookHealth, baselineHealth] = await Promise.all([
246
+ configPort.getHookHealth?.(),
247
+ configPort.getBaselineHealth?.(),
248
+ ]);
249
+ const operationalWarnings: OperationalWarning[] = buildOperationalWarnings(hookHealth, baselineHealth);
206
250
  const statusSummary = this.statusDerivationService.derive({
207
251
  scanResult,
208
252
  presetInfo,
209
253
  configSummary: { configPath: 'phasegate.config.json', lastModified: '', version: '2' },
210
254
  phaseGateSummary: { totalStories: 0, passedStories: 0, pendingStories: 0 },
211
255
  liveValidationByLayer,
256
+ hookHealth,
257
+ baselineHealth,
258
+ operationalWarnings,
212
259
  });
213
260
  const r = HarnessApiResponse.pass({ ...summary, passed: 1 }, statusSummary);
214
261
  return { status: 'pass', errors: [], summary: r.summary, data: statusSummary as unknown as T, exitCode: 0 };
@@ -1,9 +1,10 @@
1
1
  // @layer domain
2
2
  // @unit harness-api
3
+ // @work-item-id WI-112
3
4
  // status-derivation-service.ts — StatusDerivationService Domain Service
4
5
 
5
6
  import { LayerHealth, type LayerId } from '../value-objects/layer-health.js';
6
- import { HarnessStatusSummary, type PresetInfo, type ConfigSummary, type PhaseGateSummary } from '../value-objects/harness-status-summary.js';
7
+ import { HarnessStatusSummary, type PresetInfo, type ConfigSummary, type PhaseGateSummary, type HookHealth, type BaselineHealth, type OperationalWarning } from '../value-objects/harness-status-summary.js';
7
8
  import type { ArtifactScanResult, ArtifactPresence } from '../value-objects/artifact-scan-result.js';
8
9
 
9
10
  const ALL_LAYER_IDS: readonly LayerId[] = ['L1', 'L2', 'L3', 'L4'];
@@ -90,6 +91,9 @@ export class StatusDerivationService {
90
91
  configSummary: ConfigSummary;
91
92
  phaseGateSummary: PhaseGateSummary;
92
93
  liveValidationByLayer?: Partial<Record<LayerId, 'pass' | 'fail' | 'skipped' | 'not-run' | 'error'>>;
94
+ hookHealth?: HookHealth;
95
+ baselineHealth?: BaselineHealth;
96
+ operationalWarnings?: readonly OperationalWarning[];
93
97
  }): HarnessStatusSummary {
94
98
  const { scanResult, presetInfo, configSummary, phaseGateSummary, liveValidationByLayer } = input;
95
99
 
@@ -135,6 +139,9 @@ export class StatusDerivationService {
135
139
  phaseGateSummary,
136
140
  presetInfo,
137
141
  configSummary,
142
+ hookHealth: input.hookHealth,
143
+ baselineHealth: input.baselineHealth,
144
+ operationalWarnings: input.operationalWarnings,
138
145
  });
139
146
  }
140
147
  }
@@ -1,5 +1,6 @@
1
1
  // @layer domain
2
2
  // @unit harness-api
3
+ // @work-item-id WI-114
3
4
  // drift-report-summary.ts — DriftReportSummary Value Object
4
5
 
5
6
  export interface DriftItem {
@@ -20,11 +20,48 @@ export interface ConfigSummary {
20
20
  version: string;
21
21
  }
22
22
 
23
+ export interface HookSkipState {
24
+ hookType: string;
25
+ reason: 'HOOK_DISABLED' | 'TIMEOUT_EXCEEDED' | 'REENTRY_DETECTED' | string;
26
+ targetPaths: readonly string[];
27
+ observedAt: string;
28
+ }
29
+
30
+ export interface HookHealth {
31
+ enabled: boolean;
32
+ configuredHooks: readonly string[];
33
+ latestSkip: HookSkipState | null;
34
+ skipCountsByReason: Readonly<Record<string, number>>;
35
+ applyPatchBypass: {
36
+ nativeApplyPatchIntercepted: boolean;
37
+ backstop: 'pre-commit';
38
+ documentationUrl: string;
39
+ };
40
+ }
41
+
42
+ export interface BaselineHealth {
43
+ enabled: boolean;
44
+ path: string;
45
+ grandfatheredFileCount: number;
46
+ shaMismatchCount: number;
47
+ missingFileCount: number;
48
+ removalRate: number;
49
+ }
50
+
51
+ export interface OperationalWarning {
52
+ code: string;
53
+ message: string;
54
+ nextAction: string;
55
+ }
56
+
23
57
  export interface HarnessStatusSummaryProps {
24
58
  layers: readonly LayerHealth[];
25
59
  phaseGateSummary: PhaseGateSummary;
26
60
  presetInfo: PresetInfo;
27
61
  configSummary: ConfigSummary;
62
+ hookHealth?: HookHealth;
63
+ baselineHealth?: BaselineHealth;
64
+ operationalWarnings?: readonly OperationalWarning[];
28
65
  }
29
66
 
30
67
  const REQUIRED_LAYER_IDS: readonly LayerId[] = ['L1', 'L2', 'L3', 'L4'];
@@ -34,12 +71,18 @@ export class HarnessStatusSummary {
34
71
  readonly phaseGateSummary: PhaseGateSummary;
35
72
  readonly presetInfo: PresetInfo;
36
73
  readonly configSummary: ConfigSummary;
74
+ readonly hookHealth: HookHealth | undefined;
75
+ readonly baselineHealth: BaselineHealth | undefined;
76
+ readonly operationalWarnings: readonly OperationalWarning[];
37
77
 
38
78
  private constructor(props: HarnessStatusSummaryProps) {
39
79
  this.layers = Object.freeze([...props.layers]);
40
80
  this.phaseGateSummary = props.phaseGateSummary;
41
81
  this.presetInfo = props.presetInfo;
42
82
  this.configSummary = props.configSummary;
83
+ this.hookHealth = props.hookHealth;
84
+ this.baselineHealth = props.baselineHealth;
85
+ this.operationalWarnings = Object.freeze([...(props.operationalWarnings ?? [])]);
43
86
  Object.freeze(this);
44
87
  }
45
88
 
@@ -1,5 +1,6 @@
1
1
  // @layer domain
2
2
  // @unit harness-api
3
+ // @work-item-id WI-112
3
4
  // layer-health.ts — LayerHealth Value Object
4
5
 
5
6
  export type LayerId = 'L1' | 'L2' | 'L3' | 'L4';
@@ -1,10 +1,13 @@
1
1
  // @layer infrastructure
2
2
  // @unit harness-api
3
+ // @work-item-id WI-123
3
4
  // harness-config-query-adapter.ts — HarnessConfigQueryAdapter
4
5
 
5
6
  import * as fs from 'node:fs/promises';
7
+ import { createHash } from 'node:crypto';
8
+ import { dirname, join, resolve } from 'node:path';
6
9
  import type { ConfigQueryPort } from '../../domain/ports/config-query-port.js';
7
- import type { PresetInfo, ConfigSummary, PhaseGateSummary } from '../../domain/value-objects/harness-status-summary.js';
10
+ import type { PresetInfo, ConfigSummary, PhaseGateSummary, HookHealth, BaselineHealth } from '../../domain/value-objects/harness-status-summary.js';
8
11
  import type { LayerId } from '../../domain/value-objects/layer-health.js';
9
12
 
10
13
  interface HarnessConfigJson {
@@ -18,6 +21,10 @@ interface HarnessConfigJson {
18
21
  designDocs?: string;
19
22
  integrationTests?: string;
20
23
  };
24
+ baseline?: {
25
+ enabled?: boolean;
26
+ path?: string;
27
+ };
21
28
  }
22
29
 
23
30
  const PRESET_LAYERS: Record<string, LayerId[]> = {
@@ -77,4 +84,87 @@ export class HarnessConfigQueryAdapter implements ConfigQueryPort {
77
84
  // wave2-pending: integrate with phase-dependency-model
78
85
  return { totalStories: 0, passedStories: 0, pendingStories: 0 };
79
86
  }
87
+
88
+ async getHookHealth(): Promise<HookHealth> {
89
+ const rootDir = dirname(this.configPath);
90
+ const configuredHooks: string[] = [];
91
+ for (const hookPath of ['.claude/settings.json', '.codex/hooks.json', '.husky/pre-commit', '.husky/pre-push']) {
92
+ try {
93
+ await fs.access(join(rootDir, hookPath));
94
+ configuredHooks.push(hookPath);
95
+ } catch {
96
+ // Missing hook files are represented by absence from configuredHooks.
97
+ }
98
+ }
99
+
100
+ const skipEvents = await readHookSkipEvents(join(rootDir, '.phasegate/hook-skip-events.jsonl'));
101
+ const skipCountsByReason: Record<string, number> = {};
102
+ for (const event of skipEvents) {
103
+ skipCountsByReason[event.reason] = (skipCountsByReason[event.reason] ?? 0) + 1;
104
+ }
105
+
106
+ return {
107
+ enabled: configuredHooks.length > 0,
108
+ configuredHooks,
109
+ latestSkip: skipEvents.at(-1) ?? null,
110
+ skipCountsByReason,
111
+ applyPatchBypass: {
112
+ nativeApplyPatchIntercepted: false,
113
+ backstop: 'pre-commit',
114
+ documentationUrl: 'docs/guide/codex-integration.md',
115
+ },
116
+ };
117
+ }
118
+
119
+ async getBaselineHealth(): Promise<BaselineHealth> {
120
+ const config = await this.readConfig();
121
+ const rootDir = dirname(this.configPath);
122
+ const enabled = config.baseline?.enabled ?? true;
123
+ const relativePath = config.baseline?.path ?? '.phasegate/baseline.json';
124
+ const baselinePath = resolve(rootDir, relativePath);
125
+
126
+ if (!enabled) {
127
+ return { enabled: false, path: relativePath, grandfatheredFileCount: 0, shaMismatchCount: 0, missingFileCount: 0, removalRate: 0 };
128
+ }
129
+
130
+ let raw: string;
131
+ try {
132
+ raw = await fs.readFile(baselinePath, 'utf-8');
133
+ } catch {
134
+ return { enabled: true, path: relativePath, grandfatheredFileCount: 0, shaMismatchCount: 0, missingFileCount: 0, removalRate: 0 };
135
+ }
136
+
137
+ const parsed = JSON.parse(raw) as { files?: Array<{ path?: string; sha1?: string }>; entries?: Array<{ path?: string; sha1?: string }> };
138
+ const entries = parsed.files ?? parsed.entries ?? [];
139
+ let shaMismatchCount = 0;
140
+ let missingFileCount = 0;
141
+ for (const entry of entries) {
142
+ if (typeof entry.path !== 'string' || typeof entry.sha1 !== 'string') continue;
143
+ try {
144
+ const current = await fs.readFile(resolve(rootDir, entry.path));
145
+ const sha1 = createHash('sha1').update(current).digest('hex');
146
+ if (sha1 !== entry.sha1) shaMismatchCount += 1;
147
+ } catch {
148
+ missingFileCount += 1;
149
+ }
150
+ }
151
+ const grandfatheredFileCount = entries.length;
152
+ const removalRate = grandfatheredFileCount === 0
153
+ ? 1
154
+ : (shaMismatchCount + missingFileCount) / grandfatheredFileCount;
155
+ return { enabled: true, path: relativePath, grandfatheredFileCount, shaMismatchCount, missingFileCount, removalRate };
156
+ }
157
+ }
158
+
159
+ async function readHookSkipEvents(filePath: string): Promise<Array<NonNullable<HookHealth['latestSkip']>>> {
160
+ try {
161
+ const raw = await fs.readFile(filePath, 'utf-8');
162
+ return raw
163
+ .split('\n')
164
+ .filter((line) => line.trim().length > 0)
165
+ .map((line) => JSON.parse(line) as HookHealth['latestSkip'])
166
+ .filter((event): event is NonNullable<HookHealth['latestSkip']> => event !== null);
167
+ } catch {
168
+ return [];
169
+ }
80
170
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @unit harness-api
3
3
  * @layer presentation
4
- * @work-item-id WI-142
4
+ * @work-item-id WI-113 / WI-142
5
5
  *
6
6
  * Phasegate CLI エントリポイント。
7
7
  * 各Unitの Composition Root からハンドラーを取得し、コマンドに応じてディスパッチする。
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @layer infrastructure
3
3
  * @unit phase-dependency-model
4
+ * @work-item-id WI-115
4
5
  */
5
6
 
6
7
  import type { Dirent } from "node:fs";
@@ -55,7 +55,7 @@ const DEFAULT_CONFIG = {
55
55
  };
56
56
 
57
57
  /** バリデータ定義カタログ */
58
- function buildDefaultRegistry(): ValidatorRegistry {
58
+ export function buildDefaultRegistry(): ValidatorRegistry {
59
59
  const defaultRule = ValidationRule.create({
60
60
  ruleName: 'default-rule',
61
61
  errorTemplate: { code: 'L2-001', severity: 'error', messageTemplate: '{{message}}' },
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @layer domain
3
3
  * @unit validator-system
4
+ * @work-item-id WI-116
4
5
  *
5
6
  * ValidatorId 値オブジェクト
6
7
  * L1-001〜L4-005 のバリデータを識別する不変値オブジェクト
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @layer presentation
3
3
  * @unit validator-system
4
+ * @work-item-id WI-113
4
5
  *
5
6
  * RunValidatorsHandler — バリデータ実行 CLIハンドラー
6
7
  */