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.
- package/CHANGELOG.md +6 -0
- package/README.ja.md +6 -5
- package/README.md +6 -5
- package/docs/guide/cli-reference.md +2 -2
- package/docs/guide/installation.md +1 -1
- package/docs/guide/layer-model.md +11 -2
- package/docs/guide/quick-vs-full-mode.md +1 -1
- package/docs/guide/retrofit-adoption.md +1 -1
- package/docs/guide/skills-overview.md +8 -1
- package/docs/templates/ci/consistency-check.yml +6 -6
- package/package.json +1 -1
- package/scripts/harness/agent-integration/presentation/hook-skip-event-recorder.ts +31 -0
- package/scripts/harness/agent-integration/presentation/post-tool-use-hook.ts +7 -0
- package/scripts/harness/agent-integration/presentation/stop-hook.ts +7 -0
- package/scripts/harness/ci-governance/domain/ports/validator-id-registry-port.ts +1 -0
- package/scripts/harness/ci-governance/domain/services/template-generator.ts +2 -1
- package/scripts/harness/ci-governance/infrastructure/adapters/validator-id-registry-adapter.ts +31 -3
- package/scripts/harness/harness-api/domain/services/command-dispatch-service.ts +49 -2
- package/scripts/harness/harness-api/domain/services/status-derivation-service.ts +8 -1
- package/scripts/harness/harness-api/domain/value-objects/drift-report-summary.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/harness-status-summary.ts +43 -0
- package/scripts/harness/harness-api/domain/value-objects/layer-health.ts +1 -0
- package/scripts/harness/harness-api/infrastructure/adapters/harness-config-query-adapter.ts +91 -1
- package/scripts/harness/main.ts +1 -1
- package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.ts +1 -0
- package/scripts/harness/validator-system/composition-root.ts +1 -1
- package/scripts/harness/validator-system/domain/value-objects/validator-id.ts +1 -0
- 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/` —
|
|
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
|
-
| **
|
|
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
|
-
##
|
|
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
|
-
###
|
|
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) —
|
|
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
|
|
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
|
-
| **
|
|
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
|
-
|
|
|
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
|
-
##
|
|
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) --
|
|
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` |
|
|
363
|
-
| `p2:validate-pointers` | `--include-urls` `--format text\|json` |
|
|
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
|
|
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
|
|
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
|
|
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/` に
|
|
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
|
|
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.
|
|
9
|
-
# 2.
|
|
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=$(
|
|
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=$(
|
|
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. `
|
|
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
|
@@ -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
|
}
|
|
@@ -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
|
|
36
|
+
listValidatorIds.call(this.validatorIdRegistryPort, presetId, templateType),
|
|
36
37
|
this.presetConfigPort.getPreset(presetId),
|
|
37
38
|
]);
|
|
38
39
|
|
package/scripts/harness/ci-governance/infrastructure/adapters/validator-id-registry-adapter.ts
CHANGED
|
@@ -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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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: {
|
|
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
|
}
|
|
@@ -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,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
|
}
|
package/scripts/harness/main.ts
CHANGED
|
@@ -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}}' },
|