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 CHANGED
@@ -97,7 +97,17 @@ claude
97
97
 
98
98
  `init` は legacy 互換の bootstrap 経路です。既存 hooks / scripts / package metadata に構造化 merge したい場合は `install` を使います。「設計してから書け」を強制する仕組みなので、設計文書はユーザーがスキル経由で作るのが既定動作です。
99
99
 
100
- 既存プロジェクトに導入する場合は、構造化 install で差分を確認してから適用します。
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
- | Sub-field | Type | Default | Description |
467
- |-----------------|----------|--------------------------------|-------------------------------------------------------|
468
- | `designDocs` | `string` | `"docs/product/construction"` | Root directory for confirmed design documents (logical design, domain model). |
469
- | `inceptionDocs` | `string` | `"docs/inception"` | Root directory for inception-phase documents (stories, unit designs). |
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,7 @@
1
+ #!/usr/bin/env sh
2
+ # PhaseGate personal commit-msg hook.
3
+ # Local-only counterpart of the Husky commit-msg defense.
4
+
5
+ PHASEGATE_CMD="${PHASEGATE_CMD:-npx phasegate}"
6
+
7
+ $PHASEGATE_CMD commit-msg "$1"
@@ -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": "docs/product/construction",
58
- "inceptionDocs": "docs/inception"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phasegate",
3
- "version": "0.160.16",
3
+ "version": "0.160.18",
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",
@@ -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 HandlePreToolUseUseCase.buildProtectedFileBlockOutput(blockedFilePath);
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 static buildProtectedFileBlockOutput(blockedFilePath: string | undefined): HandlePreToolUseOutput {
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
- // Wave 2 では追加カスタムパターンなし
106
- return [];
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
+ }
@@ -109,6 +109,8 @@ export interface HarnessConfigResolvedDocument {
109
109
  paths: {
110
110
  designDocs: string;
111
111
  inceptionDocs: string;
112
+ principlesDocs?: string;
113
+ folderRulesDoc?: string;
112
114
  };
113
115
  reporting: {
114
116
  format: string;
@@ -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",
@@ -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,
@@ -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 docsTargetDir = join(projectRoot, "docs");
523
- const principlesTargetDir = join(docsTargetDir, "principles");
547
+ const pathConfig = await resolveDesignDocsPathConfig(projectRoot);
548
+ const principlesTargetDir = join(projectRoot, pathConfig.principlesDocs);
524
549
 
525
- await fs.mkdir(docsTargetDir, { recursive: true });
550
+ await fs.mkdir(join(projectRoot, dirname(pathConfig.folderRulesDoc)), { recursive: true });
526
551
  await fs.mkdir(principlesTargetDir, { recursive: true });
527
552
 
528
- const folderRulesRelativePath = join("docs", "folder_management_rules.md");
529
- const folderRulesSource = join(harnessRoot, folderRulesRelativePath);
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(folderRulesRelativePath);
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(folderRulesRelativePath);
563
+ copiedFiles.push(pathConfig.folderRulesDoc);
540
564
  } catch {
541
565
  // 配置元が存在しない場合はスキップ
542
566
  }
543
567
  }
544
568
 
545
- const principlesSourceDir = join(harnessRoot, "docs", "principles");
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("docs", "principles", principleFile);
579
+ const relativePath = join(pathConfig.principlesDocs, principleFile);
556
580
  const sourcePath = join(principlesSourceDir, principleFile);
557
581
  const targetPath = join(projectRoot, relativePath);
558
582