phasegate 0.160.21 → 0.162.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +6 -1
  3. package/docs/contracts/requirement-test-matrix.schema.json +1 -1
  4. package/docs/guide/cli-reference.md +1 -1
  5. package/docs/guide/configuration.md +31 -1
  6. package/docs/guide/skills-overview.md +2 -0
  7. package/docs/principles/model-routing.md +1 -0
  8. package/docs/templates/personal/phasegate-local-config.json +4 -1
  9. package/docs/templates/project/phasegate.config.json +4 -1
  10. package/package.json +6 -3
  11. package/scripts/harness/agent-integration/domain/services/bash-write-target-extractor.ts +91 -23
  12. package/scripts/harness/agent-integration/domain/value-objects/protected-file-list.ts +4 -0
  13. package/scripts/harness/agent-integration/domain/value-objects/write-target-scope.ts +54 -5
  14. package/scripts/harness/agent-integration/infrastructure/adapters/ci-governance-baseline-grandfather-adapter.ts +25 -2
  15. package/scripts/harness/agent-integration/infrastructure/adapters/phase-gate-query-adapter.ts +17 -2
  16. package/scripts/harness/biome-ast-engine/domain/value-objects/import-graph.ts +28 -1
  17. package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +6 -1
  18. package/scripts/harness/config-foundation/domain/harness-config.ts +10 -0
  19. package/scripts/harness/config-foundation/domain/services/preset-resolution-service.ts +2 -0
  20. package/scripts/harness/config-foundation/domain/value-objects/l3-config.ts +4 -0
  21. package/scripts/harness/config-foundation/domain/value-objects/layers-config.ts +1 -1
  22. package/scripts/harness/config-foundation/domain/value-objects/project-config.ts +20 -5
  23. package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +2 -1
  24. package/scripts/harness/config-foundation/infrastructure/presets/standard.json +2 -1
  25. package/scripts/harness/config-foundation/infrastructure/presets/strict.json +2 -1
  26. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +39 -9
  27. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +39 -9
  28. package/scripts/harness/harness-api/infrastructure/adapters/phase-dependency-model-query-adapter.ts +18 -4
  29. package/scripts/harness/harness-api/infrastructure/adapters/validator-system-execution-adapter.ts +27 -4
  30. package/scripts/harness/installation/application/ports/model-delegation-port.ts +10 -0
  31. package/scripts/harness/installation/application/usecases/run-install.ts +67 -5
  32. package/scripts/harness/installation/application/usecases/run-reconcile.ts +53 -5
  33. package/scripts/harness/installation/composition-root.ts +4 -2
  34. package/scripts/harness/installation/infrastructure/adapters/skill-deployer-model-delegation-adapter.ts +16 -0
  35. package/scripts/harness/installation/presentation/cli/install-handler.ts +2 -1
  36. package/scripts/harness/main.ts +99 -13
  37. package/scripts/harness/nyquist-validation/application/dto/generate-matrix-output.ts +9 -7
  38. package/scripts/harness/nyquist-validation/domain/services/matrix-validation-service.ts +27 -2
  39. package/scripts/harness/nyquist-validation/domain/services/requirement-intent-coverage-service.ts +4 -4
  40. package/scripts/harness/nyquist-validation/domain/value-objects/intent-coverage.ts +33 -0
  41. package/scripts/harness/nyquist-validation/infrastructure/adapters/markdown-requirement-source-adapter.ts +3 -1
  42. package/scripts/harness/quick-mode/application/usecases/classify-change-category-usecase.ts +8 -3
  43. package/scripts/harness/quick-mode/composition-root.ts +3 -0
  44. package/scripts/harness/quick-mode/domain/value-objects/quick-mode-config.ts +6 -1
  45. package/scripts/harness/quick-mode/infrastructure/adapters/validator-system-quick-mode-execution-adapter.ts +18 -0
  46. package/scripts/harness/quick-mode/presentation/handlers/ci-check-quick-mode-handler.ts +51 -5
  47. package/scripts/harness/setup/skill-deployer.ts +47 -0
  48. package/scripts/harness/skill-quality/domain/services/skill-structure-validator.ts +21 -0
  49. package/scripts/harness/skill-quality/domain/value-objects/skill-structure.ts +2 -0
  50. package/scripts/harness/skill-quality/infrastructure/adapters/l1-biome-validator-adapter.ts +12 -2
  51. package/scripts/harness/skill-quality/infrastructure/adapters/l2-validator-system-adapter.ts +12 -2
  52. package/scripts/harness/traceability-model/application/usecases/apply-work-item-status-usecase.ts +6 -1
  53. package/scripts/harness/traceability-model/infrastructure/parsers/story-catalog-parser.ts +28 -9
  54. package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +76 -25
  55. package/scripts/harness/validator-system/application/use-cases/run-l4-validators-usecase.ts +20 -8
  56. package/scripts/harness/validator-system/composition-root.ts +8 -1
  57. package/scripts/harness/validator-system/domain/ports/validator-config-port.ts +2 -0
  58. package/scripts/harness/validator-system/domain/services/validator-language-capability-service.ts +64 -0
  59. package/scripts/harness/validator-system/infrastructure/adapters/file-system-security-pattern-scanner-adapter.ts +16 -6
  60. package/scripts/harness/validator-system/infrastructure/adapters/file-system-work-item-reflection-adapter.ts +10 -1
  61. package/scripts/harness/validator-system/infrastructure/adapters/harness-config-validator-config-adapter.ts +7 -1
  62. package/scripts/harness/validator-system/infrastructure/adapters/import-graph-source-analysis-adapter.ts +35 -3
  63. package/scripts/harness/validator-system/infrastructure/adapters/nyquist-ac-coverage-policy-adapter.ts +86 -14
  64. package/scripts/harness/validator-system/infrastructure/adapters/phase-dependency-phase-gate-policy-adapter.ts +29 -3
  65. package/skills/cascade-updater/SKILL.md +1 -0
  66. package/skills/codebase-mapper/SKILL.md +1 -0
  67. package/skills/codex-delegator/SKILL.md +1 -0
  68. package/skills/consistency-checker/SKILL.md +1 -0
  69. package/skills/doc-freshness-checker/SKILL.md +1 -0
  70. package/skills/domain-designer/SKILL.md +1 -0
  71. package/skills/engineering-perspective/SKILL.md +1 -0
  72. package/skills/environment-designer/SKILL.md +1 -0
  73. package/skills/implementation-planner/SKILL.md +1 -0
  74. package/skills/implementation-readiness-checker/SKILL.md +1 -0
  75. package/skills/it-test-designer/SKILL.md +1 -0
  76. package/skills/it-test-logic-designer/SKILL.md +1 -0
  77. package/skills/logical-designer/SKILL.md +1 -0
  78. package/skills/mock-designer/SKILL.md +1 -0
  79. package/skills/phasegate-config-doctor/SKILL.md +1 -0
  80. package/skills/phasegate-toolkit-guide/SKILL.md +1 -0
  81. package/skills/pointer-validator/SKILL.md +1 -0
  82. package/skills/product-architect/SKILL.md +1 -0
  83. package/skills/quick-implementor/SKILL.md +1 -0
  84. package/skills/scenario-test-designer/SKILL.md +1 -0
  85. package/skills/scenario-test-logic-designer/SKILL.md +1 -0
  86. package/skills/skill-creator/SKILL.md +1 -0
  87. package/skills/story-implementor/SKILL.md +1 -0
  88. package/skills/story-mapper/SKILL.md +1 -0
  89. package/skills/story-writer/SKILL.md +1 -0
  90. package/skills/test-coverage-checker/SKILL.md +1 -0
  91. package/skills/uiux-designer/SKILL.md +1 -0
  92. package/skills/unit-designer/SKILL.md +1 -0
  93. package/skills/unit-test-designer/SKILL.md +1 -0
  94. package/skills/unit-test-logic-designer/SKILL.md +1 -0
  95. package/templates/.claude/scripts/deny-check.sh +73 -12
  96. package/templates/.claude/settings.json +4 -0
package/CHANGELOG.md CHANGED
@@ -7,9 +7,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.162.0] - 2026-07-04
11
+
12
+ ### Fixed
13
+
14
+ - **WI-221 — second-wave audit remediation: make the defenses real** — closes the remaining defects from the 2026-07-04 security audit where "advertised gates were not actually enforced". Product-default strictness is unchanged; where phasegate's own repo could not yet meet a now-real gate, it is configured honestly with a documented ratchet (see `docs/inception/_shared/l3-004-traceability-ratchet.md`).
15
+ - **フェーズゲート残存穴**: `BashWriteTargetExtractor` が `>|` / `dd of=` / `install` / `rsync` / `bash -c`・`sh -c`(ネスト再帰)の書き込みベクトルを検出するよう拡張。`.phasegate/baseline.json` を保護対象に追加し、grandfather 判定をパス一致のみから **sha1 整合検証**へ強化(手書き追記による素通りを封鎖)。
16
+ - **fail-open → fail-closed**: `phase-gate-query` / `phase-dependency-phase-gate-policy` / `phase-dependency-model-query` / skill-quality L1・L2 / nyquist-ac-coverage の各アダプタで、例外を握り潰して「合格」を返していた挙動を fail-closed 化(エラーを表面化し、防御が静かに開かないようにした)。
17
+ - **不発・誤発火バリデータ**: L4-003 dead-code の `.js`→`.ts/.tsx`(`.mjs/.cjs/.jsx`・index)解決で誤検出約1592件を解消。L4-002/WI 反映チェックが標準レイアウトで常時スキップされる不具合を修正。セキュリティスキャナの allowlist をファイル全体スキップから行スコープへ。`ci-check --quick` にバリデータを配線。`NEW_DOMAIN` の `changeKind` ハードコードを create/modify 判定に修正。
18
+ - **config スキーマ不整合(CLI 文鎮化)**: `storyReflection.mappings` のスキーマをコード/ドキュメントの `{inception, product, required}` に整合。`config:plan --intent l4-strict --apply`(および `ci-fail-on-warning`)が実消費キー `/validate/failOnWarning` を出力するよう修正。
19
+ - **Nyquist トレーサビリティ**: L3-003 カバレッジを実配線(`coverageReportPort`)し閾値を実効化。legacy StoryId エイリアス解決・`HF2-01` 形式・`MatrixValidationService` no-op を修正。domain→application の依存方向逆転を解消。
20
+ - **L3-003 カバレッジ・セマンティクス精緻化**: カバレッジ閾値未設定時はスキップ(オプトイン)、設定済み×レポート欠如/不足時は fail-closed、単一バリデータの例外が全体をクラッシュさせないよう per-validator 化。
21
+ - **L3-004 実バグ修正 + パス配線**: AC 網羅ゲートの story レジストリ空スタブを実レジストリ(traceability-model)へ配線し、`layers.L3.requirementMatrixPath`(既定 `.harness/requirement-test-matrix.json`)を新設して ci-check がマトリクスパスを供給するようにした(従来は全ユーザーで L3-004 が満たせなかった)。
22
+ - **シェル・移植性**: `deny-check.sh` のコマンド連結回避・world-readable ログ・`jq` 不在時 fail-open・glob 変換の部分適用を修正。Windows パス区切りでの WI 反映スキャン/採番不具合と 3桁固定の採番破綻を修正。import-graph substring フォールバックのレイヤー違反見逃しをセグメント境界一致で修正。`work-items --apply` の `completed`→`tested` 降格を防止。personal install の既存 pre-commit 無警告スキップと JSON マージのキー順依存を修正。
23
+ - **アーキ整合**: import-graph 修正で顕在化した `installation/application → setup/skill-deployer`(infrastructure)の禁止依存を、port(`ModelDelegationPort`)+ アダプタ + DI で解消。
24
+ - **カバレッジ計測基盤**: `@vitest/coverage-v8` を導入し、エントリポイント/サブプロセス専用ファイルを分母から原則除外した上で自リポの実カバレッジ **90.2%** を確認。`coverage` / `ci` スクリプトでレポート生成→検査の循環を解消(`coverage/` は gitignore)。
25
+
26
+ ## [0.161.0] - 2026-07-04
27
+
28
+ ### Fixed
29
+
30
+ - **WI-220 — phase-gate bypass and quick-mode relaxation defects** — closes four defects reproduced against the published `0.160.21` package:
31
+ - Claude Code の PreToolUse `Bash` matcher に `phasegate hook pre-tool-use` を配線し、Bash 経由(`cat >`, heredoc, `tee`, `sed -i`, `cp`, `mv` 等)の書き込みもフェーズゲート・保護ファイル判定を通すようにした(従来は Codex のみ配線され Claude では素通りしていた)。
32
+ - `WriteTargetScope.fromPath` の `normalize()` で `.`/`..` セグメントを解決し、`.../__tests__/../domain/*` トラバーサルによる保護回避を塞いだ(本物のテストファイルの除外は維持)。
33
+ - `WriteTargetScope` のプレフィックス一致を大文字小文字非依存にし、大小非依存ファイルシステム(macOS/Windows)での `Scripts/harness/...` 経由の保護回避を塞いだ。
34
+ - `QuickModeConfig.isMaintained` がレイヤー名エントリ(`"L2"`)を配下バリデータ ID(`"L2-002"` 等)にマッチさせるようにし、`maintainedLayers: ["L1","L2"]` で L2 が全件 skip されていた不具合を修正(ID 完全一致も後方互換)。
35
+
36
+ ## [0.160.22] - 2026-06-12
37
+
38
+ ### Added
39
+
40
+ - **WI-212 — language-aware validator and skill metadata** — adds `project.languages` configuration, `init --language`, unsupported-language skips for TypeScript-only validators, bundled skill `languages` frontmatter, and a supported languages matrix for adoption planning.
41
+ - **WI-219 — configurable model delegation policy** — adds `modelRouting.delegation` so projects can keep the existing `delegate-sonnet` skill behavior or disable fixed Sonnet delegation while preserving local review guidance.
42
+
10
43
  ### Fixed
11
44
 
12
45
  - **WI-217 — personal inception/product consistency** — makes L4-002 validate configured personal documentation roots, scopes L4-004 freshness to `paths.designDocs`, adds personal hook L4 backstop behavior for `.phasegate-local` docs, and lets `scaffold-wi` use custom IDs and personal inception roots.
46
+ - **WI-218 — WI description scope classification** — treats `docs/inception/**/WI-*/description.md` as a Phase 1 description artifact instead of Level 3, matching hook guidance and allowing description-only inception work without Full Mode.
13
47
 
14
48
  ## [0.160.16] - 2026-05-22
15
49
 
package/README.md CHANGED
@@ -413,6 +413,9 @@ For selection guidance and config examples see [Preset Selection Guide](docs/gui
413
413
  "preset": "standard",
414
414
  "storyReflection": { "enabled": true }
415
415
  },
416
+ "modelRouting": {
417
+ "delegation": "delegate-sonnet"
418
+ },
416
419
  "protectedFiles": {
417
420
  "exclude": ["tsconfig.json", "package.json"]
418
421
  },
@@ -433,6 +436,8 @@ For selection guidance and config examples see [Preset Selection Guide](docs/gui
433
436
 
434
437
  `paths` maps PhaseGate's design and reference documentation roots. Repositories that do not use `docs/` can point `designDocs`, `inceptionDocs`, `principlesDocs`, and `folderRulesDoc` at their own documentation layout; product-wide Level 1 artifacts are overridden separately with custom `phaseDependencies.gates[]`. <!-- @work-item-id WI-214 -->
435
438
 
439
+ `modelRouting.delegation` controls whether bundled skills may use the legacy `delegate-sonnet` workflow. Set it to `"none"` when the main agent session should do the work directly; skill deployment removes fixed model routing text and normal `delegate-sonnet` execution is refused. <!-- @work-item-id WI-219 -->
440
+
436
441
  `baseline` opts in to the **Phase A-2 retrofit grandfather**: pre-existing files captured in `.phasegate/baseline.json` are exempted from `phase-gate` until they are structurally modified. Generate the snapshot with `npx phasegate baseline` before introducing the harness to an existing repository. Since v0.71.0 the `baseline.enabled` flag defaults to `true`, so simply running `npx phasegate baseline` after `init` is enough — no manual config edit needed. For a step-by-step retrofit walkthrough see [Retrofit Adoption Guide](docs/guide/retrofit-adoption.md).
437
442
 
438
443
  ---
@@ -560,7 +565,7 @@ README keeps only the entry points most users need. The full public/compatibilit
560
565
  | `hook <pre-tool-use\|post-tool-use\|stop\|session-start\|user-prompt-submit>` | Run an agent hook (reads JSON from stdin; session-start/user-prompt-submit write JSON context) |
561
566
  | `pre-commit` | Run L2 pre-commit validators on staged files |
562
567
  | `bypass:audit --base <ref> [--head <ref>]` | Replay pre-commit validation over a push/CI range and require structured bypass evidence for gate failures |
563
- | `delegate-sonnet [...args]` | Delegate task to Sonnet 4.6 (transparent wrapper) |
568
+ | `delegate-sonnet [...args]` | Delegate task to Sonnet 4.6 when `modelRouting.delegation` allows it; `--help` and `--dry-run` remain available |
564
569
  | `migrate work-items --dry-run` / `--apply` | Migrate legacy `ISSUE-XXX` / `H{NN}-{NN}` directories under `docs/inception/` to the unified `WI-XXX` layout (frontmatter `type` / `legacy_id` / `affects` injected). Sequential allocator skips numbers already used by existing WIs. See [CLI Reference -- Work Item Migration](docs/guide/cli-reference.md#work-item-migration). |
565
570
  | `migrate --schema v3` | Upgrade `phasegate.config.json` to v3 schema by adding the `architecture` key (idempotent). |
566
571
 
@@ -18,7 +18,7 @@
18
18
  "properties": {
19
19
  "storyId": {
20
20
  "type": "string",
21
- "pattern": "^H\\d{2}-\\d{2}$"
21
+ "pattern": "^H(?:F\\d+|\\d{2})-\\d{2}$"
22
22
  },
23
23
  "storyMappings": {
24
24
  "type": "array",
@@ -22,7 +22,7 @@ Command names in this document are split into three surfaces:
22
22
 
23
23
  | Command | Description |
24
24
  |---|---|
25
- | `init --name <name>` | Legacy-compatible bootstrap for new projects: deploy skills, generate config, and optionally add hooks/CI. Options: `--preset <full\|standard\|minimal\|custom>`, `--skills <core\|all>`, `--agent <claude\|codex\|both>`, `--workflow <standard\|strict>`, `--with-husky`, `--with-ci`, `--yes`. |
25
+ | `init --name <name>` | Legacy-compatible bootstrap for new projects: deploy skills, generate config, and optionally add hooks/CI. Options: `--preset <full\|standard\|minimal\|custom>`, `--skills <core\|all>`, `--agent <claude\|codex\|both>`, `--workflow <standard\|strict>`, `--language <language>`, `--with-husky`, `--with-ci`, `--yes`. `--language` writes `project.languages: ["<language>"]`; omitted language keeps the TypeScript-compatible default. <!-- @work-item-id WI-212 --> |
26
26
  | `install --dry-run` / `--apply` | Idempotently merge PhaseGate into an existing project, preserve user content, add package scripts/devDependency, create selected `AGENTS.md` / `CLAUDE.md` managed sections, and write `.phasegate/manifest.json`. `--agent <claude\|codex\|both>`, `--skills <core\|all>`, and `--workflow <standard\|strict>` affect rendered agent context; `--force` replaces managed targets after backup. |
27
27
  | `doctor` | Diagnose silent or partial installations and report repair hints (`--json`, `--strict`, `--agent <claude\|codex\|both>`, `--report-out <path>`). `--agent` defaults to `both`; single-agent scopes keep shared targets applicable and mark the other agent's findings as not applicable. `--report-out` writes exactly to the supplied path, not to `reporting.outputDir`. |
28
28
  | `uninstall --dry-run` / `--apply` | Remove PhaseGate-managed files and managed blocks using `.phasegate/manifest.json`; `--force` handles managed conflict cases. |
@@ -24,7 +24,8 @@ The plan identifies target fields, managed artifacts, commands, validation, risk
24
24
  {
25
25
  "project": {
26
26
  "name": "my-project",
27
- "preset": "standard" // "minimal" | "standard" | "strict"
27
+ "preset": "standard", // "minimal" | "standard" | "strict"
28
+ "languages": ["typescript"]
28
29
  },
29
30
  "layers": {
30
31
  "L1": { "enabled": true },
@@ -56,6 +57,9 @@ The plan identifies target fields, managed artifacts, commands, validation, risk
56
57
  "default": "interactive", // "interactive" | "embedded-qa"
57
58
  "perPhase": {}
58
59
  },
60
+ "modelRouting": {
61
+ "delegation": "delegate-sonnet" // "delegate-sonnet" | "none"
62
+ },
59
63
  "harnesses": {
60
64
  "agentLessonCollection": false,
61
65
  "cascadeUpdate": false,
@@ -121,6 +125,24 @@ The `strict` preset also enables:
121
125
  |-----------|----------|--------------|----------------------------------------------------------------|
122
126
  | `name` | `string` | -- | Project identifier. Used in reports and validation output. |
123
127
  | `preset` | `string` | `"standard"` | One of `"minimal"`, `"standard"`, `"strict"`. Controls which layers and harnesses are active by default. Individual settings override the preset. |
128
+ | `languages` | `string[]` | `["typescript"]` | Project implementation languages used for validator dispatch and skill applicability. Unknown values are accepted as declarations; unsupported validator/language pairs are reported as skips with `unsupported-language` reasons. <!-- @work-item-id WI-212 --> |
129
+
130
+ #### Supported Languages
131
+
132
+ <!-- @work-item-id WI-212 -->
133
+
134
+ PhaseGate currently ships TypeScript-backed source, test, and coverage validators. Non-TypeScript declarations are supported as configuration metadata so architecture, phase-gate, documentation, pointer, and WI workflow checks can still run while TypeScript-only validators skip with a clear reason.
135
+
136
+ | Capability | TypeScript | Python / Go / Rust / other declared languages |
137
+ |---|---:|---:|
138
+ | `project.languages` config | Supported | Accepted |
139
+ | `init --language <lang>` bootstrap | Supported | Accepted |
140
+ | Architecture preset / phase-gate / WI workflow | Supported | Supported |
141
+ | Markdown document validators (`L4-002`, `L4-004`, `L4-005`, `L4-006`) | Supported | Supported |
142
+ | L3 performance AST validator (`L3-002`) | Supported | Skipped as `unsupported-language` |
143
+ | L3 coverage validator (`L3-003`) | Supported through Vitest coverage | Skipped as `unsupported-language` |
144
+ | L4 dead-code validator (`L4-003`) | Supported | Skipped as `unsupported-language` |
145
+ | Bundled skills | `languages: [typescript]` metadata present | Future language-specific skills can coexist by declaring their own `languages` metadata |
124
146
 
125
147
  #### `layers`
126
148
 
@@ -454,6 +476,14 @@ Quick Mode with `relaxedGates: ["phase-gate"]` relaxes `storyReflection` as well
454
476
  | `default` | `string` | `"interactive"` | `"interactive"` prompts the user at each decision point. `"embedded-qa"` embeds QA checks inline without prompting. |
455
477
  | `perPhase` | `object` | `{}` | Override the planning mode for specific phases. Keys are phase names, values are `"interactive"` or `"embedded-qa"`. |
456
478
 
479
+ #### `modelRouting`
480
+
481
+ <!-- @work-item-id WI-219 -->
482
+
483
+ | Sub-field | Type | Default | Description |
484
+ |--------------|----------|----------------------|-------------|
485
+ | `delegation` | `string` | `"delegate-sonnet"` | `"delegate-sonnet"` preserves the legacy skill routing and `phasegate delegate-sonnet` behavior. `"none"` deploys bundled skills without fixed `model` / `review` frontmatter or `delegate-sonnet` body instructions, and blocks normal `phasegate delegate-sonnet` execution. Help and `--dry-run` remain available. |
486
+
457
487
  #### `harnesses`
458
488
 
459
489
  | Sub-field | Type | Default | Description |
@@ -2,6 +2,8 @@
2
2
 
3
3
  Phasegate provides 30 skills covering the full AIDLC (AI-Driven Development Life Cycle). `npx phasegate init` and project `npx phasegate install` deploy skill bodies to root `skills/` and expose them to enabled agents through `.claude/skills/` / `.codex/skills/` links. Personal install instead writes real local-only per-agent skill directories. <!-- @work-item-id WI-210 -->
4
4
 
5
+ Bundled `SKILL.md` files include `languages: [typescript]` frontmatter so PhaseGate can distinguish current TypeScript-oriented guidance from future language-specific skill variants. The metadata is advisory for applicability and does not prevent non-TypeScript projects from installing the catalog. <!-- @work-item-id WI-212 -->
6
+
5
7
  ## AIDLC Process — Skill Execution Order
6
8
 
7
9
  The AIDLC follows a 3-level execution flow, each level producing design artifacts that gate the next.
@@ -17,6 +17,7 @@
17
17
  - 実装、テスト、リポジトリ内の整合修正は Implementor が担当する。
18
18
  - 委任結果は必ず親エージェントがレビューし、配置ルール、上位文書整合、スコープ逸脱を確認する。
19
19
  - ユーザーまたは実行環境の指示でサブエージェント利用が制限されている場合は、その指示を優先する。
20
+ - `phasegate.config.json` の `modelRouting.delegation` はPhaseGate配備物に対する委任可否の正本である。`"none"` の場合、配備済みスキルは固定モデル委任を指示せず、`delegate-sonnet` の通常実行も拒否される。@work-item-id WI-219
20
21
 
21
22
  ## 委任してよい作業
22
23
 
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "project": {
3
3
  "name": "personal-phasegate",
4
- "preset": "standard"
4
+ "preset": "standard",
5
+ "languages": [
6
+ "typescript"
7
+ ]
5
8
  },
6
9
  "layers": {
7
10
  "L1": {
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "project": {
3
3
  "name": "phasegate-project",
4
- "preset": "standard"
4
+ "preset": "standard",
5
+ "languages": [
6
+ "typescript"
7
+ ]
5
8
  },
6
9
  "architecture": {
7
10
  "preset": "clean"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phasegate",
3
- "version": "0.160.21",
3
+ "version": "0.162.0",
4
4
  "packageManager": "pnpm@10.30.1",
5
5
  "description": "Phasegate — AI-agnostic quality defense toolkit. Enforces structural integrity between design intent and code.",
6
6
  "license": "MIT",
@@ -59,7 +59,9 @@
59
59
  "harness:disable": "pnpm phasegate:disable",
60
60
  "harness:check-phase": "pnpm phasegate:check-phase",
61
61
  "harness:check-ready": "pnpm phasegate:check-ready",
62
- "test": "vitest run --config scripts/harness/__tests__/vitest.config.forks.ts && vitest run --config scripts/harness/__tests__/vitest.config.ts"
62
+ "test": "vitest run --config scripts/harness/__tests__/vitest.config.forks.ts && vitest run --config scripts/harness/__tests__/vitest.config.ts",
63
+ "coverage": "node -e \"require('node:fs').rmSync('coverage',{recursive:true,force:true})\" && vitest run --config scripts/harness/__tests__/vitest.config.coverage.forks.ts --reporter=blob --outputFile=coverage/.blob/forks.json && vitest run --config scripts/harness/__tests__/vitest.config.coverage.ts --reporter=blob --outputFile=coverage/.blob/threads.json && vitest run --config scripts/harness/__tests__/vitest.config.coverage.ts --merge-reports=coverage/.blob",
64
+ "ci": "npm run coverage && npm run test"
63
65
  },
64
66
  "dependencies": {
65
67
  "ajv": "^8.18.0",
@@ -69,8 +71,9 @@
69
71
  },
70
72
  "devDependencies": {
71
73
  "@biomejs/biome": "^2.4.7",
72
- "@types/picomatch": "^4.0.0",
73
74
  "@types/node": "^25.3.5",
75
+ "@types/picomatch": "^4.0.0",
76
+ "@vitest/coverage-v8": "^3.2.4",
74
77
  "typescript": "^5.0.0",
75
78
  "vitest": "^3.0.0"
76
79
  },
@@ -8,12 +8,16 @@
8
8
  * Phase Gate フックが Bash 経由のファイル書き込みを検知して保護するための基盤。
9
9
  *
10
10
  * 副作用なし・純粋関数相当。対応パターンは以下:
11
- * - リダイレクト `>` / `>>`
11
+ * - リダイレクト `>` / `>>` / `>|` (clobber)
12
12
  * - heredoc (`<<EOF > path`)
13
13
  * - `tee` / `tee -a`
14
14
  * - `sed -i` / `sed -i ''` (BSD)
15
15
  * - `cp` / `mv` (宛先のみ)
16
16
  * - `touch`
17
+ * - `dd of=<path>`
18
+ * - `install [opts] SRC... DEST` (coreutils, ファイル作成)
19
+ * - `rsync [opts] SRC... DEST`
20
+ * - `bash -c '...'` / `sh -c '...'` (ネストしたコマンドを再帰解析)
17
21
  * - 複合コマンド (`&&`, `;`, `||`) とパイプ (`|`) 分割
18
22
  * - ダブル/シングルクォート対応
19
23
  * - `apply_patch` ヒアドキュメント (`*** Begin Patch` / `*** End Patch` ブロック内の
@@ -44,7 +48,11 @@ function tokenize(input: string): Token[] {
44
48
  // 演算子をそのままトークン化
45
49
  if (ch === '>' || ch === '<' || ch === '|' || ch === ';' || ch === '&') {
46
50
  let op = ch;
47
- if (i + 1 < len && input[i + 1] === ch && (ch === '>' || ch === '<' || ch === '|' || ch === '&')) {
51
+ // `>|` (clobber redirect) は独立した 2 文字演算子として扱う
52
+ if (ch === '>' && i + 1 < len && input[i + 1] === '|') {
53
+ op = '>|';
54
+ i += 2;
55
+ } else if (i + 1 < len && input[i + 1] === ch && (ch === '>' || ch === '<' || ch === '|' || ch === '&')) {
48
56
  op = ch + ch;
49
57
  i += 2;
50
58
  } else {
@@ -139,10 +147,10 @@ function getCommandName(tokens: Token[]): string | undefined {
139
147
  function extractFromSingleCommand(tokens: Token[]): string[] {
140
148
  const results: string[] = [];
141
149
 
142
- // 1) リダイレクト `>` / `>>` の右辺 (heredoc の `>` もこのパスで拾える)
150
+ // 1) リダイレクト `>` / `>>` / `>|` の右辺 (heredoc の `>` もこのパスで拾える)
143
151
  for (let i = 0; i < tokens.length; i += 1) {
144
152
  const t = tokens[i];
145
- if (t.quoted === 'none' && (t.value === '>' || t.value === '>>')) {
153
+ if (t.quoted === 'none' && (t.value === '>' || t.value === '>>' || t.value === '>|')) {
146
154
  const next = tokens[i + 1];
147
155
  if (next !== undefined) {
148
156
  results.push(next.value);
@@ -213,14 +221,89 @@ function extractFromSingleCommand(tokens: Token[]): string[] {
213
221
  for (let i = 1; i < tokens.length; i += 1) {
214
222
  const t = tokens[i];
215
223
  if (t.quoted === 'none' && t.value.startsWith('-')) continue;
216
- if (t.quoted === 'none' && (t.value === '>' || t.value === '>>' || t.value === '|' || t.value === '<')) break;
224
+ if (t.quoted === 'none' && (t.value === '>' || t.value === '>>' || t.value === '>|' || t.value === '|' || t.value === '<')) break;
217
225
  results.push(t.value);
218
226
  }
227
+ } else if (baseName === 'dd') {
228
+ // `dd of=<path>` — 出力ファイルは `of=` オペランド
229
+ for (let i = 1; i < tokens.length; i += 1) {
230
+ const t = tokens[i];
231
+ if (t.value.startsWith('of=')) {
232
+ const dest = t.value.slice('of='.length);
233
+ if (dest.length > 0) results.push(dest);
234
+ }
235
+ }
236
+ } else if (baseName === 'install') {
237
+ // `install [opts] SRC... DEST` — 宛先は最後の非オプション引数 (coreutils はファイルを作成する)
238
+ const positionals = collectPositionals(tokens);
239
+ if (positionals.length >= 1) {
240
+ // `install -d DIR...` はディレクトリ作成のみだが保護側に倒し全ディレクトリを対象化
241
+ const isDirMode = tokens.some(
242
+ (t) => t.quoted === 'none' && (t.value === '-d' || t.value === '--directory'),
243
+ );
244
+ if (isDirMode) {
245
+ for (const p of positionals) results.push(p.value);
246
+ } else if (positionals.length >= 2) {
247
+ results.push(positionals[positionals.length - 1].value);
248
+ } else {
249
+ // SRC 省略で DEST のみ渡された不正形でも保護側に倒す
250
+ results.push(positionals[positionals.length - 1].value);
251
+ }
252
+ }
253
+ } else if (baseName === 'rsync') {
254
+ // `rsync [opts] SRC... DEST` — 宛先は最後の非オプション引数
255
+ const positionals = collectPositionals(tokens);
256
+ if (positionals.length >= 2) {
257
+ results.push(positionals[positionals.length - 1].value);
258
+ }
259
+ } else if (baseName === 'bash' || baseName === 'sh') {
260
+ // `bash -c '<nested command>'` — ネストしたコマンドを再帰解析
261
+ for (let i = 1; i < tokens.length; i += 1) {
262
+ const t = tokens[i];
263
+ if (t.quoted === 'none' && t.value === '-c') {
264
+ const script = tokens[i + 1];
265
+ if (script !== undefined) {
266
+ for (const nested of extractFromCommandString(script.value)) {
267
+ results.push(nested);
268
+ }
269
+ }
270
+ break;
271
+ }
272
+ }
219
273
  }
220
274
 
221
275
  return results;
222
276
  }
223
277
 
278
+ /** コマンド先頭 (cmd 名) を除いた非オプション位置引数を収集する。リダイレクト境界で停止。 */
279
+ function collectPositionals(tokens: Token[]): Token[] {
280
+ const positionals: Token[] = [];
281
+ for (let i = 1; i < tokens.length; i += 1) {
282
+ const t = tokens[i];
283
+ if (t.quoted === 'none' && t.value.startsWith('-')) continue;
284
+ if (t.quoted === 'none' && (t.value === '>' || t.value === '>>' || t.value === '>|' || t.value === '|' || t.value === '<')) break;
285
+ positionals.push(t);
286
+ }
287
+ return positionals;
288
+ }
289
+
290
+ /** コマンド文字列を token 化 → 演算子分割 → 各コマンドから書き込み先を抽出する内部関数。 */
291
+ function extractFromCommandString(command: string): string[] {
292
+ const tokens = tokenize(command);
293
+ const groups = splitByOperators(tokens);
294
+ const collected: string[] = [];
295
+ for (const group of groups) {
296
+ for (const path of extractFromSingleCommand(group)) {
297
+ collected.push(path);
298
+ }
299
+ }
300
+ // ネスト内の apply_patch heredoc も拾う
301
+ for (const p of extractApplyPatchTargets(command)) {
302
+ collected.push(p);
303
+ }
304
+ return collected;
305
+ }
306
+
224
307
  /** apply_patch ブロック境界マーカー (Begin/End) */
225
308
  const APPLY_PATCH_BEGIN_SOURCE = String.raw`\*\*\*\s+Begin\s+Patch`;
226
309
  const APPLY_PATCH_END_SOURCE = String.raw`\*\*\*\s+End\s+Patch`;
@@ -284,24 +367,9 @@ export class BashWriteTargetExtractor {
284
367
  return Object.freeze([]);
285
368
  }
286
369
 
287
- const tokens = tokenize(command);
288
- const groups = splitByOperators(tokens);
289
-
290
- const collected: string[] = [];
291
- for (const group of groups) {
292
- const found = extractFromSingleCommand(group);
293
- for (const path of found) {
294
- collected.push(path);
295
- }
296
- }
297
-
298
- // apply_patch ヒアドキュメント対応 (ISSUE-013 Wave 1)
299
- // 既存の token ベース抽出では heredoc body 内のマーカー行を拾えないため、
300
- // raw command 文字列から独立にスキャンする。
301
- const applyPatchTargets = extractApplyPatchTargets(command);
302
- for (const p of applyPatchTargets) {
303
- collected.push(p);
304
- }
370
+ // token → 演算子分割 → 各コマンド抽出 + apply_patch ヒアドキュメント対応
371
+ // (ISSUE-013 Wave 1)。bash -c '...' 等のネストコマンドは内部で再帰解析される。
372
+ const collected = extractFromCommandString(command);
305
373
 
306
374
  // 重複除去 (挿入順保持)
307
375
  const seen = new Set<string>();
@@ -21,6 +21,10 @@ const DEFAULT_PATTERNS = [
21
21
  'tsconfig.json',
22
22
  'package.json',
23
23
  'package-lock.json',
24
+ // baseline.json は grandfather 判定の信頼基盤。手動追記による protected file の
25
+ // grandfather bypass を防ぐため、書き込み自体を保護対象とする。
26
+ '.phasegate/baseline.json',
27
+ '**/.phasegate/baseline.json',
24
28
  ];
25
29
 
26
30
  /**
@@ -75,11 +75,15 @@ export class WriteTargetScope {
75
75
 
76
76
  const inceptionMatch = matchPrefix(normalizedPath, inceptionPath);
77
77
  if (inceptionMatch !== null) {
78
- const [unitId, secondSegment] = inceptionMatch;
78
+ const [unitId, secondSegment, thirdSegment] = inceptionMatch;
79
79
 
80
80
  // 横断的 WI: docs/inception/_cross/{WI-XXX}/ → Level 3
81
81
  if (unitId === "_cross") {
82
82
  if (secondSegment !== undefined && WORK_ITEM_ID_PATTERN.test(secondSegment)) {
83
+ if (thirdSegment === "description.md") {
84
+ return WriteTargetScope.create({ level: 1 });
85
+ }
86
+
83
87
  return WriteTargetScope.create({ level: 3, unitId, storyId: secondSegment });
84
88
  }
85
89
 
@@ -88,6 +92,10 @@ export class WriteTargetScope {
88
92
 
89
93
  // Unit 所有 WI / 既存 US パス: docs/inception/{unit}/{storyId}/ → Level 3
90
94
  if (unitId !== undefined && secondSegment !== undefined && WORK_ITEM_ID_PATTERN.test(secondSegment)) {
95
+ if (secondSegment.startsWith("WI-") && thirdSegment === "description.md") {
96
+ return WriteTargetScope.create({ level: 1 });
97
+ }
98
+
91
99
  return WriteTargetScope.create({ level: 3, unitId, storyId: secondSegment });
92
100
  }
93
101
  }
@@ -121,24 +129,65 @@ export class WriteTargetScope {
121
129
  }
122
130
  }
123
131
 
124
- const normalize = (value: string): string =>
125
- value
132
+ const normalize = (value: string): string => {
133
+ const cleaned = value
126
134
  .replaceAll("\\", "/")
127
135
  .replace(/^\.\/+/, "")
128
136
  .replace(/\/+/g, "/")
129
137
  .replace(/\/$/, "");
130
138
 
139
+ return resolveTraversal(cleaned);
140
+ };
141
+
142
+ // posix セマンティクスで `.`/`..` セグメントを解決する(プロジェクト相対パス想定)。
143
+ // これにより `a/b/../c` → `a/c`。フェーズゲート保護回避(P-2)対策として、
144
+ // __tests__ 除外や prefix 一致判定はこの解決後のパスに対して適用される。
145
+ const resolveTraversal = (value: string): string => {
146
+ if (value === "") {
147
+ return "";
148
+ }
149
+
150
+ const segments = value.split("/");
151
+ const resolved: string[] = [];
152
+
153
+ for (const segment of segments) {
154
+ if (segment === "" || segment === ".") {
155
+ continue;
156
+ }
157
+
158
+ if (segment === "..") {
159
+ if (resolved.length > 0 && resolved[resolved.length - 1] !== "..") {
160
+ resolved.pop();
161
+ } else {
162
+ // プロジェクトルートより上への参照はそのまま残す(保護判定に影響させない)
163
+ resolved.push("..");
164
+ }
165
+ continue;
166
+ }
167
+
168
+ resolved.push(segment);
169
+ }
170
+
171
+ return resolved.join("/");
172
+ };
173
+
131
174
  const matchPrefix = (targetPath: string, basePath: string): string[] | null => {
132
175
  const normalizedBase = normalize(basePath);
133
176
 
134
- if (targetPath === normalizedBase) {
177
+ // フェーズゲートはセキュリティ境界のため、大小非依存 FS(macOS/Windows)での
178
+ // 保護回避(P-3)を防ぐべく prefix 一致は大文字小文字を無視して判定する。
179
+ const lowerTarget = targetPath.toLowerCase();
180
+ const lowerBase = normalizedBase.toLowerCase();
181
+
182
+ if (lowerTarget === lowerBase) {
135
183
  return [];
136
184
  }
137
185
 
138
- if (!targetPath.startsWith(`${normalizedBase}/`)) {
186
+ if (!lowerTarget.startsWith(`${lowerBase}/`)) {
139
187
  return null;
140
188
  }
141
189
 
190
+ // remainder(unitId 等の抽出結果)は元の大小を保持する。
142
191
  const remainder = targetPath.slice(normalizedBase.length + 1);
143
192
  return remainder === "" ? [] : remainder.split("/");
144
193
  };
@@ -8,6 +8,8 @@ import type {
8
8
  import type { ConfigQueryPort } from '../../domain/ports/config-query-port.js';
9
9
  import type { BaselineRepositoryPort } from '../../../ci-governance/domain/ports/baseline-repository-port.js';
10
10
  import { BaselineJsonRepositoryAdapter } from '../../../ci-governance/infrastructure/adapters/baseline-json-repository-adapter.js';
11
+ import type { FileHasherPort } from '../../../ci-governance/domain/ports/file-hasher-port.js';
12
+ import { FileSystemSha1HasherAdapter } from '../../../ci-governance/infrastructure/adapters/file-system-sha1-hasher-adapter.js';
11
13
 
12
14
  export interface CiGovernanceBaselineGrandfatherAdapterDeps {
13
15
  readonly baseDir: string;
@@ -16,6 +18,7 @@ export interface CiGovernanceBaselineGrandfatherAdapterDeps {
16
18
  baseDir: string,
17
19
  relativePath: string,
18
20
  ) => BaselineRepositoryPort;
21
+ readonly fileHasherFactory?: (baseDir: string) => FileHasherPort;
19
22
  }
20
23
 
21
24
  export class CiGovernanceBaselineGrandfatherAdapter
@@ -68,11 +71,31 @@ export class CiGovernanceBaselineGrandfatherAdapter
68
71
  };
69
72
  }
70
73
 
74
+ // 整合性検証: baseline に path が載っているだけでは grandfather しない。
75
+ // 記録済み sha1 と実ファイルの sha1 が一致する場合のみ grandfather 扱いとする。
76
+ // これにより baseline.json への手動 path 追記による bypass を防ぐ
77
+ //(追記側は実ファイルの正しい sha1 を予測できず、改変後の content とも一致しない)。
78
+ const hasherFactory =
79
+ this.deps.fileHasherFactory ??
80
+ ((baseDir) => new FileSystemSha1HasherAdapter(baseDir));
81
+ const hasher = hasherFactory(this.deps.baseDir);
82
+ const entryByPath = new Map(snapshot.entries.map((e) => [e.path, e.sha1] as const));
83
+
71
84
  const grandfathered: string[] = [];
72
85
  for (const p of targetFilePaths) {
73
- if (snapshot.contains(p)) grandfathered.push(p);
86
+ const recordedSha1 = entryByPath.get(p);
87
+ if (recordedSha1 === undefined) continue;
88
+ let actualSha1: string;
89
+ try {
90
+ actualSha1 = await hasher.hashFile(p);
91
+ } catch {
92
+ // ファイル読み取り失敗 (未作成・権限等) は grandfather 不可扱い (保護側に倒す)
93
+ continue;
94
+ }
95
+ if (actualSha1 === recordedSha1) grandfathered.push(p);
74
96
  }
75
- const allGrandfathered = grandfathered.length === targetFilePaths.length;
97
+ const allGrandfathered =
98
+ targetFilePaths.length > 0 && grandfathered.length === targetFilePaths.length;
76
99
 
77
100
  return {
78
101
  allGrandfathered,
@@ -39,13 +39,28 @@ export class PhaseGateQueryAdapter implements PhaseGateQueryPort {
39
39
  return PhaseGateQueryResult.create(false, [result.text], []);
40
40
  }
41
41
 
42
- return PhaseGateQueryResult.create(true, [], ['phase gate check returned error']);
42
+ // Fail-closed: an unexpected exit code means the gate could not be evaluated.
43
+ // A quality gate that cannot confirm PASS must block, not open.
44
+ console.error(`[agent-integration] phase gate check returned unexpected exit code ${result.exitCode}`);
45
+ return PhaseGateQueryResult.create(
46
+ false,
47
+ [`phase gate check returned unexpected exit code ${result.exitCode}; treating as NOT passed (fail-closed)`],
48
+ []
49
+ );
43
50
  } catch (error) {
44
51
  if (error instanceof ConfigValidationError) {
45
52
  return PhaseGateQueryResult.create(false, [error.message], []);
46
53
  }
47
54
 
48
- return PhaseGateQueryResult.create(true, [], ['phase-dependency-model not available']);
55
+ // Fail-closed: if phase-dependency-model could not be loaded/executed we cannot
56
+ // confirm the gate passed, so treat it as blocked rather than silently opening it.
57
+ const message = error instanceof Error ? error.message : String(error);
58
+ console.error(`[agent-integration] phase gate check failed to run: ${message}`);
59
+ return PhaseGateQueryResult.create(
60
+ false,
61
+ [`phase gate check could not be evaluated (phase-dependency-model unavailable): ${message}`],
62
+ []
63
+ );
49
64
  }
50
65
  }
51
66
 
@@ -46,6 +46,13 @@ const globToRegex = (pattern: string): RegExp => {
46
46
  const next = pattern[index + 1];
47
47
 
48
48
  if (current === '*' && next === '*') {
49
+ // A `**/` segment matches any number of leading path segments, INCLUDING
50
+ // none, so `**/shared-kernel/**` still matches `shared-kernel/a.ts`.
51
+ if (pattern[index + 2] === '/') {
52
+ output += '(?:.*/)?';
53
+ index += 2;
54
+ continue;
55
+ }
49
56
  output += '.*';
50
57
  index += 1;
51
58
  continue;
@@ -62,8 +69,28 @@ const globToRegex = (pattern: string): RegExp => {
62
69
  return new RegExp(`^${output}$`);
63
70
  };
64
71
 
72
+ // Fallback for wildcard-free patterns (e.g. `generated`, `shared-kernel`). It
73
+ // must align on a `/` segment boundary at the START and terminate on a real
74
+ // boundary — a `/`, the extension dot, or end of string. This replaces a naive
75
+ // `value.includes(pattern stripped of * and /)` substring test that over-matched
76
+ // (e.g. `vendor` matched `domain/vendorized-service.ts`), which silently
77
+ // suppressed genuine layer violations. The boundary-terminated form keeps
78
+ // intended ignores working (`generated` → `generated.ts`) while no longer
79
+ // swallowing unrelated segments.
80
+ const segmentPrefixRegex = (pattern: string): RegExp => {
81
+ return new RegExp(`(^|/)${escapeRegex(pattern)}([./]|$)`);
82
+ };
83
+
65
84
  export const matchesPattern = (value: string, pattern: string): boolean => {
66
- return globToRegex(pattern).test(value) || value.includes(pattern.replace(/\*/g, '').replace(/\//g, ''));
85
+ if (globToRegex(pattern).test(value)) {
86
+ return true;
87
+ }
88
+ // Wildcard patterns are authoritative via the glob regex above; only bare
89
+ // (wildcard-free) patterns fall back to segment-boundary matching.
90
+ if (pattern.includes('*')) {
91
+ return false;
92
+ }
93
+ return segmentPrefixRegex(pattern).test(value);
67
94
  };
68
95
 
69
96
  const canonicalCycleKey = (path: readonly string[]): string => {