phasegate 0.160.16 → 0.160.18
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/README.ja.md +36 -1
- package/README.md +9 -1
- package/docs/guide/configuration.md +15 -5
- package/docs/guide/installation.md +3 -1
- package/docs/templates/personal/hooks/commit-msg +7 -0
- package/docs/templates/personal/hooks/pre-commit +21 -0
- package/docs/templates/personal/phasegate-local-config.json +4 -2
- package/docs/templates/project/phasegate.config.json +3 -1
- package/package.json +1 -1
- package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +29 -7
- package/scripts/harness/agent-integration/infrastructure/adapters/harness-config-config-query-adapter.ts +23 -4
- package/scripts/harness/config-foundation/domain/harness-config.ts +2 -0
- package/scripts/harness/config-foundation/domain/value-objects/paths-config.ts +14 -1
- package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +3 -1
- package/scripts/harness/config-foundation/infrastructure/presets/standard.json +3 -1
- package/scripts/harness/config-foundation/infrastructure/presets/strict.json +3 -1
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +19 -1
- package/scripts/harness/installation/application/usecases/run-install.ts +48 -0
- package/scripts/harness/main.ts +3 -2
- package/scripts/harness/setup/skill-deployer.ts +35 -11
package/README.ja.md
CHANGED
|
@@ -97,7 +97,17 @@ claude
|
|
|
97
97
|
|
|
98
98
|
`init` は legacy 互換の bootstrap 経路です。既存 hooks / scripts / package metadata に構造化 merge したい場合は `install` を使います。「設計してから書け」を強制する仕組みなので、設計文書はユーザーがスキル経由で作るのが既定動作です。
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
既存プロジェクトへの導入は、**チーム共通で入れる (team install)** か **自分だけで試す (personal install)** かで手順が変わります。誰のリポジトリで誰がメンテするかで選んでください。
|
|
101
|
+
|
|
102
|
+
| シナリオ | コマンド | 影響範囲 |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| 新規プロジェクトを bootstrap | `npx phasegate init --name <name> --with-husky --with-ci` | リポジトリ全体(team) |
|
|
105
|
+
| 既存プロジェクトにチーム共通で導入 | `npx phasegate install --apply` | リポジトリ全体(team) |
|
|
106
|
+
| チーム所有リポジトリで個人評価 | `npx phasegate install --personal --agent claude --apply` | 自分の作業コピーのみ(ローカル) |
|
|
107
|
+
|
|
108
|
+
#### team install — チーム共通で導入する場合
|
|
109
|
+
|
|
110
|
+
`package.json` / `CLAUDE.md` / `.husky/*` / `.github/workflows/*` などのチーム共有ファイルを merge 対象に含めます。**この変更はコミット後にチーム全員へ影響します**。事前に dry-run で差分を確認してから apply してください。
|
|
101
111
|
|
|
102
112
|
```bash
|
|
103
113
|
npx phasegate install --dry-run
|
|
@@ -107,6 +117,24 @@ npx phasegate doctor
|
|
|
107
117
|
|
|
108
118
|
`install` は既存の Claude / Codex hooks や Husky script を捨てずに PhaseGate の設定を merge します。書き込み前に変更予定を表示し、package scripts と `phasegate` devDependency、root `skills/` への selected bundled skills 配布、agent skill symlink、未作成の CI workflow、`.phasegate/manifest.json` を整えます。強制的な managed 更新が必要な場合は `npx phasegate install --apply --force` を使います。この場合、置き換え対象は `.phasegate/backups/` に退避されます。<!-- @work-item-id WI-210 -->
|
|
109
119
|
|
|
120
|
+
#### personal install — 自分だけで試す場合(チーム所有リポジトリで評価したい)
|
|
121
|
+
|
|
122
|
+
チーム所有のリポジトリで自分だけ PhaseGate を試したい場合は `--personal` フラグを使います。**チーム共有ファイルには一切触れず、自分の agent runtime だけを設定する** ローカル専用パスです。
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npx phasegate install --personal --agent claude --dry-run
|
|
126
|
+
npx phasegate install --personal --agent claude --apply
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
| 項目 | personal install の挙動 |
|
|
130
|
+
|---|---|
|
|
131
|
+
| **触らないファイル** | `package.json` / `AGENTS.md` / `CLAUDE.md` / `.husky/*` / `.github/workflows/*` / `.gitignore` / GitHub CLI 設定 / repo secrets / CI 設定 |
|
|
132
|
+
| **作るファイル** | `.phasegate-local/phasegate.config.json`(ローカル専用 config)/ `.claude/CLAUDE.local.md` または `.codex/AGENTS.local.md`(agent context)/ `.claude/settings.json` + `.claude/skills/` または `.codex/hooks.json` + `.codex/skills/`(選択した agent の runtime artifact)/ `.git/hooks/pre-commit` + `.git/hooks/commit-msg`(ローカル git hook)/ `.phasegate-local/docs/`(設計原則文書コピー)/ `.phasegate/manifest.json` |
|
|
133
|
+
| **コミット漏れ対策** | `.git/info/exclude` にローカル専用 block を管理して、個人用ファイルが誤ってチームの commit に混ざらないようにする。commit 時の L2 防御は `.git/hooks/` で発火する |
|
|
134
|
+
| **Codex hook flag** | user-level feature flag の有効化は手動アクションとして残る(`codex features enable hooks`) |
|
|
135
|
+
|
|
136
|
+
Codex を併用する場合は `--agent codex` または `--agent both` を指定します。アンインストールは team install と同様に `npx phasegate uninstall --apply` を使えば manifest 経由でローカル成果物のみが除去されます。<!-- @work-item-id WI-207 --> <!-- @work-item-id WI-208 --> <!-- @work-item-id WI-209 --> <!-- @work-item-id WI-213 -->
|
|
137
|
+
|
|
110
138
|
後で PhaseGate を外す場合は、manifest ベースの uninstall を使います。
|
|
111
139
|
|
|
112
140
|
```bash
|
|
@@ -277,6 +305,12 @@ L2-STORY-REFLECTION バリデータがこのアノテーションを検出し、
|
|
|
277
305
|
"phaseDependencies": { "preset": "standard", "storyReflection": { "enabled": true } },
|
|
278
306
|
"quickMode": { "allowedCategories": ["bugfix", "docs", "test", "config"] },
|
|
279
307
|
"protectedFiles": { "exclude": ["package.json"] },
|
|
308
|
+
"paths": {
|
|
309
|
+
"designDocs": "docs/product/construction",
|
|
310
|
+
"inceptionDocs": "docs/inception",
|
|
311
|
+
"principlesDocs": "docs/principles",
|
|
312
|
+
"folderRulesDoc": "docs/folder_management_rules.md"
|
|
313
|
+
},
|
|
280
314
|
"baseline": { "enabled": true, "path": ".phasegate/baseline.json" }
|
|
281
315
|
}
|
|
282
316
|
```
|
|
@@ -301,6 +335,7 @@ phasegate には独立した 3 系統の preset があります。役割が違
|
|
|
301
335
|
|---|---|
|
|
302
336
|
| `quickMode.fullModeRequiredWhen` | Quick Mode → Full Mode への強制エスカレート条件(複数カテゴリ混在 / 新規ドメインファイル / API 契約変更)。安全側の default は全 `true` |
|
|
303
337
|
| `protectedFiles.exclude` | デフォルト保護対象(`package.json`, `tsconfig.json`, `biome.json` 等)から除外したいファイル |
|
|
338
|
+
| `paths.designDocs` / `paths.inceptionDocs` / `paths.principlesDocs` / `paths.folderRulesDoc` | PhaseGate が参照・配置する設計/原則/配置ルール文書の場所。`docs/` 以外の既存規約にもマッピング可能 |
|
|
304
339
|
| `baseline.enabled` | 既存リポジトリ導入時の retrofit grandfather。default `true`。`npx phasegate baseline` で snapshot 生成 |
|
|
305
340
|
| `phaseDependencies.storyReflection` | inception 設計が product docs に反映されるまで `src/{unit}/` への書き込みをブロック |
|
|
306
341
|
|
package/README.md
CHANGED
|
@@ -109,7 +109,7 @@ npx phasegate install --personal --agent claude --dry-run
|
|
|
109
109
|
npx phasegate install --personal --agent claude --apply
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
`--personal` does not plan or write `package.json`, `AGENTS.md`, `CLAUDE.md`, `.husky/*`, `.github/workflows/*`, `.gitignore`, GitHub CLI config, repo secrets, or CI settings. It creates `.phasegate-local/phasegate.config.json`, real local-only agent runtime artifacts for the selected agent (`.claude/settings.json` + `.claude/skills/` and/or `.codex/hooks.json` + `.codex/skills/`), a managed local-only block in `.git/info/exclude`, and `.phasegate/manifest.json`. Codex user-level hook feature enablement remains a manual action. <!-- @work-item-id WI-207 --> <!-- @work-item-id WI-208 --> <!-- @work-item-id WI-209 -->
|
|
112
|
+
`--personal` does not plan or write `package.json`, `AGENTS.md`, `CLAUDE.md`, `.husky/*`, `.github/workflows/*`, `.gitignore`, GitHub CLI config, repo secrets, or CI settings. It creates `.phasegate-local/phasegate.config.json`, local agent context (`.claude/CLAUDE.local.md` and/or `.codex/AGENTS.local.md`), real local-only agent runtime artifacts for the selected agent (`.claude/settings.json` + `.claude/skills/` and/or `.codex/hooks.json` + `.codex/skills/`), local git hooks under `.git/hooks/`, local reference docs under `.phasegate-local/docs/`, a managed local-only block in `.git/info/exclude`, and `.phasegate/manifest.json`. Codex user-level hook feature enablement remains a manual action. <!-- @work-item-id WI-207 --> <!-- @work-item-id WI-208 --> <!-- @work-item-id WI-209 --> <!-- @work-item-id WI-213 -->
|
|
113
113
|
|
|
114
114
|
For agent-driven setup planning, use:
|
|
115
115
|
|
|
@@ -416,6 +416,12 @@ For selection guidance and config examples see [Preset Selection Guide](docs/gui
|
|
|
416
416
|
"protectedFiles": {
|
|
417
417
|
"exclude": ["tsconfig.json", "package.json"]
|
|
418
418
|
},
|
|
419
|
+
"paths": {
|
|
420
|
+
"designDocs": "docs/product/construction",
|
|
421
|
+
"inceptionDocs": "docs/inception",
|
|
422
|
+
"principlesDocs": "docs/principles",
|
|
423
|
+
"folderRulesDoc": "docs/folder_management_rules.md"
|
|
424
|
+
},
|
|
419
425
|
"baseline": {
|
|
420
426
|
"enabled": true,
|
|
421
427
|
"path": ".phasegate/baseline.json"
|
|
@@ -425,6 +431,8 @@ For selection guidance and config examples see [Preset Selection Guide](docs/gui
|
|
|
425
431
|
|
|
426
432
|
`quickMode.fullModeRequiredWhen` declares which conditions force a Quick Mode change to escalate to the full `/story-implementor` flow. All three triggers default to `true` so retrofits stay safe; flip individual flags to `false` only when a project intentionally accepts the risk.
|
|
427
433
|
|
|
434
|
+
`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
|
+
|
|
428
436
|
`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).
|
|
429
437
|
|
|
430
438
|
---
|
|
@@ -64,7 +64,9 @@ The plan identifies target fields, managed artifacts, commands, validation, risk
|
|
|
64
64
|
},
|
|
65
65
|
"paths": {
|
|
66
66
|
"designDocs": "docs/product/construction",
|
|
67
|
-
"inceptionDocs": "docs/inception"
|
|
67
|
+
"inceptionDocs": "docs/inception",
|
|
68
|
+
"principlesDocs": "docs/principles",
|
|
69
|
+
"folderRulesDoc": "docs/folder_management_rules.md"
|
|
68
70
|
},
|
|
69
71
|
"reporting": {
|
|
70
72
|
"format": "json",
|
|
@@ -463,10 +465,14 @@ Quick Mode with `relaxedGates: ["phase-gate"]` relaxes `storyReflection` as well
|
|
|
463
465
|
|
|
464
466
|
#### `paths`
|
|
465
467
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
|
469
|
-
|
|
468
|
+
<!-- @work-item-id WI-214 -->
|
|
469
|
+
|
|
470
|
+
| Sub-field | Type | Default | Description |
|
|
471
|
+
|------------------|----------|--------------------------------------|-------------------------------------------------------|
|
|
472
|
+
| `designDocs` | `string` | `"docs/product/construction"` | Root directory for confirmed design documents (logical design, domain model). |
|
|
473
|
+
| `inceptionDocs` | `string` | `"docs/inception"` | Root directory for inception-phase documents (stories, unit designs). |
|
|
474
|
+
| `principlesDocs` | `string` | `"docs/principles"` | Root directory for immutable PhaseGate principles docs. |
|
|
475
|
+
| `folderRulesDoc` | `string` | `"docs/folder_management_rules.md"` | File path for the folder management rules document. |
|
|
470
476
|
|
|
471
477
|
#### `reporting`
|
|
472
478
|
|
|
@@ -611,6 +617,8 @@ docs/
|
|
|
611
617
|
|
|
612
618
|
If you move your design documents to a non-default location, update `paths` accordingly so that all validators and hooks resolve files correctly.
|
|
613
619
|
|
|
620
|
+
`paths.principlesDocs` and `paths.folderRulesDoc` also control where setup deploys PhaseGate reference docs and what the pre-tool-use hook protects. Existing repositories can keep their own documentation layout, for example `documentation/principles` and `documentation/folder_rules.md`, without adopting the default `docs/` tree. <!-- @work-item-id WI-214 -->
|
|
621
|
+
|
|
614
622
|
**How paths flow into the L2 phase-gate validator (since v0.117.0 / WI-085):**
|
|
615
623
|
|
|
616
624
|
<!-- @work-item-id WI-149 -->
|
|
@@ -621,6 +629,8 @@ If you move your design documents to a non-default location, update `paths` acco
|
|
|
621
629
|
|---|---|---|
|
|
622
630
|
| `{designDocsRoot}` | `paths.designDocs` | `docs/product/construction` |
|
|
623
631
|
| `{inceptionDocsRoot}` | `paths.inceptionDocs` | `docs/inception` |
|
|
632
|
+
| `{principlesDocsRoot}` | `paths.principlesDocs` | `docs/principles` |
|
|
633
|
+
| `{folderRulesPath}` | `paths.folderRulesDoc` | `docs/folder_management_rules.md` |
|
|
624
634
|
|
|
625
635
|
Setting `paths.designDocs` to `mydocs/product/construction` makes the L2 phase-gate require `mydocs/product/construction/{unit}/domain_model.md` instead of the default. This setting points to the construction subtree, not the broader product root; product-wide artifacts under `docs/product/` stay literal unless you define a custom `phaseDependencies.gates[]` preset. Default values match the v0.115.0 layout for full backward compatibility.
|
|
626
636
|
|
|
@@ -65,7 +65,9 @@ npx phasegate install --personal --agent claude --dry-run
|
|
|
65
65
|
npx phasegate install --personal --agent claude --apply
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
Personal install keeps team-owned files out of both the plan and apply path: `package.json`, `AGENTS.md`, `CLAUDE.md`, `.husky/*`, `.github/workflows/*`, `.gitignore`, GitHub CLI config, repo secrets, and CI settings are not touched. PhaseGate writes `.phasegate-local/phasegate.config.json`, creates real local-only agent runtime artifacts for the selected agent (`.claude/settings.json` + `.claude/skills/` and/or `.codex/hooks.json` + `.codex/skills/`), records `.phasegate/manifest.json`, and manages a local exclude block in `.git/info/exclude`. Codex user-level hook feature enablement is still reported as a manual action. <!-- @work-item-id WI-207 --> <!-- @work-item-id WI-208 --> <!-- @work-item-id WI-209 -->
|
|
68
|
+
Personal install keeps team-owned files out of both the plan and apply path: `package.json`, `AGENTS.md`, `CLAUDE.md`, `.husky/*`, `.github/workflows/*`, `.gitignore`, GitHub CLI config, repo secrets, and CI settings are not touched. PhaseGate writes `.phasegate-local/phasegate.config.json`, creates local agent context (`.claude/CLAUDE.local.md` and/or `.codex/AGENTS.local.md`), creates real local-only agent runtime artifacts for the selected agent (`.claude/settings.json` + `.claude/skills/` and/or `.codex/hooks.json` + `.codex/skills/`), deploys local git hooks under `.git/hooks/`, copies reference docs under `.phasegate-local/docs/`, records `.phasegate/manifest.json`, and manages a local exclude block in `.git/info/exclude`. Codex user-level hook feature enablement is still reported as a manual action. <!-- @work-item-id WI-207 --> <!-- @work-item-id WI-208 --> <!-- @work-item-id WI-209 --> <!-- @work-item-id WI-213 -->
|
|
69
|
+
|
|
70
|
+
If an existing repository keeps design or governance docs outside `docs/`, set `paths.designDocs`, `paths.inceptionDocs`, `paths.principlesDocs`, and `paths.folderRulesDoc` in `phasegate.config.json` before setup/reconcile. PhaseGate deploy and hook protection use those mappings instead of forcing the default `docs/` layout. <!-- @work-item-id WI-214 -->
|
|
69
71
|
|
|
70
72
|
For agent-readable planning before writing files:
|
|
71
73
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# PhaseGate personal pre-commit hook.
|
|
3
|
+
# Local-only counterpart of the Husky pre-commit defense.
|
|
4
|
+
|
|
5
|
+
PHASEGATE_CMD="${PHASEGATE_CMD:-npx phasegate}"
|
|
6
|
+
|
|
7
|
+
echo "PhaseGate personal pre-commit"
|
|
8
|
+
|
|
9
|
+
$PHASEGATE_CMD lint
|
|
10
|
+
L1_EXIT=$?
|
|
11
|
+
if [ $L1_EXIT -ne 0 ]; then
|
|
12
|
+
echo "PhaseGate lint failed."
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if [ "${HARNESS_QUICK_MODE:-0}" = "1" ]; then
|
|
17
|
+
$PHASEGATE_CMD validate --layer L2 --format human --no-l4
|
|
18
|
+
else
|
|
19
|
+
$PHASEGATE_CMD validate --layer L2 --format human
|
|
20
|
+
fi
|
|
21
|
+
|
|
@@ -54,8 +54,10 @@
|
|
|
54
54
|
"deadCodeGC": false
|
|
55
55
|
},
|
|
56
56
|
"paths": {
|
|
57
|
-
"designDocs": "
|
|
58
|
-
"inceptionDocs": "
|
|
57
|
+
"designDocs": ".phasegate-local/product/construction",
|
|
58
|
+
"inceptionDocs": ".phasegate-local/inception",
|
|
59
|
+
"principlesDocs": ".phasegate-local/docs/principles",
|
|
60
|
+
"folderRulesDoc": ".phasegate-local/docs/folder_management_rules.md"
|
|
59
61
|
},
|
|
60
62
|
"reporting": {
|
|
61
63
|
"format": "json",
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
"harnesses": {},
|
|
21
21
|
"paths": {
|
|
22
22
|
"designDocs": "docs/product/construction",
|
|
23
|
-
"inceptionDocs": "docs/inception"
|
|
23
|
+
"inceptionDocs": "docs/inception",
|
|
24
|
+
"principlesDocs": "docs/principles",
|
|
25
|
+
"folderRulesDoc": "docs/folder_management_rules.md"
|
|
24
26
|
},
|
|
25
27
|
"reporting": {
|
|
26
28
|
"format": "json",
|
package/package.json
CHANGED
package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* @work-item-id WI-201
|
|
6
6
|
* @work-item-id WI-202 / WI-204
|
|
7
7
|
* @work-item-id WI-206
|
|
8
|
+
* @work-item-id WI-214
|
|
8
9
|
*
|
|
9
10
|
* HandlePreToolUseUseCase
|
|
10
11
|
* PreToolUse Hook処理のオーケストレーション
|
|
@@ -116,7 +117,7 @@ export class HandlePreToolUseUseCase {
|
|
|
116
117
|
const blockedFilePath = metadata?.blockedFilePath ?? input.targetFilePaths[0];
|
|
117
118
|
|
|
118
119
|
if (metadata?.reason === "PROTECTED_FILE") {
|
|
119
|
-
return
|
|
120
|
+
return this.buildProtectedFileBlockOutput(blockedFilePath);
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
if (metadata?.reason === "PHASE_GATE") {
|
|
@@ -492,15 +493,22 @@ export class HandlePreToolUseUseCase {
|
|
|
492
493
|
message: (fp) =>
|
|
493
494
|
`保護ファイルへの書き込みがブロックされました: ${fp}\nClaude Code の設定変更は /update-config スキルを使用してください。`,
|
|
494
495
|
},
|
|
495
|
-
{
|
|
496
|
-
pattern: /(?:^|\/)docs\/principles\//,
|
|
497
|
-
message: (fp) =>
|
|
498
|
-
`保護ファイルへの書き込みがブロックされました: ${fp}\n原則ドキュメントは immutable です。変更はできません。`,
|
|
499
|
-
},
|
|
500
496
|
];
|
|
501
497
|
|
|
502
|
-
private
|
|
498
|
+
private async buildProtectedFileBlockOutput(blockedFilePath: string | undefined): Promise<HandlePreToolUseOutput> {
|
|
503
499
|
const fp = blockedFilePath ?? "不明なファイル";
|
|
500
|
+
const dynamicPrinciplesPattern = await this.findMatchingDynamicProtectedPattern(fp, "principles");
|
|
501
|
+
if (dynamicPrinciplesPattern !== null) {
|
|
502
|
+
return {
|
|
503
|
+
shouldBlock: true,
|
|
504
|
+
blockedFilePath,
|
|
505
|
+
blockReason: "PROTECTED_FILE",
|
|
506
|
+
error: {
|
|
507
|
+
message: `保護ファイルへの書き込みがブロックされました: ${fp}\n原則ドキュメントは immutable です。変更はできません。対象パターン: ${dynamicPrinciplesPattern}`,
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
504
512
|
const matched = HandlePreToolUseUseCase.PROTECTED_FILE_GUIDANCE.find(({ pattern }) => pattern.test(fp));
|
|
505
513
|
const message = matched
|
|
506
514
|
? matched.message(fp)
|
|
@@ -514,6 +522,20 @@ export class HandlePreToolUseUseCase {
|
|
|
514
522
|
};
|
|
515
523
|
}
|
|
516
524
|
|
|
525
|
+
private async findMatchingDynamicProtectedPattern(filePath: string, kind: "principles"): Promise<string | null> {
|
|
526
|
+
const patterns = await this.configQueryPort.getProtectedFilePatterns();
|
|
527
|
+
for (const pattern of patterns) {
|
|
528
|
+
if (kind === "principles" && !pattern.endsWith("/**")) {
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
const prefix = pattern.slice(0, -"/**".length);
|
|
532
|
+
if (filePath === prefix || filePath.startsWith(`${prefix}/`)) {
|
|
533
|
+
return pattern;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
|
|
517
539
|
private static buildStoryReflectionBlockOutput(
|
|
518
540
|
blockedFilePath: string | undefined,
|
|
519
541
|
blockers: readonly string[],
|
|
@@ -36,6 +36,7 @@ interface HarnessesSection {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
interface ProtectedFilesSection {
|
|
39
|
+
patterns?: string[];
|
|
39
40
|
exclude?: string[];
|
|
40
41
|
}
|
|
41
42
|
|
|
@@ -57,6 +58,12 @@ interface AgentIntegrationSection {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
interface HarnessConfigDocument {
|
|
61
|
+
paths?: {
|
|
62
|
+
designDocs?: string;
|
|
63
|
+
inceptionDocs?: string;
|
|
64
|
+
principlesDocs?: string;
|
|
65
|
+
folderRulesDoc?: string;
|
|
66
|
+
};
|
|
60
67
|
harnesses?: HarnessesSection;
|
|
61
68
|
project?: ProjectSection;
|
|
62
69
|
protectedFiles?: ProtectedFilesSection;
|
|
@@ -102,8 +109,15 @@ export class HarnessConfigConfigQueryAdapter implements ConfigQueryPort {
|
|
|
102
109
|
}
|
|
103
110
|
|
|
104
111
|
async getProtectedFilePatterns(): Promise<string[]> {
|
|
105
|
-
|
|
106
|
-
|
|
112
|
+
const config = this.loadConfig();
|
|
113
|
+
const configured = config.protectedFiles?.patterns ?? [];
|
|
114
|
+
const principlesDocs = config.paths?.principlesDocs ?? 'docs/principles';
|
|
115
|
+
const folderRulesDoc = config.paths?.folderRulesDoc ?? 'docs/folder_management_rules.md';
|
|
116
|
+
return [
|
|
117
|
+
...configured,
|
|
118
|
+
`${normalizeProjectPath(principlesDocs)}/**`,
|
|
119
|
+
normalizeProjectPath(folderRulesDoc),
|
|
120
|
+
];
|
|
107
121
|
}
|
|
108
122
|
|
|
109
123
|
async getProtectedFileExclusions(): Promise<string[]> {
|
|
@@ -119,12 +133,13 @@ export class HarnessConfigConfigQueryAdapter implements ConfigQueryPort {
|
|
|
119
133
|
getProjectPaths(): ProjectPaths {
|
|
120
134
|
const config = this.loadConfig();
|
|
121
135
|
const paths = config.project?.paths;
|
|
136
|
+
const topLevelPaths = config.paths;
|
|
122
137
|
|
|
123
138
|
return ProjectPaths.create(
|
|
124
139
|
paths?.source ?? ['scripts/harness'],
|
|
125
140
|
{
|
|
126
|
-
construction: paths?.docs?.construction ?? 'docs/product/construction',
|
|
127
|
-
inception: paths?.docs?.inception ?? 'docs/inception',
|
|
141
|
+
construction: paths?.docs?.construction ?? topLevelPaths?.designDocs ?? 'docs/product/construction',
|
|
142
|
+
inception: paths?.docs?.inception ?? topLevelPaths?.inceptionDocs ?? 'docs/inception',
|
|
128
143
|
},
|
|
129
144
|
);
|
|
130
145
|
}
|
|
@@ -145,3 +160,7 @@ export class HarnessConfigConfigQueryAdapter implements ConfigQueryPort {
|
|
|
145
160
|
return enforce === true;
|
|
146
161
|
}
|
|
147
162
|
}
|
|
163
|
+
|
|
164
|
+
function normalizeProjectPath(path: string): string {
|
|
165
|
+
return path.replace(/\\/g, '/').replace(/\/+$/g, '');
|
|
166
|
+
}
|
|
@@ -10,18 +10,29 @@ import { ConfigValidationError } from '../errors/config-validation-error.js';
|
|
|
10
10
|
export interface PathsConfigProps {
|
|
11
11
|
readonly designDocs: string;
|
|
12
12
|
readonly inceptionDocs: string;
|
|
13
|
+
readonly principlesDocs?: string;
|
|
14
|
+
readonly folderRulesDoc?: string;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export class PathsConfig {
|
|
16
18
|
readonly designDocs: string;
|
|
17
19
|
readonly inceptionDocs: string;
|
|
20
|
+
readonly principlesDocs: string;
|
|
21
|
+
readonly folderRulesDoc: string;
|
|
18
22
|
|
|
19
23
|
constructor(props: PathsConfigProps) {
|
|
24
|
+
const principlesDocs = props.principlesDocs ?? 'docs/principles';
|
|
25
|
+
const folderRulesDoc = props.folderRulesDoc ?? 'docs/folder_management_rules.md';
|
|
26
|
+
|
|
20
27
|
PathsConfig.validatePath(props.designDocs, 'designDocs');
|
|
21
28
|
PathsConfig.validatePath(props.inceptionDocs, 'inceptionDocs');
|
|
29
|
+
PathsConfig.validatePath(principlesDocs, 'principlesDocs');
|
|
30
|
+
PathsConfig.validatePath(folderRulesDoc, 'folderRulesDoc');
|
|
22
31
|
|
|
23
32
|
this.designDocs = props.designDocs;
|
|
24
33
|
this.inceptionDocs = props.inceptionDocs;
|
|
34
|
+
this.principlesDocs = principlesDocs;
|
|
35
|
+
this.folderRulesDoc = folderRulesDoc;
|
|
25
36
|
}
|
|
26
37
|
|
|
27
38
|
private static validatePath(value: string, fieldName: string): void {
|
|
@@ -44,7 +55,9 @@ export class PathsConfig {
|
|
|
44
55
|
equals(other: PathsConfig): boolean {
|
|
45
56
|
return (
|
|
46
57
|
this.designDocs === other.designDocs &&
|
|
47
|
-
this.inceptionDocs === other.inceptionDocs
|
|
58
|
+
this.inceptionDocs === other.inceptionDocs &&
|
|
59
|
+
this.principlesDocs === other.principlesDocs &&
|
|
60
|
+
this.folderRulesDoc === other.folderRulesDoc
|
|
48
61
|
);
|
|
49
62
|
}
|
|
50
63
|
}
|
|
@@ -41,7 +41,9 @@
|
|
|
41
41
|
},
|
|
42
42
|
"paths": {
|
|
43
43
|
"designDocs": "docs/product/construction",
|
|
44
|
-
"inceptionDocs": "docs/inception"
|
|
44
|
+
"inceptionDocs": "docs/inception",
|
|
45
|
+
"principlesDocs": "docs/principles",
|
|
46
|
+
"folderRulesDoc": "docs/folder_management_rules.md"
|
|
45
47
|
},
|
|
46
48
|
"reporting": {
|
|
47
49
|
"format": "json",
|
|
@@ -41,7 +41,9 @@
|
|
|
41
41
|
},
|
|
42
42
|
"paths": {
|
|
43
43
|
"designDocs": "docs/product/construction",
|
|
44
|
-
"inceptionDocs": "docs/inception"
|
|
44
|
+
"inceptionDocs": "docs/inception",
|
|
45
|
+
"principlesDocs": "docs/principles",
|
|
46
|
+
"folderRulesDoc": "docs/folder_management_rules.md"
|
|
45
47
|
},
|
|
46
48
|
"reporting": {
|
|
47
49
|
"format": "json",
|
|
@@ -41,7 +41,9 @@
|
|
|
41
41
|
},
|
|
42
42
|
"paths": {
|
|
43
43
|
"designDocs": "docs/product/construction",
|
|
44
|
-
"inceptionDocs": "docs/inception"
|
|
44
|
+
"inceptionDocs": "docs/inception",
|
|
45
|
+
"principlesDocs": "docs/principles",
|
|
46
|
+
"folderRulesDoc": "docs/folder_management_rules.md"
|
|
45
47
|
},
|
|
46
48
|
"reporting": {
|
|
47
49
|
"format": "json",
|
package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json
CHANGED
|
@@ -435,7 +435,9 @@
|
|
|
435
435
|
"additionalProperties": false,
|
|
436
436
|
"required": [
|
|
437
437
|
"designDocs",
|
|
438
|
-
"inceptionDocs"
|
|
438
|
+
"inceptionDocs",
|
|
439
|
+
"principlesDocs",
|
|
440
|
+
"folderRulesDoc"
|
|
439
441
|
],
|
|
440
442
|
"properties": {
|
|
441
443
|
"designDocs": {
|
|
@@ -445,6 +447,14 @@
|
|
|
445
447
|
"inceptionDocs": {
|
|
446
448
|
"type": "string",
|
|
447
449
|
"minLength": 1
|
|
450
|
+
},
|
|
451
|
+
"principlesDocs": {
|
|
452
|
+
"type": "string",
|
|
453
|
+
"minLength": 1
|
|
454
|
+
},
|
|
455
|
+
"folderRulesDoc": {
|
|
456
|
+
"type": "string",
|
|
457
|
+
"minLength": 1
|
|
448
458
|
}
|
|
449
459
|
}
|
|
450
460
|
},
|
|
@@ -488,6 +498,14 @@
|
|
|
488
498
|
"type": "object",
|
|
489
499
|
"additionalProperties": false,
|
|
490
500
|
"properties": {
|
|
501
|
+
"patterns": {
|
|
502
|
+
"type": "array",
|
|
503
|
+
"items": {
|
|
504
|
+
"type": "string",
|
|
505
|
+
"minLength": 1
|
|
506
|
+
},
|
|
507
|
+
"uniqueItems": true
|
|
508
|
+
},
|
|
491
509
|
"exclude": {
|
|
492
510
|
"type": "array",
|
|
493
511
|
"items": {
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
// @work-item-id WI-208
|
|
12
12
|
// @work-item-id WI-209
|
|
13
13
|
// @work-item-id WI-210
|
|
14
|
+
// @work-item-id WI-213
|
|
15
|
+
// @work-item-id WI-214
|
|
14
16
|
|
|
15
17
|
import { mkdir, readFile, writeFile, copyFile, chmod, access, lstat, readlink, symlink, readdir, rm } from "node:fs/promises";
|
|
16
18
|
import { dirname, join } from "node:path";
|
|
@@ -89,6 +91,8 @@ const TEXT_BEGIN = "# phasegate personal install exclude (BEGIN)";
|
|
|
89
91
|
const TEXT_END = "# phasegate personal install exclude (END)";
|
|
90
92
|
const PERSONAL_AGENT_RUNTIME_FILES = new Set([".claude/settings.json", ".codex/hooks.json"]);
|
|
91
93
|
const SHARED_SKILLS_VERSION_PATH = "skills/.harness-version";
|
|
94
|
+
const PERSONAL_PRINCIPLES_DOCS = ".phasegate-local/docs/principles";
|
|
95
|
+
const PERSONAL_FOLDER_RULES_DOC = ".phasegate-local/docs/folder_management_rules.md";
|
|
92
96
|
|
|
93
97
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
94
98
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -624,6 +628,12 @@ export class RunInstallUseCase {
|
|
|
624
628
|
},
|
|
625
629
|
...(options.includeClaude
|
|
626
630
|
? [
|
|
631
|
+
{
|
|
632
|
+
path: ".claude/CLAUDE.local.md",
|
|
633
|
+
strategy: "markdown-managed" as const,
|
|
634
|
+
templatePath: "docs/templates/agent-context/CLAUDE.md.template.md",
|
|
635
|
+
block: { start: MARKDOWN_BEGIN, end: MARKDOWN_END, content: "phasegate personal CLAUDE.local.md managed section" },
|
|
636
|
+
},
|
|
627
637
|
{
|
|
628
638
|
path: ".claude/settings.json",
|
|
629
639
|
strategy: "copy" as const,
|
|
@@ -633,6 +643,12 @@ export class RunInstallUseCase {
|
|
|
633
643
|
: []),
|
|
634
644
|
...(options.includeCodex
|
|
635
645
|
? [
|
|
646
|
+
{
|
|
647
|
+
path: ".codex/AGENTS.local.md",
|
|
648
|
+
strategy: "markdown-managed" as const,
|
|
649
|
+
templatePath: "docs/templates/agent-context/AGENTS.md.template.md",
|
|
650
|
+
block: { start: MARKDOWN_BEGIN, end: MARKDOWN_END, content: "phasegate personal AGENTS.local.md managed section" },
|
|
651
|
+
},
|
|
636
652
|
{
|
|
637
653
|
path: ".codex/hooks.json",
|
|
638
654
|
strategy: "copy" as const,
|
|
@@ -640,6 +656,38 @@ export class RunInstallUseCase {
|
|
|
640
656
|
},
|
|
641
657
|
]
|
|
642
658
|
: []),
|
|
659
|
+
{
|
|
660
|
+
path: ".git/hooks/pre-commit",
|
|
661
|
+
strategy: "copy" as const,
|
|
662
|
+
templatePath: "docs/templates/personal/hooks/pre-commit",
|
|
663
|
+
executable: true,
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
path: ".git/hooks/commit-msg",
|
|
667
|
+
strategy: "copy" as const,
|
|
668
|
+
templatePath: "docs/templates/personal/hooks/commit-msg",
|
|
669
|
+
executable: true,
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
path: PERSONAL_FOLDER_RULES_DOC,
|
|
673
|
+
strategy: "copy" as const,
|
|
674
|
+
templatePath: "docs/folder_management_rules.md",
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
path: `${PERSONAL_PRINCIPLES_DOCS}/architecture-philosophy.md`,
|
|
678
|
+
strategy: "copy" as const,
|
|
679
|
+
templatePath: "docs/principles/architecture-philosophy.md",
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
path: `${PERSONAL_PRINCIPLES_DOCS}/model-routing.md`,
|
|
683
|
+
strategy: "copy" as const,
|
|
684
|
+
templatePath: "docs/principles/model-routing.md",
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
path: `${PERSONAL_PRINCIPLES_DOCS}/testing-rules.md`,
|
|
688
|
+
strategy: "copy" as const,
|
|
689
|
+
templatePath: "docs/principles/testing-rules.md",
|
|
690
|
+
},
|
|
643
691
|
{
|
|
644
692
|
path: ".git/info/exclude",
|
|
645
693
|
strategy: "text-managed" as const,
|
package/scripts/harness/main.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* @work-item-id WI-202 / WI-204
|
|
18
18
|
* @work-item-id WI-205
|
|
19
19
|
* @work-item-id WI-206
|
|
20
|
+
* @work-item-id WI-213
|
|
20
21
|
*
|
|
21
22
|
* Phasegate CLI エントリポイント。
|
|
22
23
|
* 各Unitの Composition Root からハンドラーを取得し、コマンドに応じてディスパッチする。
|
|
@@ -167,7 +168,7 @@ Setup:
|
|
|
167
168
|
--skills <core|all>, --agent <claude|codex|both>, --workflow <standard|strict>,
|
|
168
169
|
--with-husky, --with-ci, --yes)
|
|
169
170
|
update-skills Alias for reconcile (kept for compatibility)
|
|
170
|
-
doctor Diagnose silent installation failures (--json, --strict, --agent <claude|codex|both>, --report-out <path>)
|
|
171
|
+
doctor Diagnose silent installation failures (--json, --strict, --personal, --agent <claude|codex|both>, --report-out <path>)
|
|
171
172
|
scaffold-wi <unit|_cross> <story|issue|chore>
|
|
172
173
|
Create docs/inception/{unit}/WI-XXX/description.md
|
|
173
174
|
emit-agent-rules Print AGENTS.md / CLAUDE.md WI workflow rules block
|
|
@@ -2001,7 +2002,7 @@ async function main(): Promise<void> {
|
|
|
2001
2002
|
}
|
|
2002
2003
|
|
|
2003
2004
|
case "doctor": {
|
|
2004
|
-
const KNOWN_DOCTOR_FLAGS = ["--json", "--strict", "--agent", "--report-out"];
|
|
2005
|
+
const KNOWN_DOCTOR_FLAGS = ["--json", "--strict", "--personal", "--agent", "--report-out"];
|
|
2005
2006
|
const flagError = validateKnownFlags(args, KNOWN_DOCTOR_FLAGS);
|
|
2006
2007
|
if (flagError) {
|
|
2007
2008
|
console.error(flagError);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// Note: import.meta.url を使わず、呼び出し元 (main.ts) がパスを解決して渡す設計。
|
|
8
8
|
|
|
9
9
|
import { promises as fs } from "node:fs";
|
|
10
|
-
import { join } from "node:path";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
11
|
|
|
12
12
|
const HARNESS_VERSION_FILE = ".harness-version";
|
|
13
13
|
const SKILLS_SOURCE_DIR = "skills";
|
|
@@ -18,6 +18,8 @@ const CODEX_SKILLS_LINK_DIR = ".codex";
|
|
|
18
18
|
const HARNESS_CONFIG_FILE = "phasegate.config.json";
|
|
19
19
|
const HOOKS_TEMPLATE_DIR = join("templates", ".claude");
|
|
20
20
|
const HOOKS_TARGET_DIR = ".claude";
|
|
21
|
+
const DEFAULT_PRINCIPLES_DOCS = join("docs", "principles");
|
|
22
|
+
const DEFAULT_FOLDER_RULES_DOC = join("docs", "folder_management_rules.md");
|
|
21
23
|
|
|
22
24
|
// ── Skill Category Map ──
|
|
23
25
|
|
|
@@ -516,33 +518,55 @@ export interface DeployDesignDocsResult {
|
|
|
516
518
|
skippedFiles: string[];
|
|
517
519
|
}
|
|
518
520
|
|
|
521
|
+
interface DesignDocsPathConfig {
|
|
522
|
+
readonly principlesDocs: string;
|
|
523
|
+
readonly folderRulesDoc: string;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
async function resolveDesignDocsPathConfig(projectRoot: string): Promise<DesignDocsPathConfig> {
|
|
527
|
+
try {
|
|
528
|
+
const raw = await fs.readFile(join(projectRoot, HARNESS_CONFIG_FILE), "utf-8");
|
|
529
|
+
const config = JSON.parse(raw) as { paths?: { principlesDocs?: unknown; folderRulesDoc?: unknown } };
|
|
530
|
+
const principlesDocs =
|
|
531
|
+
typeof config.paths?.principlesDocs === "string" && config.paths.principlesDocs.length > 0
|
|
532
|
+
? config.paths.principlesDocs
|
|
533
|
+
: DEFAULT_PRINCIPLES_DOCS;
|
|
534
|
+
const folderRulesDoc =
|
|
535
|
+
typeof config.paths?.folderRulesDoc === "string" && config.paths.folderRulesDoc.length > 0
|
|
536
|
+
? config.paths.folderRulesDoc
|
|
537
|
+
: DEFAULT_FOLDER_RULES_DOC;
|
|
538
|
+
return { principlesDocs, folderRulesDoc };
|
|
539
|
+
} catch {
|
|
540
|
+
return { principlesDocs: DEFAULT_PRINCIPLES_DOCS, folderRulesDoc: DEFAULT_FOLDER_RULES_DOC };
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
519
544
|
export async function deployDesignDocs(harnessRoot: string, projectRoot: string): Promise<DeployDesignDocsResult> {
|
|
520
545
|
const copiedFiles: string[] = [];
|
|
521
546
|
const skippedFiles: string[] = [];
|
|
522
|
-
const
|
|
523
|
-
const principlesTargetDir = join(
|
|
547
|
+
const pathConfig = await resolveDesignDocsPathConfig(projectRoot);
|
|
548
|
+
const principlesTargetDir = join(projectRoot, pathConfig.principlesDocs);
|
|
524
549
|
|
|
525
|
-
await fs.mkdir(
|
|
550
|
+
await fs.mkdir(join(projectRoot, dirname(pathConfig.folderRulesDoc)), { recursive: true });
|
|
526
551
|
await fs.mkdir(principlesTargetDir, { recursive: true });
|
|
527
552
|
|
|
528
|
-
const
|
|
529
|
-
const
|
|
530
|
-
const folderRulesTarget = join(projectRoot, folderRulesRelativePath);
|
|
553
|
+
const folderRulesSource = join(harnessRoot, DEFAULT_FOLDER_RULES_DOC);
|
|
554
|
+
const folderRulesTarget = join(projectRoot, pathConfig.folderRulesDoc);
|
|
531
555
|
|
|
532
556
|
try {
|
|
533
557
|
await fs.access(folderRulesTarget);
|
|
534
|
-
skippedFiles.push(
|
|
558
|
+
skippedFiles.push(pathConfig.folderRulesDoc);
|
|
535
559
|
} catch {
|
|
536
560
|
try {
|
|
537
561
|
await fs.access(folderRulesSource);
|
|
538
562
|
await fs.copyFile(folderRulesSource, folderRulesTarget);
|
|
539
|
-
copiedFiles.push(
|
|
563
|
+
copiedFiles.push(pathConfig.folderRulesDoc);
|
|
540
564
|
} catch {
|
|
541
565
|
// 配置元が存在しない場合はスキップ
|
|
542
566
|
}
|
|
543
567
|
}
|
|
544
568
|
|
|
545
|
-
const principlesSourceDir = join(harnessRoot,
|
|
569
|
+
const principlesSourceDir = join(harnessRoot, DEFAULT_PRINCIPLES_DOCS);
|
|
546
570
|
|
|
547
571
|
try {
|
|
548
572
|
const principleEntries = await fs.readdir(principlesSourceDir, { withFileTypes: true });
|
|
@@ -552,7 +576,7 @@ export async function deployDesignDocs(harnessRoot: string, projectRoot: string)
|
|
|
552
576
|
.sort();
|
|
553
577
|
|
|
554
578
|
for (const principleFile of principleFiles) {
|
|
555
|
-
const relativePath = join(
|
|
579
|
+
const relativePath = join(pathConfig.principlesDocs, principleFile);
|
|
556
580
|
const sourcePath = join(principlesSourceDir, principleFile);
|
|
557
581
|
const targetPath = join(projectRoot, relativePath);
|
|
558
582
|
|