phasegate 0.160.17 → 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
@@ -305,6 +305,12 @@ L2-STORY-REFLECTION バリデータがこのアノテーションを検出し、
305
305
  "phaseDependencies": { "preset": "standard", "storyReflection": { "enabled": true } },
306
306
  "quickMode": { "allowedCategories": ["bugfix", "docs", "test", "config"] },
307
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
+ },
308
314
  "baseline": { "enabled": true, "path": ".phasegate/baseline.json" }
309
315
  }
310
316
  ```
@@ -329,6 +335,7 @@ phasegate には独立した 3 系統の preset があります。役割が違
329
335
  |---|---|
330
336
  | `quickMode.fullModeRequiredWhen` | Quick Mode → Full Mode への強制エスカレート条件(複数カテゴリ混在 / 新規ドメインファイル / API 契約変更)。安全側の default は全 `true` |
331
337
  | `protectedFiles.exclude` | デフォルト保護対象(`package.json`, `tsconfig.json`, `biome.json` 等)から除外したいファイル |
338
+ | `paths.designDocs` / `paths.inceptionDocs` / `paths.principlesDocs` / `paths.folderRulesDoc` | PhaseGate が参照・配置する設計/原則/配置ルール文書の場所。`docs/` 以外の既存規約にもマッピング可能 |
332
339
  | `baseline.enabled` | 既存リポジトリ導入時の retrofit grandfather。default `true`。`npx phasegate baseline` で snapshot 生成 |
333
340
  | `phaseDependencies.storyReflection` | inception 設計が product docs に反映されるまで `src/{unit}/` への書き込みをブロック |
334
341
 
package/README.md CHANGED
@@ -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
 
@@ -67,6 +67,8 @@ npx phasegate install --personal --agent claude --apply
67
67
 
68
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
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 -->
71
+
70
72
  For agent-readable planning before writing files:
71
73
 
72
74
  ```bash
@@ -55,7 +55,9 @@
55
55
  },
56
56
  "paths": {
57
57
  "designDocs": ".phasegate-local/product/construction",
58
- "inceptionDocs": ".phasegate-local/inception"
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.17",
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": {
@@ -12,6 +12,7 @@
12
12
  // @work-item-id WI-209
13
13
  // @work-item-id WI-210
14
14
  // @work-item-id WI-213
15
+ // @work-item-id WI-214
15
16
 
16
17
  import { mkdir, readFile, writeFile, copyFile, chmod, access, lstat, readlink, symlink, readdir, rm } from "node:fs/promises";
17
18
  import { dirname, join } from "node:path";
@@ -90,6 +91,8 @@ const TEXT_BEGIN = "# phasegate personal install exclude (BEGIN)";
90
91
  const TEXT_END = "# phasegate personal install exclude (END)";
91
92
  const PERSONAL_AGENT_RUNTIME_FILES = new Set([".claude/settings.json", ".codex/hooks.json"]);
92
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";
93
96
 
94
97
  function isRecord(value: unknown): value is Record<string, unknown> {
95
98
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -666,22 +669,22 @@ export class RunInstallUseCase {
666
669
  executable: true,
667
670
  },
668
671
  {
669
- path: ".phasegate-local/docs/folder_management_rules.md",
672
+ path: PERSONAL_FOLDER_RULES_DOC,
670
673
  strategy: "copy" as const,
671
674
  templatePath: "docs/folder_management_rules.md",
672
675
  },
673
676
  {
674
- path: ".phasegate-local/docs/principles/architecture-philosophy.md",
677
+ path: `${PERSONAL_PRINCIPLES_DOCS}/architecture-philosophy.md`,
675
678
  strategy: "copy" as const,
676
679
  templatePath: "docs/principles/architecture-philosophy.md",
677
680
  },
678
681
  {
679
- path: ".phasegate-local/docs/principles/model-routing.md",
682
+ path: `${PERSONAL_PRINCIPLES_DOCS}/model-routing.md`,
680
683
  strategy: "copy" as const,
681
684
  templatePath: "docs/principles/model-routing.md",
682
685
  },
683
686
  {
684
- path: ".phasegate-local/docs/principles/testing-rules.md",
687
+ path: `${PERSONAL_PRINCIPLES_DOCS}/testing-rules.md`,
685
688
  strategy: "copy" as const,
686
689
  templatePath: "docs/principles/testing-rules.md",
687
690
  },
@@ -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