phasegate 0.283.0 → 0.335.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 +16 -0
- package/docs/ADR/017-warning-severity-aggregation.md +17 -0
- package/docs/ADR/038-config-state-operation-permission-policy.md +78 -0
- package/docs/ADR/039-hook-observable-state-as-authorization-unit.md +74 -0
- package/docs/ADR/040-quick-mode-config-via-preset-resolution.md +66 -0
- package/docs/guide/cli-reference.md +89 -7
- package/docs/guide/configuration.md +43 -2
- package/docs/guide/layer-model.md +2 -0
- package/docs/guide/quick-vs-full-mode.md +63 -3
- package/docs/guide/troubleshooting.md +37 -0
- package/docs/templates/agent-context/CLAUDE.md.template.md +6 -6
- package/package.json +2 -2
- package/scripts/harness/agent-integration/application/dto/handle-pre-tool-use-dto.ts +5 -1
- package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +108 -20
- package/scripts/harness/agent-integration/domain/services/bash-write-target-extractor.ts +215 -5
- package/scripts/harness/agent-integration/domain/value-objects/protected-file-list.ts +5 -0
- package/scripts/harness/agent-integration/infrastructure/adapters/file-system-full-mode-session-query-adapter.ts +75 -23
- package/scripts/harness/agent-integration/infrastructure/adapters/harness-config-config-query-adapter.ts +18 -3
- package/scripts/harness/agent-integration/infrastructure/adapters/quick-mode-full-mode-requirement-adapter.ts +15 -1
- package/scripts/harness/agent-integration/presentation/pre-tool-use-hook.ts +57 -9
- package/scripts/harness/ci-governance/application/dto/scaffold-inception-input.ts +9 -0
- package/scripts/harness/ci-governance/application/dto/scaffold-inception-output.ts +13 -0
- package/scripts/harness/ci-governance/application/usecases/list-templates-usecase.ts +30 -0
- package/scripts/harness/ci-governance/application/usecases/scaffold-inception-usecase.ts +63 -0
- package/scripts/harness/ci-governance/application/usecases/show-template-usecase.ts +39 -0
- package/scripts/harness/ci-governance/composition-root.ts +57 -5
- package/scripts/harness/ci-governance/domain/ports/inception-doc-writer-port.ts +19 -0
- package/scripts/harness/ci-governance/domain/ports/inception-template-repository-port.ts +16 -0
- package/scripts/harness/ci-governance/domain/ports/template-catalog-port.ts +23 -0
- package/scripts/harness/ci-governance/domain/services/claude-md-composer.ts +20 -11
- package/scripts/harness/ci-governance/domain/value-objects/inception-doc-kind.ts +109 -0
- package/scripts/harness/ci-governance/domain/value-objects/template-catalog-entry.ts +53 -0
- package/scripts/harness/ci-governance/domain/value-objects/template-name.ts +50 -0
- package/scripts/harness/ci-governance/infrastructure/adapters/file-system-inception-doc-writer-adapter.ts +42 -0
- package/scripts/harness/ci-governance/infrastructure/adapters/file-system-inception-template-repository-adapter.ts +32 -0
- package/scripts/harness/ci-governance/infrastructure/adapters/file-system-template-catalog-adapter.ts +60 -0
- package/scripts/harness/ci-governance/presentation/handlers/check-repetition-handler.ts +10 -2
- package/scripts/harness/ci-governance/presentation/handlers/scaffold-inception-handler.ts +101 -0
- package/scripts/harness/ci-governance/presentation/handlers/templates-handler.ts +103 -0
- package/scripts/harness/config-foundation/domain/harness-config.ts +12 -0
- package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +3 -3
- package/scripts/harness/config-foundation/infrastructure/presets/standard.json +3 -3
- package/scripts/harness/config-foundation/infrastructure/presets/strict.json +3 -3
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +44 -1
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +42 -1
- package/scripts/harness/harness-api/domain/services/command-dispatch-service.ts +12 -3
- package/scripts/harness/harness-api/domain/value-objects/ci-check-result.ts +2 -22
- package/scripts/harness/harness-api/domain/value-objects/known-harness-commands.ts +3 -1
- package/scripts/harness/harness-error/application/dto/create-harness-error-input.ts +3 -1
- package/scripts/harness/harness-error/application/dto/harness-error-contract.ts +3 -1
- package/scripts/harness/harness-error/application/mappers/harness-error-contract-mapper.ts +9 -17
- package/scripts/harness/harness-error/application/usecases/create-harness-error-use-case.ts +7 -7
- package/scripts/harness/harness-error/domain/services/harness-error-factory.ts +30 -33
- package/scripts/harness/harness-error/domain/value-objects/error-definition.ts +28 -17
- package/scripts/harness/harness-error/domain/value-objects/harness-error.ts +30 -9
- package/scripts/harness/harness-error/domain/value-objects/remediation-type.ts +30 -0
- package/scripts/harness/harness-error/infrastructure/registry/l2-error-definitions.ts +44 -29
- package/scripts/harness/harness-error/infrastructure/registry/l3-error-definitions.ts +44 -27
- package/scripts/harness/harness-error/infrastructure/registry/l4-error-definitions.ts +47 -32
- package/scripts/harness/installation/application/checks/claude-context-missing-check.ts +13 -7
- package/scripts/harness/installation/application/checks/config-status-check.ts +52 -0
- package/scripts/harness/installation/application/checks/husky-pre-commit-missing-check.ts +6 -0
- package/scripts/harness/installation/application/ports/config-status-probe-port.ts +9 -0
- package/scripts/harness/installation/application/usecases/run-doctor-diagnostics.ts +30 -8
- package/scripts/harness/installation/application/usecases/run-install.ts +44 -11
- package/scripts/harness/installation/application/usecases/run-reconcile.ts +44 -11
- package/scripts/harness/installation/composition-root.ts +13 -3
- package/scripts/harness/installation/domain/check-id.ts +2 -0
- package/scripts/harness/installation/domain/config-status.ts +17 -0
- package/scripts/harness/installation/domain/ports/heuristic-check.ts +10 -1
- package/scripts/harness/installation/infrastructure/adapters/config-status-probe-adapter.ts +79 -0
- package/scripts/harness/installation/presentation/cli/doctor-handler.ts +6 -1
- package/scripts/harness/installation/presentation/formatters/diagnostic-report-formatter.ts +19 -5
- package/scripts/harness/integrations/pre-commit.ts +17 -3
- package/scripts/harness/main.ts +159 -21
- package/scripts/harness/phase-dependency-model/application/services/evidence-bundle-assembler.ts +2 -1
- package/scripts/harness/phase-dependency-model/domain/ports/plan-document-reader-port.ts +12 -0
- package/scripts/harness/phase-dependency-model/infrastructure/filesystem/markdown-plan-document-reader.ts +64 -33
- package/scripts/harness/phase2-extensions/presentation/handlers/check-freshness-handler.ts +17 -9
- package/scripts/harness/quick-mode/application/ports/file-existence-port.ts +15 -0
- package/scripts/harness/quick-mode/application/usecases/classify-change-category-usecase.ts +60 -22
- package/scripts/harness/quick-mode/composition-root.ts +25 -15
- package/scripts/harness/quick-mode/domain/errors/quick-mode-config-error.ts +19 -0
- package/scripts/harness/quick-mode/domain/services/quick-mode-judgment-engine.ts +116 -18
- package/scripts/harness/quick-mode/domain/value-objects/category-override-rules.ts +147 -0
- package/scripts/harness/quick-mode/domain/value-objects/change-category.ts +33 -0
- package/scripts/harness/quick-mode/domain/value-objects/quick-mode-config.ts +31 -10
- package/scripts/harness/quick-mode/index.ts +7 -1
- package/scripts/harness/quick-mode/infrastructure/adapters/fs-file-existence-adapter.ts +38 -0
- package/scripts/harness/quick-mode/infrastructure/adapters/harness-config-quick-mode-config-adapter.ts +83 -32
- package/scripts/harness/skill-quality/infrastructure/adapters/file-system-requirement-test-matrix-adapter.ts +51 -8
- package/scripts/harness/skill-quality/presentation/handlers/check-coverage-handler.ts +13 -6
- package/scripts/harness/traceability-model/domain/value-objects/work-item-frontmatter.ts +5 -1
- package/scripts/harness/traceability-model/infrastructure/gateways/file-system-work-item-identity-gateway.ts +6 -1
- package/scripts/harness/traceability-model/infrastructure/gateways/file-system-work-item-status-gateway.ts +15 -2
- package/scripts/harness/validator-system/application/use-cases/aggregate-validation-results-usecase.ts +11 -14
- package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +8 -0
- package/scripts/harness/validator-system/domain/services/effective-severity-policy.ts +39 -0
- package/scripts/harness/validator-system/domain/value-objects/consistency-report.ts +6 -4
- package/scripts/harness/validator-system/domain/value-objects/drift-report.ts +12 -7
- package/scripts/harness/validator-system/domain/value-objects/validation-result.ts +11 -3
- package/scripts/harness/validator-system/infrastructure/adapters/file-system-security-pattern-scanner-adapter.ts +20 -17
- package/scripts/harness/validator-system/infrastructure/adapters/phase-dependency-phase-gate-policy-adapter.ts +35 -14
- package/scripts/harness/validator-system/infrastructure/adapters/traceability-metadata-policy-adapter.ts +13 -8
- package/scripts/harness/validator-system/presentation/formatters/agent-validation-result-formatter.ts +19 -10
- package/skills/product-architect/SKILL.md +19 -0
- package/skills/quick-implementor/SKILL.md +19 -0
- package/skills/story-mapper/SKILL.md +11 -0
- package/skills/story-writer/SKILL.md +11 -0
- package/skills/unit-designer/SKILL.md +11 -0
- package/templates/product_overview.template.md +85 -0
- package/templates/product_overview_plan.template.md +55 -0
- package/templates/story_mapping_plan.template.md +61 -0
- package/templates/story_writer_plan.template.md +61 -0
- package/templates/unit_design_plan.template.md +63 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,8 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **WI-335 — エラー案内の機械往復保証(remediationType 分類 + round-trip テスト)** — suggested action が本当に「従えば直る」かを人間レビュー頼みにせず CI で保証する仕組み。HarnessError に optional `remediationType`(mechanical / ai-assisted / manual、未設定は manual 扱い)を導入し、代表 validator を分類(L2-002 metadata=mechanical、L3-003 レポート不在=mechanical・閾値未達=ai-assisted、L3-001=manual、L4-001/002=ai-assisted)。mechanical 宣言されたエラーは「エラー → suggestion 文言を機械適用 → 再実行 → pass」の往復テストで固定し、案内文言と実挙動が乖離すると(#37/#39 型の破れ)テストが落ちる構造にした。
|
|
13
|
+
|
|
14
|
+
- **WI-329 — 実分布リリースゲート(tarball release-smoke)** — 単体テスト全 green でも新規インストール即クラッシュ(#34)・非 TS リポで fail-closed(#37/#39)・dead flag(#36)がすり抜けた教訓から、`npm pack` した tarball を実分布相当 fixture(純 Python / Go monorepo / docs のみ)にクリーンインストールし install → doctor → validate → uninstall を実走する E2E を追加。通常 suite では skip(`PHASEGATE_RELEASE_SMOKE=1` ガードで hermetic 性維持)し、CI の新 job `release-smoke` が pack job の実バイト列 artifact に対して実行する。`--with-husky` のフラグ有効性 assert で dead-flag 再発も検知。
|
|
15
|
+
|
|
10
16
|
### Fixed
|
|
11
17
|
|
|
18
|
+
- **WI-334 — CI workflow ファイルの change-category 分類取りこぼしを修正** — `.github/workflows/*.yml` の新規作成が CREATE→feature フォールバックに落ちて pre-tool-use hook にブロックされ、かつ .github/ は unit を持たないため案内される story-implementor 経路が構造的に完遂不能だった(WI-329 のドッグフードで発見。MODIFY は bugfix で通るため防御としても不整合)。WI-261(skills/**/*.md → docs)と同型の明示ルールで `.github/workflows/` 配下の yml/yaml を config に分類(内容防御は L3-006 + integrity pin が担当)。併せて CLI `check-change-category` の changeKind をファイル存在で推定し hook 判定と一致させた(hook の Bash 経路は従来挙動を完全維持)。
|
|
19
|
+
|
|
20
|
+
- **WI-333 — config 不在時の hook 全遮断デッドロックを解消(ADR-038 G1 / GitHub #40 完全解消)** — config **不在**(ENOENT)で pre-tool-use hook が未捕捉例外→exit 2 となり、config を作成する Write すら遮断される自己修復デッドロックが missing 状態に残っていた(WI-314 は invalid のみ対応)。hook 用 config adapter で ENOENT を警告+既定値の fail-open に変更(EACCES 等の真の異常は従来どおり throw)。gated パスへの書き込みは phase-gate 側が config-foundation 経由の独立 load で fail-closed を維持することをテストで固定。ADR-038 の許可表と G1 を解消済みに更新。
|
|
21
|
+
|
|
22
|
+
- **WI-330 — doctor の config 状態可視化と許可ポリシーの仕様化(GitHub #40 恒久化)** — doctor が config 不在・不正でも他項目が揃っていれば GREEN を報告していた盲点を解消。新 check `config-status` を追加し、missing → warn(「既定 fail-open で動作中、`phasegate init` で生成可」)、invalid-json / invalid-schema → red(修復の一手つき)、JSON・human 出力に `configStatus` を明示。「config 状態 × 操作クラス」の許可表を ADR-038 として仕様化し、既知ギャップも正直に記録(**G1: config 不在時に pre-tool-use hook が ENOENT 未捕捉で全ツール遮断=自己修復デッドロックが missing 状態では現存**、G2: invalid-json の検査系が fail-open で原則より緩い)。G1 は WI-333 で修正。
|
|
23
|
+
|
|
24
|
+
- **WI-331 — CLAUDE.md の user-section を managed block の外へ移す構造修正(GitHub #35 根本修正)** — #35 の根本原因は CLAUDE.md テンプレートだけ user-section が managed block の内側にあり(AGENTS.md は外側)、block 置換のたびに WI-315 の「抽出→再注入」に頼っていたこと。テンプレートを AGENTS.md と対称の外側配置に変更し、install / reconcile は旧構造(内側)を検出すると本文を保持したまま外側へ移設する冪等 migration を実行(2 回目適用で byte 同値をテスト固定)。refresh 経路(claude-md-composer)は新テンプレート採用で自動移行、`$` シーケンス保護と空行保持も強化。新構造では uninstall 後も user-section が生存する副次改善あり。
|
|
25
|
+
|
|
26
|
+
- **WI-332 — severity 実効判定の単一ソース化と横断 regression(GitHub #38 恒久化)** — 実効 severity 判定(ADR-017 の isEffectivelyPassed)が harness-api と validator-system に複製され、pre-commit は独自集約で severity を見ておらず warning-only failure が exit 1 になっていた。判定を `validator-system/domain/services/effective-severity-policy.ts` に一本化し、validate 集約・CiCheckResult(ci-check / complete-check)・pre-commit の全経路が同一実装を通る形に統一(pre-commit の warning-only は ADR-017 どおり exit 0 へ)。同一結果セットを 3 経路に通して実効判定の一致を assert する横断 regression を追加し、将来の乖離を機械検知する。
|
|
27
|
+
|
|
12
28
|
- **WI-328 — 実効言語と出所を phasegate:status に表示(GitHub #39 残課題)** — WI-319/320 の言語自動検出の結果がどこにも表示されず、どの validator が有効/SKIP になるか知る術がなかった。`phasegate:status` の JSON に `languages: { effective, source }`(source = `declared` / `detected` / `fallback`)を追加。解決ロジックは validator-system の `resolveProjectLanguages()`(WI-319 の検出テーブル)に一本化して再利用し、validator の有効/SKIP 判定と必ず同じ結果を表示する。ConfigQueryPort への追加は optional メソッドで後方互換。
|
|
13
29
|
|
|
14
30
|
- **WI-327 — 最小 config(project のみ)で動作可能に** — 手書き `phasegate.config.json` のスキーマが top-level 8 項目を required とし、`{"project": {"name": ..., "preset": ...}}` の最小構成で L1-001 が連発していた。プリセット解決(`PresetResolutionService.deepMerge`)は省略セクションの補完を既に完備していたため、v3 **と v2**(architecture キーなしの最小 config は v2 検証に振られる)の top-level required を `["project"]` に緩和し、型定義を実態に一致させた。**検証は弱めていない**: セクション内の required・型・enum・additionalProperties は不変で、書かれているが不正なキーは従来どおりエラー(spawn E2E で exit 2 を固定)。最小 config の解決結果がプリセット定義と一致することも統合テストで固定。
|
|
@@ -212,3 +212,20 @@ human/agent/ci formatter で `[FAIL]` と `[WARN]` を分離:
|
|
|
212
212
|
2. **(B) warning も exit 1 を default に保つ(CI 互換性優先)** — default を変えず移行コストはゼロだが、`defaultSeverity: warning` 宣言と「常に fail」実装の semantic 乖離が残るため不採用。
|
|
213
213
|
|
|
214
214
|
採用理由の詳細は上記 Decision「採用理由」を参照。
|
|
215
|
+
|
|
216
|
+
## 追記 (WI-332, 2026-07-18): 実効判定の共有実装と全経路の経由
|
|
217
|
+
|
|
218
|
+
本 ADR の Decision(集計セマンティクス)自体は変更しない。実装配置に関する追記のみ。
|
|
219
|
+
|
|
220
|
+
github#38(`complete-check` だけが独自集約を持ち warning-only failure で exit 1 になった回帰。
|
|
221
|
+
WI-318 で修正)の恒久化として、WI-332 で「実効 severity 判定」を単一の共有実装に集約した:
|
|
222
|
+
|
|
223
|
+
- **共有実装(単一ソース)**: `scripts/harness/validator-system/domain/services/effective-severity-policy.ts` の `isEffectivelyPassed()`。本 ADR Decision の集計セマンティクス(skipped / passed=true は実質 pass、error 含みは fail、errors=[] は防御的に fail、warning-only は `failOnWarning=false` 既定で実質 pass)をそのまま実装する
|
|
224
|
+
- **全経路がこれを経由する**:
|
|
225
|
+
- `validate` 集約 — `validator-system/application/use-cases/aggregate-validation-results-usecase.ts`(WI-332 で複製実装を削除し共有実装を参照)
|
|
226
|
+
- `ci-check` / `complete-check` — `harness-api/domain/value-objects/ci-check-result.ts` の `CiCheckResult.fromResults()`(WI-318 で 2 コマンド共有化、WI-332 で複製実装を削除し共有実装を参照)
|
|
227
|
+
- `pre-commit` / `commit-msg` / `bypass-audit` — `integrations/pre-commit.ts` の `buildReport()` / `classifyValidatorFailure()`(WI-332 で手動集約 `failed === 0` を共有実装経由に変更。pre-commit 経路には `validate.failOnWarning` の config 配線が無いため、既定値 `false` に固定)
|
|
228
|
+
- **例外**: `status` コマンドは「failure を表示しつつ常に exit 0」という独立契約のため、本判定の対象外
|
|
229
|
+
- **回帰防御**: `scripts/harness/__tests__/integration/validator-system/severity-aggregation-consistency.test.ts` が同一の validator 結果セット(全 pass / warning-only / error / mixed / errors=[] / skipped)を 3 経路(validate 集約・CiCheckResult・pre-commit 集約)に通し、実効判定の一致を assert する。どれかの経路が独自判定に戻るとこのテストが落ちる
|
|
230
|
+
|
|
231
|
+
新しい集約経路を追加する場合は、必ず `isEffectivelyPassed()` を経由し、上記横断テストに経路を追加すること(ADR-021 の格下げ禁止契約も併せて参照)。
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
adr_id: "038"
|
|
3
|
+
title: "config 状態と操作クラスの許可ポリシー"
|
|
4
|
+
status: Accepted
|
|
5
|
+
date: 2026-07-18
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# config 状態と操作クラスの許可ポリシー
|
|
9
|
+
|
|
10
|
+
<!-- @work-item-id WI-330 -->
|
|
11
|
+
<!-- @work-item-id WI-333 -->
|
|
12
|
+
|
|
13
|
+
## Context
|
|
14
|
+
|
|
15
|
+
GitHub #40 は「スキーマ違反の `phasegate.config.json` があると pre-tool-use hook が exit 2 で全ツールを遮断し、config を修復する経路自体が消える(自己修復デッドロック)」という障害だった。WI-314 で hook / doctor を fail-open にする応急処置を入れ、WI-323 で hook 側 adapter の JSON parse fail-open、WI-325 で `ConfigParseError` の導入(誤メッセージ修正)を行った。しかし、
|
|
16
|
+
|
|
17
|
+
- (a) doctor は config 不在・不正でも config 状態を diagnostics に含めず、他が揃っていれば GREEN を報告していた。
|
|
18
|
+
- (b) 「config がどの状態のとき、どの操作クラスが許可されるか」を定義した仕様書が存在せず、各修正がアドホックな穴埋めになっていた。
|
|
19
|
+
- (c) config **missing** 系のテストが欠落しており、missing 状態の実挙動は未検証だった。
|
|
20
|
+
|
|
21
|
+
WI-330 で許可表を実測に基づいて仕様化し、doctor に config 状態 check(`config-status`)を追加し、欠落マスをテストで固定した。
|
|
22
|
+
|
|
23
|
+
## Decision
|
|
24
|
+
|
|
25
|
+
### 1. config 状態は 4 値で分類する
|
|
26
|
+
|
|
27
|
+
`missing` / `invalid-json` / `invalid-schema` / `valid`。判定は config-foundation の実 load 経路(`FileSystemConfigRepository` の JSON parse + AJV schema 検証 + preset 解決)をそのまま使う。エラー型との対応:
|
|
28
|
+
|
|
29
|
+
| 状態 | 検出エラー型 | 定義箇所 |
|
|
30
|
+
|------|-------------|---------|
|
|
31
|
+
| missing | `ConfigNotFoundError` | `scripts/harness/config-foundation/infrastructure/repositories/file-system-config-repository.ts:12` |
|
|
32
|
+
| invalid-json | `ConfigParseError`(`ConfigPersistenceError` のサブクラス、WI-325) | 同ファイル `:41` |
|
|
33
|
+
| invalid-schema | `ConfigValidationError`(AJV 違反・未知 preset を含む) | `scripts/harness/config-foundation/domain/errors/config-validation-error.ts:9`、throw 箇所は `load-resolved-config-use-case.ts:71-99` |
|
|
34
|
+
| valid | —(load 成功) | — |
|
|
35
|
+
|
|
36
|
+
config の解決順は project 直下 `phasegate.config.json` → `.phasegate-local/phasegate.config.json`(personal install)で、CLI は cwd から上方探索する(`file-system-config-repository.ts:58-81` `findNearestConfig`)。
|
|
37
|
+
|
|
38
|
+
### 2. 許可表(config 状態 × 操作クラス)
|
|
39
|
+
|
|
40
|
+
2026-07-18 時点の**実測**(`invalid-config-fail-open.integration.test.ts` で固定。missing 列は WI-333 で fail-open 化)。○ = 許可 / ● = 意図したゲート判定で通過・遮断 / × = 遮断。
|
|
41
|
+
|
|
42
|
+
| 操作クラス \ config 状態 | valid | missing | invalid-json | invalid-schema |
|
|
43
|
+
|---|---|---|---|---|
|
|
44
|
+
| **config 自身への編集**(hook 経由 Write/Edit) | ○ fail-open(既定では保護対象外。`protectedFiles.patterns` に含めた場合のみ CLI 誘導 block) | ○ fail-open(WI-333。自己修復経路) | ○ fail-open | ○ fail-open |
|
|
45
|
+
| **gated パス書込**(`scripts/harness/` 等、hook 経由) | ● phase-gate 判定(fail-closed) | ● 既定設定で phase-gate 判定(fail-closed 維持、WI-333) | ● 既定設定で phase-gate 判定(fail-closed 維持) | ● 既定設定で phase-gate 判定(fail-closed 維持) |
|
|
46
|
+
| **無関係パス書込**(hook 経由) | ○ | ○ fail-open(警告付き、WI-333) | ○ fail-open | ○ fail-open |
|
|
47
|
+
| **無関係 Bash**(書込抽出なし、hook 経由) | ○ | ○ fail-open(警告付き、WI-333) | ○ fail-open(警告付き) | ○ fail-open(警告付き) |
|
|
48
|
+
| **検査系コマンド**(`validate` / `ci-check` 等) | ○ 通常実行 | ○ fail-open(既定設定で実行、exit 0) | **○ fail-open(警告付き exit 0)【設計意図より緩い】** | × fail-closed(exit 2 + Recovery 手順) |
|
|
49
|
+
| **hook・doctor 起動** | ○ | ○ fail-open(doctor は `configStatus: missing` → warn。hook は警告 + 既定設定で完走、WI-333) | ○ fail-open(doctor は `configStatus: invalid-json` → red) | ○ fail-open(`CONFIG_FAIL_OPEN_COMMANDS`。doctor は `configStatus: invalid-schema` → red) |
|
|
50
|
+
|
|
51
|
+
コード根拠:
|
|
52
|
+
|
|
53
|
+
- **CLI dispatch 層の fail-open/fail-closed 分岐**: `scripts/harness/main.ts:1867` `CONFIG_FAIL_OPEN_COMMANDS = new Set(["hook", "doctor"])`、`main.ts:1869-1902` `loadResolvedConfig()` — `ConfigValidationError` は hook/doctor のみ fail-open・他は exit 2(`:1878-1889`)、`ConfigNotFoundError` は全コマンド silent fail-open(`:1891-1893`)、`ConfigParseError`(`instanceof ConfigPersistenceError`)は全コマンド警告付き fail-open(`:1894-1900`)。
|
|
54
|
+
- **hook 実行層の fail-open**: `scripts/harness/agent-integration/infrastructure/adapters/harness-config-config-query-adapter.ts` `loadConfig()` — JSON parse 失敗と `fs.readFileSync` の ENOENT(missing、WI-333)はいずれも警告 + 空 config(既定値)で続行。ENOENT **以外**の fs エラー(EACCES / EISDIR 等の真の異常)は従来どおり throw し、`scripts/harness/agent-integration/presentation/pre-tool-use-hook.ts` の outer catch で「実行エラー」exit 2 になる。fallback config path は `pre-tool-use-hook.ts:58-76` `findConfigPath()`(不在時は `startDir/phasegate.config.json` を返す)。
|
|
55
|
+
- **missing の gated パス fail-closed**: hook 側 config は既定値に縮退するが、gated パス書込は `phase-gate-query-adapter.ts` が config-foundation の `ConfigNotFoundError` を generic catch で「評価不能 = NOT passed」に落とすため、phase-gate block(フェーズゲート違反、exit 2)が維持される。
|
|
56
|
+
- **config 自身が gated/protected でない根拠**: 既定の保護パターンに `phasegate.config.json` は含まれない(`scripts/harness/agent-integration/domain/value-objects/protected-file-list.ts:17-27` `DEFAULT_PATTERNS`)。ユーザーが `protectedFiles.patterns` で保護した場合のみ CLI 誘導付き block(`handle-pre-tool-use-usecase.ts:480-495`)。
|
|
57
|
+
- **doctor の config 状態可視化(WI-330 で追加)**: `scripts/harness/installation/infrastructure/adapters/config-status-probe-adapter.ts`(分類)、`installation/application/checks/config-status-check.ts`(missing → warn / invalid-* → red)、`installation/presentation/formatters/diagnostic-report-formatter.ts`(JSON `configStatus` フィールドと human `Config:` 行)。
|
|
58
|
+
|
|
59
|
+
### 3. 設計原則
|
|
60
|
+
|
|
61
|
+
1. **自己修復例外は復旧経路の保証であり、検査系の fail-closed は緩めない。** hook(エージェントのツール遮断点)と doctor(自己診断)は config がどんな状態でも起動・完走できなければならない。逆に `validate` / `ci-check` 等の検査系は、設定が壊れた状態の検査結果を「合格」として流通させないため fail-closed を原則とする。
|
|
62
|
+
2. **fail-open は必ず可視化とセットにする。** fail-open で既定設定に落ちた事実は stderr 警告(CLI/hook)と doctor の `configStatus` + `config-status` finding(missing = warn、invalid-* = red)で必ず表面化させる。「不正 config でも doctor GREEN」は本 ADR をもって仕様違反である。
|
|
63
|
+
3. **gated スコープの fail-closed は config 状態に依存しない。** config が壊れていても、gated パスへの書込は既定設定による phase-gate 判定で引き続きブロックされる(fail-open は「遮断の解除」ではなく「既定設定への縮退」)。
|
|
64
|
+
|
|
65
|
+
### 4. 既知ギャップ(本 ADR は現状を固定し、修正は別 WI)
|
|
66
|
+
|
|
67
|
+
| # | ギャップ | 現状 | あるべき姿 |
|
|
68
|
+
|---|---------|------|-----------|
|
|
69
|
+
| G1 | **missing 状態で pre-tool-use hook が全ツール遮断**(config 自身への Write も遮断 = 自己修復デッドロック復活) | **WI-333 で解消**: `harness-config-config-query-adapter.ts` `loadConfig()` が ENOENT を捕捉し「警告 + 既定値で続行」の fail-open。config 自身への Write / 無関係 Bash は exit 0、gated パス書込は既定設定の phase-gate 判定で fail-closed 維持(`invalid-config-fail-open.integration.test.ts` の WI-333 テストで固定) | invalid-json と同じ「警告 + 既定値で続行」 — 達成済み |
|
|
70
|
+
| G2 | **invalid-json の検査系が fail-open**(invalid-schema より緩い) | `ConfigParseError` が `ConfigPersistenceError` 経路で全コマンド fail-open(exit 0) | 検査系は invalid-schema と同様 fail-closed が原則に忠実。ただし挙動変更は breaking のため要判断 |
|
|
71
|
+
|
|
72
|
+
いずれも `scripts/harness/__tests__/integration/harness-api/invalid-config-fail-open.integration.test.ts` のテストで挙動を固定済み(G1 は fail-open 期待に反転済み)。挙動を修正する際はテストの期待値反転とこの表の更新を同一変更で行うこと。
|
|
73
|
+
|
|
74
|
+
## Consequences
|
|
75
|
+
|
|
76
|
+
- doctor は config 状態を常に報告する(JSON: `configStatus`、human: `Config:` 行、finding: `config-status`)。config 不在・不正のまま GREEN を報告することはなくなる(missing は warn、invalid-* は red で exit 1)。
|
|
77
|
+
- 許可表が仕様となったため、fail-open/fail-closed の変更は本 ADR の改訂を伴う。
|
|
78
|
+
- 経緯: WI-314(hook/doctor fail-open 化)→ WI-323(hook adapter の JSON parse fail-open)→ WI-325(`ConfigParseError` 導入)→ WI-330(本 ADR・doctor 可視化・missing 系テスト固定)→ WI-333(G1 解消: hook adapter の ENOENT fail-open、github#40 完全解消)。
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
adr_id: "039"
|
|
3
|
+
title: "hook の authorization 単位は観測可能な state とし、skill 名を伝播しない"
|
|
4
|
+
status: Accepted
|
|
5
|
+
date: 2026-08-06
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# hook の authorization 単位は観測可能な state とし、skill 名を伝播しない
|
|
9
|
+
|
|
10
|
+
<!-- @work-item-id WI-375 -->
|
|
11
|
+
<!-- @work-item-id WI-376 -->
|
|
12
|
+
|
|
13
|
+
## Context
|
|
14
|
+
|
|
15
|
+
pre-tool-use hook には「呼び出し元 skill 名」を受け取る口が 2 つ実装されていた。
|
|
16
|
+
|
|
17
|
+
- hook input JSON の `caller_skill` フィールド(`scripts/harness/agent-integration/presentation/pre-tool-use-hook.ts` の `PreToolUseHookInput`)
|
|
18
|
+
- 環境変数 `PHASEGATE_CALLER_SKILL`(同ファイルの `input.caller_skill ?? process.env.PHASEGATE_CALLER_SKILL`)
|
|
19
|
+
|
|
20
|
+
WI-202 はこれを「block guidance の出し分けにのみ使う optional context」として設計した。しかし **値を供給する producer はどこにも存在しない**。
|
|
21
|
+
|
|
22
|
+
1. **Claude Code の PreToolUse payload に skill 情報が無い。** hook に渡る JSON は `session_id` / `transcript_path` / `cwd` / `hook_event_name` / `tool_name` / `tool_input` / `permission_mode` であり、実行中の skill を示すフィールドは規定されていない。`caller_skill` は phasegate が独自に期待しているだけの未定義キーである。
|
|
23
|
+
2. **配布物の `.claude/settings.json` にも `.codex/hooks.json` にも `PHASEGATE_CALLER_SKILL` を設定する env 宣言が無い。** skill 側(`skills/*/SKILL.md`)にも export する手順は書かれていない。
|
|
24
|
+
3. 結果として `callerSkill` は常に `undefined` で hook に届き、`callerSkill === "quick-implementor"` の分岐は **到達不能なデッドパス**だった。それでも `HandlePreToolUseUseCase` を直接 new して `callerSkill: "quick-implementor"` を渡す統合テストは緑になっており、「実運用では一度も通らない経路が、テスト上は被覆されている」という**偽の被覆**を生んでいた(GitHub #27 Defect C / #44 課題 1)。
|
|
25
|
+
|
|
26
|
+
WI-354 で guidance 分岐の一次条件を `dominantCategory`(Quick Mode の分類結果)に変更したため実害は解消済みだが、「カテゴリ未確定時のみ `callerSkill` を見る」フォールバックと受け口自体は残っていた。
|
|
27
|
+
|
|
28
|
+
さらに本質的な問題として、**skill 名は仮に producer を作っても認証されない自己申告値である**。hook を呼ぶのはエージェント自身であり、`caller_skill: "quick-implementor"` を名乗ることは誰にでもできる。ADR-030(injection threat model)の trust root の考え方に照らせば、エージェントが自由に制御できる文字列は防御判定の入力にできない。GitHub #26 で「skill 名で full mode を許可する」案を退け、`phasegate config:plan --apply` のような **managed command 経路**(phasegate 自身が実行し、その痕跡をファイルとして残す経路)に倒した結論と同根である。
|
|
29
|
+
|
|
30
|
+
## Decision
|
|
31
|
+
|
|
32
|
+
### 1. authorization / guidance の入力は「hook が自ら観測・検証できる state」に限る
|
|
33
|
+
|
|
34
|
+
pre-tool-use hook の判定と案内は、以下の観測可能な state のみを入力とする。
|
|
35
|
+
|
|
36
|
+
| state | 実体 | 観測方法 |
|
|
37
|
+
|-------|------|---------|
|
|
38
|
+
| 書き込み対象パスと変更カテゴリ | `tool_input` から抽出した対象パス、Quick Mode の `dominantCategory` | hook 自身が分類(`quick-mode` unit) |
|
|
39
|
+
| Full Mode session marker | `phasegate session begin --mode full --unit … --work-item …` が作るセッションファイル(WI / unit / 期限) | `FileSystemFullModeSessionQueryAdapter` がファイルを読む |
|
|
40
|
+
| 設計文書の存在 | `docs/product/construction/{unit}/logical_design.md` / `domain_model.md` | `PhaseGateQueryAdapter.checkDesignDocsExist` |
|
|
41
|
+
| WI の承認・reflection 状態 | inception 配下の WI 文書、story reflection | 各 query adapter がファイルを読む |
|
|
42
|
+
| 解決済み config | `phasegate.config.json` + 防御プリセット解決結果 | config-foundation 経由 |
|
|
43
|
+
| baseline / attestation | `.phasegate/baseline.json`、attestation マニフェスト | ci-governance / attestation unit |
|
|
44
|
+
|
|
45
|
+
これらはすべて「phasegate 自身の managed command が書いた痕跡」または「リポジトリの実ファイル」であり、hook 実行時点で独立に再検証できる。
|
|
46
|
+
|
|
47
|
+
### 2. エージェントの自己申告 identity は受け取らない
|
|
48
|
+
|
|
49
|
+
skill 名・エージェント種別のような自己申告値は、**authorization にも guidance 分岐にも使わない**。よって受け口を削除する。
|
|
50
|
+
|
|
51
|
+
- `PreToolUseHookInput.caller_skill` フィールドを削除する
|
|
52
|
+
- 環境変数 `PHASEGATE_CALLER_SKILL` の参照を削除する
|
|
53
|
+
- `HandlePreToolUseInput.callerSkill` を削除する
|
|
54
|
+
- `shouldGuideQuickModeRelax` の `callerSkill === "quick-implementor"` フォールバックを削除する(判定は `dominantCategory` のみ)
|
|
55
|
+
- `callerSkill` を注入するだけで成立していた統合テストを削除し、代わりに **category ベースの分岐が WI-354 の挙動のまま不変であること**を回帰テストで固定する
|
|
56
|
+
|
|
57
|
+
「使われていない口を念のため残す」ことは、偽の被覆を生み、将来「値さえ入れれば許可が広がる」という誤った拡張余地を残すため採らない。
|
|
58
|
+
|
|
59
|
+
### 3. skill context が必要になった場合の唯一の入口は managed command
|
|
60
|
+
|
|
61
|
+
将来「どの skill が書いているか」に応じて挙動を変える必要が生じた場合も、hook input に新しい自己申告フィールドを足すことはしない。skill 側に `phasegate session begin` 等の **managed command を実行させ、その結果として生まれた検証可能な state**(session marker のフィールド)を hook が読む方式のみを採る。この経路では phasegate が引数を検証し、期限・unit・WI を自ら記録するため、値の出所が hook から追跡できる。
|
|
62
|
+
|
|
63
|
+
## Consequences
|
|
64
|
+
|
|
65
|
+
- pre-tool-use hook の入力契約から `caller_skill` が消える。未定義キーを送っていた呼び出し元があっても、hook は追加キーを無視するため互換性は壊れない(そもそも producer が存在しない)。
|
|
66
|
+
- Full Mode ブロック時の復旧案内は `dominantCategory` のみから決まる。`bugfix` / `docs` / `test` / `config` は quick-mode-relax 案内、`feature` / `domain` / `api` および**カテゴリ未確定**は `/story-implementor` 案内という WI-354 の挙動が唯一の仕様となる(カテゴリ未確定時に skill 名で分岐が変わる可能性が消える)。
|
|
67
|
+
- 「テストは緑だが実運用では到達しない」経路が 1 件減る。今後 hook の分岐を追加する際は、入力が上表の observable state に由来するかを ADR の基準として確認する。
|
|
68
|
+
- 経緯: WI-202(受け口の追加)→ WI-206(skill 名を許可条件にしない方針の調査)→ #26(managed command 経路への結論)→ WI-354(guidance の category ベース化)→ 本 ADR(受け口の削除と原則の明文化)。
|
|
69
|
+
|
|
70
|
+
## Alternatives
|
|
71
|
+
|
|
72
|
+
1. **producer を実装する(`.claude/settings.json` の env に `PHASEGATE_CALLER_SKILL` を埋める / skill 側で export させる)。** 値はエージェントが自由に設定できる自己申告であり、authorization の入力としては防御にならない。guidance 専用に限定しても、配布物の env 設定はユーザーの settings 編集に依存し、欠落時に静かに分岐が消えるため、category ベース判定より劣る。
|
|
73
|
+
2. **現状維持(受け口を残したまま到達不能分岐を放置)。** デッドパスと偽の被覆が残り、`callerSkill` を「渡せば許可が変わるフック」と誤解した拡張を招く。
|
|
74
|
+
3. **Claude Code 側の payload 拡張を待つ。** phasegate は AI 非依存(ADR-006)を掲げており、特定エージェントの payload 拡張に防御設計を依存させない。
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
adr_id: "040"
|
|
3
|
+
title: "Quick Mode の実効設定を防御プリセット解決経由で決定する"
|
|
4
|
+
status: Accepted
|
|
5
|
+
date: 2026-08-06
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Quick Mode の実効設定を防御プリセット解決経由で決定する
|
|
9
|
+
|
|
10
|
+
<!-- @work-item-id WI-375 -->
|
|
11
|
+
<!-- @work-item-id WI-377 -->
|
|
12
|
+
<!-- @work-item-id WI-378 -->
|
|
13
|
+
|
|
14
|
+
## Context
|
|
15
|
+
|
|
16
|
+
ADR-007 は `phasegate.config.json` を品質設定の Single Source of Truth と定め、防御プリセット(`minimal` / `standard` / `strict`)の解決は config-foundation の `PresetResolutionService` + `PresetDefinitionStore` が担う。ところが Quick Mode の実効経路である `HarnessConfigQuickModeConfigAdapter`(`scripts/harness/quick-mode/infrastructure/adapters/harness-config-quick-mode-config-adapter.ts`)は、**`phasegate.config.json` を `fs.readFile` + `JSON.parse` で直接読み、preset 解決を経由していなかった**。
|
|
17
|
+
|
|
18
|
+
その結果:
|
|
19
|
+
|
|
20
|
+
- `scripts/harness/config-foundation/infrastructure/presets/*.json` の `quickMode` セクションは**どこからも読まれないデッド宣言**だった。
|
|
21
|
+
- 実効既定値は adapter 内のハードコード定数 `DEFAULT_QUICK_MODE_CONFIG` にあり、宣言(presets)と実効値(adapter)が二重管理されていた。
|
|
22
|
+
- WI-353 で `allowedCategories` の乖離(presets が `["bugfix"]`、実効値が 4 カテゴリ)を是正し契約テストで再発を検知できるようにしたが、`maintainedLayers` / `relaxedGates` は依然乖離していた(presets: `["L1","L2"]` / `[]`、実効値かつ `docs/guide/configuration.md` の記載: `["L1","L2-002","L2-003","L2-014","L3-001"]` / `["L2-001","L3-002","L3-003","L3-004","L4"]`)。
|
|
23
|
+
- 「防御プリセットごとに Quick Mode の強度を変える」(例: `strict` では `allowedCategories` を絞る)という設計は、経路が繋がっていないため実現不能だった。
|
|
24
|
+
|
|
25
|
+
GitHub #44 課題 2。**既存プロジェクトの実効挙動を変えない移行**が絶対条件である(#27 の再発防止)。
|
|
26
|
+
|
|
27
|
+
## Decision
|
|
28
|
+
|
|
29
|
+
### 1. Quick Mode 設定は preset 解決結果(resolved config)から決定する
|
|
30
|
+
|
|
31
|
+
`HarnessConfigQuickModeConfigAdapter` は `PresetDefinitionStore` + `PresetResolutionService` を使い、`project.preset` に対応する preset 定義と `phasegate.config.json` の `quickMode` を config-foundation と同一の merge 規則(`deepMerge`: 配列はキー単位で全置換、未宣言キーは preset 値を継承)で解決した結果を使う。
|
|
32
|
+
|
|
33
|
+
これにより presets の `quickMode` 宣言が実際に読まれ、防御プリセットごとに Quick Mode 強度を変える設計が有効化される。
|
|
34
|
+
|
|
35
|
+
### 2. 移行時は挙動不変を絶対条件とし、presets の宣言値を実効既定値に揃える
|
|
36
|
+
|
|
37
|
+
経路の載せ替えだけを行うと、`quickMode` を書いていない既存プロジェクトの `maintainedLayers` / `relaxedGates` が preset の宣言値(`["L1","L2"]` / `[]`)に変わってしまう。これは「Quick Mode で L2-001 phase-gate が維持され、L3-001 が維持されなくなる」という実挙動の変化であり、許容しない。
|
|
38
|
+
|
|
39
|
+
したがって **3 プリセットの `quickMode` 宣言を実効既定値(= `docs/guide/configuration.md` が既定として記載している値)に揃える**。WI-353 が `allowedCategories` に対して行った是正を、`maintainedLayers` / `relaxedGates` にも適用する形になる。逆方向(実効値を preset 宣言に合わせる)は既存利用者への破壊的変更であり採らない。
|
|
40
|
+
|
|
41
|
+
防御プリセットごとの Quick Mode 強度差(`strict` で絞る等)は**本 ADR では導入しない**。経路を繋ぐ変更と挙動を変える変更を同一コミットに混ぜないためである。今後差をつける場合は presets の該当ファイルのみを変更すれば実効値に反映される。
|
|
42
|
+
|
|
43
|
+
### 3. 解決不能時は従来どおりの既定値に fail-open する
|
|
44
|
+
|
|
45
|
+
`project.preset` が未知・未宣言、または preset 解決が例外を投げる config(他セクションが壊れている等)では、従来と同じく adapter 内の既定値 + raw `quickMode` の per-key フォールバックで動作する。ADR-038 §3-1 の「hook は config がどんな状態でも完走する」原則を維持し、preset 解決の導入によって**新たな遮断経路を作らない**。
|
|
46
|
+
|
|
47
|
+
`HarnessConfigNotFoundError`(ENOENT)と `HarnessConfigParseError`(JSON 不正)は adapter の公開契約として維持する。
|
|
48
|
+
|
|
49
|
+
### 4. WI-353 の契約テストは「宣言が実際に読まれること」の検証に昇格させる
|
|
50
|
+
|
|
51
|
+
WI-353 のテストは「preset 宣言値 == adapter のハードコード既定値」という**二重管理の整合**を検査していた。経路が繋がった後は、`quickMode` を持たない config に対する adapter の実効値が preset 宣言値そのものであることを 3 プリセット分検証する形に置き換える。これによりデッド宣言の解消自体が回帰テストで固定される。
|
|
52
|
+
|
|
53
|
+
あわせて「防御プリセット × `quickMode` キー有無 × 明示 override 有無」のマトリクス回帰テストで挙動不変を固定する。
|
|
54
|
+
|
|
55
|
+
## Consequences
|
|
56
|
+
|
|
57
|
+
- presets の `quickMode` が実効値の所在(single source)になり、adapter 内の既定値は preset 解決不能時の fail-open 用フォールバックという位置づけに縮小する。
|
|
58
|
+
- `phasegate` が出力する解決済み config の `quickMode` と、Quick Mode 判定が実際に使う値が一致する(従来は前者だけが preset 由来で、後者と食い違っていた)。
|
|
59
|
+
- 挙動不変の担保はテストで行う。preset 宣言を変更すると Quick Mode の実挙動が変わるため、presets の `quickMode` 変更は破壊的変更として扱う。
|
|
60
|
+
- 経緯: WI-353(`allowedCategories` の宣言値是正・契約テスト)→ 本 ADR / WI-377(経路の載せ替えと残り 2 キーの是正)→ WI-378(契約テストの昇格)。
|
|
61
|
+
|
|
62
|
+
## Alternatives
|
|
63
|
+
|
|
64
|
+
1. **adapter の raw 読みを維持し、presets の `quickMode` セクションを削除する。** デッド宣言は消えるが、防御プリセットごとに Quick Mode 強度を変える設計余地を永久に失い、解決済み config から `quickMode` が消えて ADR-007 の Single Source of Truth と矛盾する。
|
|
65
|
+
2. **経路を載せ替え、実効値を preset 宣言(`["L1","L2"]` / `[]`)に合わせる。** 既存プロジェクトの Quick Mode 緩和範囲が無告知で変わる破壊的変更であり、#27 と同種の事故を再発させる。
|
|
66
|
+
3. **`LoadResolvedConfigUseCase` をそのまま呼ぶ(AJV schema 検証込みの完全経路)。** schema 違反 config で Quick Mode 判定が新たに例外を投げるようになり、ADR-038 の fail-open 表を変更してしまう。本 ADR は domain service(`PresetResolutionService`)と preset 定義のみを利用し、検証の厳格度は変えない。
|
|
@@ -127,7 +127,7 @@ npx phasegate baseline --paths "scripts/harness/**/*.ts,src/**/*.ts"
|
|
|
127
127
|
|
|
128
128
|
| Command | Options | Description |
|
|
129
129
|
|---|---|---|
|
|
130
|
-
| `scaffold-design` | `--unit <id>` `--phase <logical\|domain\|uiux\|unit-test\|it-test>` `--force` `--json` | `templates/*.template.md` を読み取り `{{unit}}` を `--unit` 値で置換して `
|
|
130
|
+
| `scaffold-design` | `--unit <id>` `--phase <logical\|domain\|uiux\|unit-test\|it-test>` `--force` `--json` | `templates/*.template.md` を読み取り `{{unit}}` を `--unit` 値で置換して `{paths.designDocs}/{unit}/*.md` を生成する。既存ファイルは `--force` なしでは保護(exit 2)。|
|
|
131
131
|
|
|
132
132
|
### `scaffold-design` の使い方
|
|
133
133
|
|
|
@@ -146,21 +146,103 @@ npx phasegate scaffold-design --unit harness-api --phase logical --force
|
|
|
146
146
|
npx phasegate scaffold-design --unit harness-api --phase logical --json
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
生成先と対応テンプレ(生成先の `docs/product/construction` 部分は
|
|
150
|
+
`phasegate.config.json` の `paths.designDocs` に追従する。WI-369 以前は
|
|
151
|
+
ハードコードされており、`paths.designDocs` を移設した PJ では scaffold 先と
|
|
152
|
+
フェーズゲートの検査先がズレていた):
|
|
150
153
|
|
|
151
154
|
| `--phase` | 生成先 | テンプレ |
|
|
152
155
|
|---|---|---|
|
|
153
|
-
| `logical` | `
|
|
154
|
-
| `domain` | `
|
|
155
|
-
| `uiux` | `
|
|
156
|
-
| `unit-test` | `
|
|
157
|
-
| `it-test` | `
|
|
156
|
+
| `logical` | `{paths.designDocs}/{unit}/logical_design.md` | `templates/logical_design.template.md` |
|
|
157
|
+
| `domain` | `{paths.designDocs}/{unit}/domain_model.md` | `templates/domain_model.template.md` |
|
|
158
|
+
| `uiux` | `{paths.designDocs}/{unit}/uiux_design.md` | `templates/uiux_design.template.md` |
|
|
159
|
+
| `unit-test` | `{paths.designDocs}/{unit}/unit_test_design.md` | `templates/unit_test_design.template.md` |
|
|
160
|
+
| `it-test` | `{paths.designDocs}/{unit}/it_test_design.md` | `templates/it_test_design.template.md` |
|
|
158
161
|
|
|
159
162
|
exit code は `0` = 生成成功 / 上書き成功、`2` = 既存ファイルあり(`--force` 無)
|
|
160
163
|
または引数不正。
|
|
161
164
|
|
|
162
165
|
---
|
|
163
166
|
|
|
167
|
+
## Scaffold Inception (Phase Gate Level-1 Templates)
|
|
168
|
+
|
|
169
|
+
| Command | Options | Description |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| `scaffold-inception` | `--kind <doc-kind>` `--dry-run`(既定)/ `--apply` `--force` `--json` | L2 の Level-1 フェーズゲートが要求する inception plan 文書と `product_overview.md` の雛形を生成する。既存ファイルは `--force` なしでは保護(exit 2)。|
|
|
172
|
+
|
|
173
|
+
WI-368(GitHub issue #42)で導入。`scaffold-design` は `--unit` を必須とする
|
|
174
|
+
construction 文書専用で、unit 軸を持たない `_shared/*_plan.md` や product 直下の
|
|
175
|
+
文書を表現できないため、別コマンドとして分離している。
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
# 何がどこに生成されるかを確認(既定は dry-run)
|
|
179
|
+
npx phasegate scaffold-inception --kind product-overview-plan
|
|
180
|
+
|
|
181
|
+
# 実際に書き込む
|
|
182
|
+
npx phasegate scaffold-inception --kind product-overview-plan --apply
|
|
183
|
+
npx phasegate scaffold-inception --kind product-overview --apply
|
|
184
|
+
|
|
185
|
+
# 既存ファイルを意図的に上書き
|
|
186
|
+
npx phasegate scaffold-inception --kind product-overview-plan --apply --force
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
生成先と対応テンプレ:
|
|
190
|
+
|
|
191
|
+
| `--kind` | 生成先 | テンプレ |
|
|
192
|
+
|---|---|---|
|
|
193
|
+
| `product-overview-plan` | `{paths.inceptionDocs}/_shared/product_overview_plan.md` | `templates/product_overview_plan.template.md` |
|
|
194
|
+
| `product-overview` | `{paths.designDocs}/../product_overview.md` | `templates/product_overview.template.md` |
|
|
195
|
+
| `story-writer-plan` | `{paths.inceptionDocs}/_shared/story_writer_plan.md` | `templates/story_writer_plan.template.md` |
|
|
196
|
+
| `story-mapping-plan` | `{paths.inceptionDocs}/_shared/story_mapping_plan.md` | `templates/story_mapping_plan.template.md` |
|
|
197
|
+
| `unit-design-plan` | `{paths.inceptionDocs}/_shared/unit_design_plan.md` | `templates/unit_design_plan.template.md` |
|
|
198
|
+
|
|
199
|
+
### テンプレート = ゲート合格保証
|
|
200
|
+
|
|
201
|
+
plan テンプレートは QA セクション見出しを必ず含むため、`--apply` の生成物は
|
|
202
|
+
**無編集のまま** `planningMode: "interactive"`(既定)の Level-1 ゲートを通る。
|
|
203
|
+
この性質はラウンドトリップテストで機械的に検証されている。
|
|
204
|
+
|
|
205
|
+
`planningMode: "embedded-qa"` では `[Question]` と `[Answer]` の個数一致に加え
|
|
206
|
+
`[Answer]` に本文があることが要求されるため、生成直後の雛形は**通らない**。
|
|
207
|
+
これは意図した挙動であり、テンプレートが人間の承認証跡を偽造しないための境界。
|
|
208
|
+
|
|
209
|
+
### スコープ外の doc-kind
|
|
210
|
+
|
|
211
|
+
`user_stories.md` / `user_story_mapping.md` / `units/{unit}_unit.md` /
|
|
212
|
+
`units/integration_contract.md` と、unit スコープの plan 文書
|
|
213
|
+
(`{unit}/domain_model_plan.md` 等、Level-2 ゲート)は未収録。段階投入とする。
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Templates (Bundled Template Access)
|
|
218
|
+
|
|
219
|
+
| Command | Options | Description |
|
|
220
|
+
|---|---|---|
|
|
221
|
+
| `templates list` | `--json` | 同梱テンプレート名を一覧表示する。|
|
|
222
|
+
| `templates show <name>` | — | テンプレート本文を stdout に出力する。|
|
|
223
|
+
|
|
224
|
+
WI-367(GitHub issue #42)で導入。consumer プロジェクトでは phasegate は
|
|
225
|
+
`node_modules/phasegate/templates/` に入り、エージェントの sandbox が
|
|
226
|
+
`node_modules` の Read を deny していると **テンプレート実体に到達できない**。
|
|
227
|
+
`skills list` / `skills info` と同型の stdout 経路でこれを解消する。
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
npx phasegate templates list
|
|
231
|
+
npx phasegate templates show product_overview_plan
|
|
232
|
+
|
|
233
|
+
# 生成物としてそのまま保存できる(本文以外を混ぜない)
|
|
234
|
+
npx phasegate templates show logical_design > docs/product/construction/foo/logical_design.md
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
`<name>` は同梱テンプレートの catalog(`templates/` の readdir 結果)と
|
|
238
|
+
**完全一致照合**する。ユーザー入力文字列がパス構成要素になる経路を持たないため、
|
|
239
|
+
`../../package.json` のようなパスは exit 2 で拒否され内容も出力されない。
|
|
240
|
+
|
|
241
|
+
exit code は `0` = 成功、`2` = name 未指定 / 不正な name / 未知の name /
|
|
242
|
+
未知のサブコマンド。
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
164
246
|
## Work Item Migration
|
|
165
247
|
|
|
166
248
|
`docs/inception/` 配下の work item directory を **統一 `WI-XXX` レイアウト**へ移行する CLI。
|
|
@@ -41,7 +41,8 @@ The plan identifies target fields, managed artifacts, commands, validation, risk
|
|
|
41
41
|
"mixedCategories": true,
|
|
42
42
|
"newDomainFile": true,
|
|
43
43
|
"apiContractChange": true
|
|
44
|
-
}
|
|
44
|
+
},
|
|
45
|
+
"categoryOverrides": {} // Optional. e.g. { "docs": ["results/**", "notes/**"] }
|
|
45
46
|
},
|
|
46
47
|
"phaseDependencies": {
|
|
47
48
|
"preset": "standard", // "full" | "standard" | "minimal" | "custom" ("default" -> "full")
|
|
@@ -198,10 +199,13 @@ The five layers are:
|
|
|
198
199
|
|
|
199
200
|
| Sub-field | Type | Default | Description |
|
|
200
201
|
|------------------------|------------|-----------------------------------------|-----------------------------------------------------------------------------|
|
|
201
|
-
| `allowedCategories` | `string[]` | `["bugfix", "docs", "test", "config"]` | Change categories permitted under Quick Mode. Any category outside this list requires the full `story-implementor` workflow. |
|
|
202
|
+
| `allowedCategories` | `string[]` | `["bugfix", "docs", "test", "config"]` | Change categories permitted under Quick Mode. Any category outside this list requires the full `story-implementor` workflow. **Enum** — only `bugfix`, `docs`, `test`, `config`, `feature`, `domain`, `api` are accepted; anything else is a config error. |
|
|
202
203
|
| `maintainedLayers` | `string[]` | `["L1", "L2-002", "L2-003", "L2-014", "L3-001"]` | Exact validator IDs that remain enforced in Quick Mode. `L1` is the only layer shorthand; `L2` is not expanded. |
|
|
203
204
|
| `relaxedGates` | `string[]` | `["L2-001", "L3-002", "L3-003", "L3-004", "L4"]` | Validators/layers relaxed by Quick Mode. `L4` means all L4 validators are skipped. |
|
|
204
205
|
| `fullModeRequiredWhen` | `object` | all flags `true` | Conditions that force a Quick Mode change to escalate to the full `/story-implementor` flow. See below. |
|
|
206
|
+
| `categoryOverrides` | `object` | `{}` | Maps a change category to a list of globs, so project-specific paths land in a category the built-in table cannot infer. See below. |
|
|
207
|
+
|
|
208
|
+
The defaults above come from the defense preset declared in `project.preset`; Quick Mode resolves its effective settings through the same preset resolution as the rest of the config (ADR-040). Sub-fields you declare in `phasegate.config.json` replace the preset value for that sub-field only (arrays are replaced wholesale, not merged); sub-fields you omit keep the preset value. All three shipped presets (`minimal` / `standard` / `strict`) currently declare the same Quick Mode values, so switching the defense preset does not change Quick Mode strength today. <!-- @work-item-id WI-377 -->
|
|
205
209
|
|
|
206
210
|
##### `fullModeRequiredWhen`
|
|
207
211
|
|
|
@@ -217,6 +221,43 @@ Introduced in ISSUE-006 Story A (v0.63.0) and wired into the pre-tool-use hook b
|
|
|
217
221
|
|
|
218
222
|
Set a flag to `false` only when the project intentionally accepts the risk of merging that category of change without the design ceremony -- e.g. an early-stage prototype where new domain files are expected to churn.
|
|
219
223
|
|
|
224
|
+
##### `allowedCategories` is an enum
|
|
225
|
+
|
|
226
|
+
<!-- @work-item-id WI-373 -->
|
|
227
|
+
|
|
228
|
+
`allowedCategories` accepts only the seven `ChangeCategory` values. A misspelling such as `"typoo"` is rejected as a config error rather than silently ignored, and case is not normalised — `"Docs"` is an error, not an alias for `"docs"`, because classification keys are always lowercase and a normalised spelling would only hide the same dead setting.
|
|
229
|
+
|
|
230
|
+
If the quickMode section is invalid, the pre-tool-use hook fails **closed**: writes are blocked with an explicit "quickMode 設定が不正なため" reason until the config is fixed. (A *missing* config still fails open, so greenfield adoption is unaffected.)
|
|
231
|
+
|
|
232
|
+
##### `categoryOverrides`
|
|
233
|
+
|
|
234
|
+
<!-- @work-item-id WI-372 -->
|
|
235
|
+
|
|
236
|
+
```jsonc
|
|
237
|
+
{
|
|
238
|
+
"quickMode": {
|
|
239
|
+
"categoryOverrides": {
|
|
240
|
+
"docs": ["results/**", "notes/**"]
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Keys are the seven `ChangeCategory` values; values are lists of globs matched against project-relative POSIX paths. Supported syntax is `**` (crosses `/`), `*` (does not cross `/`), and `?` (one non-`/` character); brace expansion and negation are not supported.
|
|
247
|
+
|
|
248
|
+
| Rule | Behaviour |
|
|
249
|
+
|------|-----------|
|
|
250
|
+
| Precedence | Overrides are evaluated **before** the built-in classification table, so an explicit declaration beats inference. |
|
|
251
|
+
| Downgrade guard | A path the built-in rules classify as `domain` or `api` cannot be moved to a lower-risk category; an override may only raise the risk. |
|
|
252
|
+
| Valid keys | All seven categories, including `domain` / `api` / `feature`. Assigning a path to those *hardens* the gate, since they sit outside the default `allowedCategories`. |
|
|
253
|
+
| Rejection rules | `NEW_DOMAIN` and `API_CONTRACT` remain path-based and ignore overrides entirely. Only `MIXED_CHANGES` uses the override-adjusted category. |
|
|
254
|
+
| Ties | If two categories match the same path, the higher-risk one wins, so the result never depends on JSON key order. |
|
|
255
|
+
|
|
256
|
+
Overrides apply identically to the pre-tool-use hook, `check-change-category`, and `ci-check --quick`. Leaving `categoryOverrides` unset reproduces the pre-existing classification exactly. See [Quick vs Full Mode](quick-vs-full-mode.md#overriding-the-table-for-project-specific-paths) for worked reasoning.
|
|
257
|
+
|
|
258
|
+
<!-- @work-item-id WI-380 -->
|
|
259
|
+
`categoryOverrides` goes through the same preset resolution as the other Quick Mode sub-fields (ADR-040), and merges one level deeper than they do: the merge unit is the **category key**, not the whole object. None of the three shipped presets declares `categoryOverrides`, so today whatever you write in `phasegate.config.json` is the effective value verbatim. If a preset does declare it, a category you also declare replaces that category's glob list wholesale, while categories you leave out keep the preset's globs.
|
|
260
|
+
|
|
220
261
|
<!-- @work-item-id WI-159 -->
|
|
221
262
|
Quick Mode uses exact validator IDs for `maintainedLayers`. To keep all L2 validators active, list `L2-001`, `L2-002`, `L2-003`, `L2-013`, `L2-014`, and `L2-015` explicitly. The default keeps metadata, test-quality, work-item status, and security checks active while skipping phase-gate, CLI E2E coverage, contract traceability coverage, performance, coverage, nyquist, and L4 scheduled validators.
|
|
222
263
|
|
|
@@ -43,6 +43,8 @@ npx phasegate hook post-tool-use < payload.json
|
|
|
43
43
|
npx phasegate hook stop < payload.json
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
> **Config 状態と許可ポリシー**: `phasegate.config.json` が不在・破損している状態で各 hook / コマンドがどう振る舞うか(fail-open / fail-closed の別、自己修復経路、doctor の `configStatus` 報告)は [ADR-038](../ADR/038-config-state-operation-permission-policy.md) の許可表で仕様化されている。
|
|
47
|
+
|
|
46
48
|
### L0-B: Husky git hooks
|
|
47
49
|
|
|
48
50
|
Deployed by `phasegate init --with-husky` into `.husky/`.
|
|
@@ -71,6 +71,64 @@ npx phasegate session end --work-item <WI-XXX>
|
|
|
71
71
|
|
|
72
72
|
The PreToolUse hook validates `.phasegate/session.json` by TTL, unit, and dominant category. The `/story-implementor` skill is still the design/TDD guide; the session marker is the authorization the hook can actually observe. <!-- @work-item-id WI-206 -->
|
|
73
73
|
|
|
74
|
+
### How a path becomes a category
|
|
75
|
+
|
|
76
|
+
The classifier is **path-based, not intent-based**. It never reads your commit message or your stated purpose; it looks at the write target's path and its change kind (`CREATE` / `MODIFY` / `DELETE`). Rules are evaluated in this order, first match wins:
|
|
77
|
+
|
|
78
|
+
| Order | Match | Category |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| 0 | `quickMode.categoryOverrides` glob match (see below) | the configured category |
|
|
81
|
+
| 1 | `*.config.json`, `*.config.ts`, `phasegate.config.json` | `config` |
|
|
82
|
+
| 2 | `.github/workflows/*.yml`, `.github/workflows/*.yaml` | `config` |
|
|
83
|
+
| 3 | Repo-root bootstrap files: `.gitignore`, `.gitattributes`, `.editorconfig`, `.npmrc`, `.nvmrc`, `tsconfig.json`, `tsconfig.*.json`, anything under `.husky/` | `config` |
|
|
84
|
+
| 4 | Comment/whitespace-only diff | `docs` |
|
|
85
|
+
| 5 | `*port.ts`, `*adapter.ts` | `api` |
|
|
86
|
+
| 6 | `__tests__/`, `*.test.ts`, `*.spec.ts` | `test` |
|
|
87
|
+
| 7 | Anything under a `domain/` directory | `domain` |
|
|
88
|
+
| 8 | Anything under `docs/` | `docs` |
|
|
89
|
+
| 9 | `skills/**/*.md` | `docs` |
|
|
90
|
+
| 10 | Any other `CREATE` | `feature` |
|
|
91
|
+
| 11 | Any other `MODIFY` / `DELETE` | `bugfix` |
|
|
92
|
+
|
|
93
|
+
Two consequences worth internalising:
|
|
94
|
+
|
|
95
|
+
- The `config` rules are an **explicit allow-list**, not a wildcard. `.github/dependabot.yml`, `packages/app/.gitignore`, or a `Dockerfile` are *not* `config` — created fresh they fall through to `feature` and are blocked. This is deliberate fail-closed behaviour: widening the list is a config decision, not an inference the hook makes for you.
|
|
96
|
+
- `feature` is the fallback for **every new non-test source file**. "It's only a tiny helper" does not change the classification.
|
|
97
|
+
|
|
98
|
+
When the hook blocks a write, the message now names the deciding category and change kind per path (`foo.ts (category=feature, changeKind=CREATE)`), and — for `bugfix` / `docs` / `test` / `config` blocks — points at `allowedCategories` and `config:plan --intent quick-mode-relax` rather than at `/story-implementor`. <!-- @work-item-id WI-352 --> <!-- @work-item-id WI-354 -->
|
|
99
|
+
|
|
100
|
+
### Overriding the table for project-specific paths
|
|
101
|
+
|
|
102
|
+
<!-- @work-item-id WI-372 -->
|
|
103
|
+
|
|
104
|
+
The built-in table has no knowledge of your project's own conventions. A directory such as `results/` or `notes/` holds documents, but the classifier sees an unknown path: `MODIFY` falls through to `bugfix`, and `CREATE` falls through to `feature` — a category that cannot be added to `allowedCategories` at all, so the write is permanently Full Mode.
|
|
105
|
+
|
|
106
|
+
`quickMode.categoryOverrides` maps a category to a list of globs:
|
|
107
|
+
|
|
108
|
+
```jsonc
|
|
109
|
+
{
|
|
110
|
+
"quickMode": {
|
|
111
|
+
"allowedCategories": ["bugfix", "docs", "test", "config"],
|
|
112
|
+
"categoryOverrides": {
|
|
113
|
+
"docs": ["results/**", "notes/**"]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Four rules govern how overrides interact with the table above:
|
|
120
|
+
|
|
121
|
+
1. **Overrides are evaluated first — order 0 in the table.** They are an explicit declaration about your repository, so they beat inference. Without this, `notes/deploy.config.json` would still be caught by rule 1 and the setting would look broken.
|
|
122
|
+
2. **Overrides cannot downgrade `domain` or `api`.** If the built-in rules classify a path as `domain` (rule 7) or `api` (rule 5), an override may only raise the risk, never lower it. `{"docs": ["scripts/**"]}` will *not* turn a domain file into a doc. The weakening vector is closed by construction, not by convention.
|
|
123
|
+
3. **All seven categories are valid override keys**, including `domain`, `api`, and `feature`. Assigning a path to one of those only makes the gate harder to pass, since they sit outside the default `allowedCategories` — it is a hardening lever, e.g. `{"domain": ["packages/core/**"]}`.
|
|
124
|
+
4. **`NEW_DOMAIN` and `API_CONTRACT` still apply.** The three rejection rules stay path-based and ignore overrides entirely, so a misconfigured override cannot let a new domain file or a port/adapter change slip through. Only `MIXED_CHANGES` consumes the override-adjusted category.
|
|
125
|
+
|
|
126
|
+
If two override categories match the same path, the higher-risk one wins, so the result never depends on JSON key order.
|
|
127
|
+
|
|
128
|
+
Supported glob syntax is deliberately small: `**` (crosses `/`), `*` (does not cross `/`), and `?` (one non-`/` character). Brace expansion and negation are not supported. Patterns are matched against project-relative POSIX paths.
|
|
129
|
+
|
|
130
|
+
Overrides apply identically to all three consumers — the PreToolUse hook, `check-change-category`, and `ci-check --quick` — because they share one config load. With `categoryOverrides` unset the classification is exactly what it was before the feature existed.
|
|
131
|
+
|
|
74
132
|
### Dry-running the classifier
|
|
75
133
|
|
|
76
134
|
Use `check-change-category` to evaluate an arbitrary file list without actually starting an implementation:
|
|
@@ -159,16 +217,18 @@ Codify the split in your head (or in a project CLAUDE.md note) — "`domain/**`
|
|
|
159
217
|
## FAQ
|
|
160
218
|
|
|
161
219
|
**Q. I have a one-line bugfix in `domain/`. Does "any file under `domain/`" auto-trigger Full?**
|
|
162
|
-
|
|
220
|
+
Yes — mechanically. The classifier is path-based (see [How a path becomes a category](#how-a-path-becomes-a-category)), so *any* write under a `domain/` directory lands in the `domain` category, which is outside the default `allowedCategories`, and the hook blocks it with `MIXED_CHANGES`. Judgement about "it's only an off-by-one" does not enter into it. If the fix is genuinely trivial, the sanctioned routes are a Full Mode session (`phasegate session begin --mode full --unit <unit> ...`) or, when the project has decided domain edits are routinely Quick-eligible, widening `allowedCategories` via `config:plan`. Do not expect the hook to infer intent.
|
|
163
221
|
|
|
164
222
|
**Q. What if Quick discovers the change is bigger than expected mid-flight?**
|
|
165
223
|
Stop, commit nothing, and re-launch `/story-implementor`. Don't try to "finish in Quick" — you'll skip the design gate that would otherwise catch the scope creep.
|
|
166
224
|
|
|
167
225
|
**Q. Can I disable Quick Mode entirely?**
|
|
168
|
-
|
|
226
|
+
Not by emptying the list — `allowedCategories: []` is rejected as invalid config (`allowedCategories must not be empty`), so it breaks the hook rather than tightening it. Narrow it instead: `allowedCategories: ["docs"]` routes effectively everything through Full Mode while staying valid. Alternatively, leave the categories as-is and set every `quickMode.fullModeRequiredWhen.*` flag to `true` (the default) so any non-trivial scope automatically escalates.
|
|
169
227
|
|
|
170
228
|
**Q. Can I add custom categories?**
|
|
171
|
-
No. `allowedCategories`
|
|
229
|
+
No. `allowedCategories` accepts only values from a fixed enum — the seven `ChangeCategory` values `bugfix`, `docs`, `test`, `config`, `feature`, `domain`, `api`. The default allow-list is the first four; the remaining three exist so that a project can *deliberately* widen the gate (rarely a good idea), not so you can invent a new label. The enum is enforced by both the config schema and `QuickModeConfig`, so a misspelling is a loud config error, not a setting that quietly never matches. <!-- @work-item-id WI-373 -->
|
|
230
|
+
|
|
231
|
+
What you *can* customise is which paths map to which of those seven categories — see [Overriding the table for project-specific paths](#overriding-the-table-for-project-specific-paths). If your workflow needs a category outside the enum, that is evidence the change is probably Full Mode material.
|
|
172
232
|
|
|
173
233
|
---
|
|
174
234
|
|