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.
- package/CHANGELOG.md +34 -0
- package/README.md +6 -1
- package/docs/contracts/requirement-test-matrix.schema.json +1 -1
- package/docs/guide/cli-reference.md +1 -1
- package/docs/guide/configuration.md +31 -1
- package/docs/guide/skills-overview.md +2 -0
- package/docs/principles/model-routing.md +1 -0
- package/docs/templates/personal/phasegate-local-config.json +4 -1
- package/docs/templates/project/phasegate.config.json +4 -1
- package/package.json +6 -3
- package/scripts/harness/agent-integration/domain/services/bash-write-target-extractor.ts +91 -23
- package/scripts/harness/agent-integration/domain/value-objects/protected-file-list.ts +4 -0
- package/scripts/harness/agent-integration/domain/value-objects/write-target-scope.ts +54 -5
- package/scripts/harness/agent-integration/infrastructure/adapters/ci-governance-baseline-grandfather-adapter.ts +25 -2
- package/scripts/harness/agent-integration/infrastructure/adapters/phase-gate-query-adapter.ts +17 -2
- package/scripts/harness/biome-ast-engine/domain/value-objects/import-graph.ts +28 -1
- package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +6 -1
- package/scripts/harness/config-foundation/domain/harness-config.ts +10 -0
- package/scripts/harness/config-foundation/domain/services/preset-resolution-service.ts +2 -0
- package/scripts/harness/config-foundation/domain/value-objects/l3-config.ts +4 -0
- package/scripts/harness/config-foundation/domain/value-objects/layers-config.ts +1 -1
- package/scripts/harness/config-foundation/domain/value-objects/project-config.ts +20 -5
- package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +2 -1
- package/scripts/harness/config-foundation/infrastructure/presets/standard.json +2 -1
- package/scripts/harness/config-foundation/infrastructure/presets/strict.json +2 -1
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +39 -9
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +39 -9
- package/scripts/harness/harness-api/infrastructure/adapters/phase-dependency-model-query-adapter.ts +18 -4
- package/scripts/harness/harness-api/infrastructure/adapters/validator-system-execution-adapter.ts +27 -4
- package/scripts/harness/installation/application/ports/model-delegation-port.ts +10 -0
- package/scripts/harness/installation/application/usecases/run-install.ts +67 -5
- package/scripts/harness/installation/application/usecases/run-reconcile.ts +53 -5
- package/scripts/harness/installation/composition-root.ts +4 -2
- package/scripts/harness/installation/infrastructure/adapters/skill-deployer-model-delegation-adapter.ts +16 -0
- package/scripts/harness/installation/presentation/cli/install-handler.ts +2 -1
- package/scripts/harness/main.ts +99 -13
- package/scripts/harness/nyquist-validation/application/dto/generate-matrix-output.ts +9 -7
- package/scripts/harness/nyquist-validation/domain/services/matrix-validation-service.ts +27 -2
- package/scripts/harness/nyquist-validation/domain/services/requirement-intent-coverage-service.ts +4 -4
- package/scripts/harness/nyquist-validation/domain/value-objects/intent-coverage.ts +33 -0
- package/scripts/harness/nyquist-validation/infrastructure/adapters/markdown-requirement-source-adapter.ts +3 -1
- package/scripts/harness/quick-mode/application/usecases/classify-change-category-usecase.ts +8 -3
- package/scripts/harness/quick-mode/composition-root.ts +3 -0
- package/scripts/harness/quick-mode/domain/value-objects/quick-mode-config.ts +6 -1
- package/scripts/harness/quick-mode/infrastructure/adapters/validator-system-quick-mode-execution-adapter.ts +18 -0
- package/scripts/harness/quick-mode/presentation/handlers/ci-check-quick-mode-handler.ts +51 -5
- package/scripts/harness/setup/skill-deployer.ts +47 -0
- package/scripts/harness/skill-quality/domain/services/skill-structure-validator.ts +21 -0
- package/scripts/harness/skill-quality/domain/value-objects/skill-structure.ts +2 -0
- package/scripts/harness/skill-quality/infrastructure/adapters/l1-biome-validator-adapter.ts +12 -2
- package/scripts/harness/skill-quality/infrastructure/adapters/l2-validator-system-adapter.ts +12 -2
- package/scripts/harness/traceability-model/application/usecases/apply-work-item-status-usecase.ts +6 -1
- package/scripts/harness/traceability-model/infrastructure/parsers/story-catalog-parser.ts +28 -9
- package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +76 -25
- package/scripts/harness/validator-system/application/use-cases/run-l4-validators-usecase.ts +20 -8
- package/scripts/harness/validator-system/composition-root.ts +8 -1
- package/scripts/harness/validator-system/domain/ports/validator-config-port.ts +2 -0
- package/scripts/harness/validator-system/domain/services/validator-language-capability-service.ts +64 -0
- package/scripts/harness/validator-system/infrastructure/adapters/file-system-security-pattern-scanner-adapter.ts +16 -6
- package/scripts/harness/validator-system/infrastructure/adapters/file-system-work-item-reflection-adapter.ts +10 -1
- package/scripts/harness/validator-system/infrastructure/adapters/harness-config-validator-config-adapter.ts +7 -1
- package/scripts/harness/validator-system/infrastructure/adapters/import-graph-source-analysis-adapter.ts +35 -3
- package/scripts/harness/validator-system/infrastructure/adapters/nyquist-ac-coverage-policy-adapter.ts +86 -14
- package/scripts/harness/validator-system/infrastructure/adapters/phase-dependency-phase-gate-policy-adapter.ts +29 -3
- package/skills/cascade-updater/SKILL.md +1 -0
- package/skills/codebase-mapper/SKILL.md +1 -0
- package/skills/codex-delegator/SKILL.md +1 -0
- package/skills/consistency-checker/SKILL.md +1 -0
- package/skills/doc-freshness-checker/SKILL.md +1 -0
- package/skills/domain-designer/SKILL.md +1 -0
- package/skills/engineering-perspective/SKILL.md +1 -0
- package/skills/environment-designer/SKILL.md +1 -0
- package/skills/implementation-planner/SKILL.md +1 -0
- package/skills/implementation-readiness-checker/SKILL.md +1 -0
- package/skills/it-test-designer/SKILL.md +1 -0
- package/skills/it-test-logic-designer/SKILL.md +1 -0
- package/skills/logical-designer/SKILL.md +1 -0
- package/skills/mock-designer/SKILL.md +1 -0
- package/skills/phasegate-config-doctor/SKILL.md +1 -0
- package/skills/phasegate-toolkit-guide/SKILL.md +1 -0
- package/skills/pointer-validator/SKILL.md +1 -0
- package/skills/product-architect/SKILL.md +1 -0
- package/skills/quick-implementor/SKILL.md +1 -0
- package/skills/scenario-test-designer/SKILL.md +1 -0
- package/skills/scenario-test-logic-designer/SKILL.md +1 -0
- package/skills/skill-creator/SKILL.md +1 -0
- package/skills/story-implementor/SKILL.md +1 -0
- package/skills/story-mapper/SKILL.md +1 -0
- package/skills/story-writer/SKILL.md +1 -0
- package/skills/test-coverage-checker/SKILL.md +1 -0
- package/skills/uiux-designer/SKILL.md +1 -0
- package/skills/unit-designer/SKILL.md +1 -0
- package/skills/unit-test-designer/SKILL.md +1 -0
- package/skills/unit-test-logic-designer/SKILL.md +1 -0
- package/templates/.claude/scripts/deny-check.sh +73 -12
- 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
|
|
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
|
|
|
@@ -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"
|
|
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
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phasegate",
|
|
3
|
-
"version": "0.
|
|
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
|
-
|
|
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
|
-
|
|
288
|
-
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
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 =
|
|
97
|
+
const allGrandfathered =
|
|
98
|
+
targetFilePaths.length > 0 && grandfathered.length === targetFilePaths.length;
|
|
76
99
|
|
|
77
100
|
return {
|
|
78
101
|
allGrandfathered,
|
package/scripts/harness/agent-integration/infrastructure/adapters/phase-gate-query-adapter.ts
CHANGED
|
@@ -39,13 +39,28 @@ export class PhaseGateQueryAdapter implements PhaseGateQueryPort {
|
|
|
39
39
|
return PhaseGateQueryResult.create(false, [result.text], []);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 => {
|