phasegate 0.145.4 → 0.146.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phasegate",
3
- "version": "0.145.4",
3
+ "version": "0.146.0",
4
4
  "packageManager": "pnpm@10.30.1",
5
5
  "description": "Phasegate — AI-agnostic quality defense toolkit. Enforces structural integrity between design intent and code.",
6
6
  "license": "MIT",
@@ -0,0 +1,64 @@
1
+ // @unit installation
2
+ // @layer application
3
+ // @work-item-id WI-143
4
+
5
+ import { join, relative, sep } from "node:path";
6
+ import { DiagnosticFinding } from "../../domain/diagnostic-finding.js";
7
+ import type { HeuristicCheck } from "../../domain/ports/heuristic-check.js";
8
+ import type { FileInspectorPort } from "../ports/file-inspector-port.js";
9
+
10
+ interface PhasegateConfigProbe {
11
+ readonly quickMode?: {
12
+ readonly relaxedGates?: readonly string[];
13
+ };
14
+ }
15
+
16
+ export class WiWorkflowDriftCheck implements HeuristicCheck {
17
+ readonly checkId = "wi-workflow-drift" as const;
18
+
19
+ async run(projectRoot: string, inspector: FileInspectorPort): Promise<DiagnosticFinding | null> {
20
+ const inceptionRoot = join(projectRoot, "docs", "inception");
21
+ const files = await inspector.listFiles(inceptionRoot);
22
+ const relativeFiles = files.map((file) => toPosix(relative(projectRoot, file)));
23
+ const workItemCount = relativeFiles.filter(isWorkItemDescription).length;
24
+ const adHocPlans = relativeFiles.filter(isAdHocPlan);
25
+ const hasPhaseGateRelaxed = await this.hasRelaxedPhaseGate(projectRoot, inspector);
26
+
27
+ if (workItemCount > 0 || adHocPlans.length === 0) {
28
+ return null;
29
+ }
30
+
31
+ const message = hasPhaseGateRelaxed
32
+ ? `WI-first drift detected: 0 WI directories, ${adHocPlans.length} ad-hoc plan file(s), and quickMode.relaxedGates includes phase-gate.`
33
+ : `WI-first drift detected: 0 WI directories and ${adHocPlans.length} ad-hoc plan file(s).`;
34
+
35
+ return DiagnosticFinding.create({
36
+ checkId: this.checkId,
37
+ severity: "red",
38
+ target: "docs/inception",
39
+ message,
40
+ repairMode: "mechanical",
41
+ repairHint: "phasegate migrate work-items --apply",
42
+ suggestedSkill: null,
43
+ });
44
+ }
45
+
46
+ private async hasRelaxedPhaseGate(projectRoot: string, inspector: FileInspectorPort): Promise<boolean> {
47
+ const config = await inspector.readJson<PhasegateConfigProbe>(join(projectRoot, "phasegate.config.json"));
48
+ return config?.quickMode?.relaxedGates?.includes("phase-gate") ?? false;
49
+ }
50
+ }
51
+
52
+ function toPosix(path: string): string {
53
+ return path.split(sep).join("/");
54
+ }
55
+
56
+ function isWorkItemDescription(path: string): boolean {
57
+ return /^docs\/inception\/(?:_cross|[^/]+)\/WI-\d{3}\/description\.md$/.test(path);
58
+ }
59
+
60
+ function isAdHocPlan(path: string): boolean {
61
+ if (!path.startsWith("docs/inception/")) return false;
62
+ if (/\/WI-\d{3}\//.test(path)) return false;
63
+ return path.includes("/codding_plan/") || path.endsWith("_plan.md");
64
+ }
@@ -12,6 +12,7 @@ import { HuskyCommitMsgMissingCheck } from "./application/checks/husky-commit-ms
12
12
  import { HuskyPreCommitMissingCheck } from "./application/checks/husky-pre-commit-missing-check.js";
13
13
  import { HuskyPrePushMissingCheck } from "./application/checks/husky-pre-push-missing-check.js";
14
14
  import { PackageJsonDevdepMissingCheck } from "./application/checks/package-json-devdep-missing-check.js";
15
+ import { WiWorkflowDriftCheck } from "./application/checks/wi-workflow-drift-check.js";
15
16
  import { RunInstallUseCase } from "./application/usecases/run-install.js";
16
17
  import { RunReconcileUseCase } from "./application/usecases/run-reconcile.js";
17
18
  import { RunUninstallUseCase } from "./application/usecases/run-uninstall.js";
@@ -49,6 +50,7 @@ export function createInstallationModule() {
49
50
  new PackageJsonDevdepMissingCheck(),
50
51
  new ClaudeSkillsSymlinkCheck(),
51
52
  new CodexSkillsSymlinkCheck(),
53
+ new WiWorkflowDriftCheck(),
52
54
  ];
53
55
  const runDoctorDiagnosticsUseCase = new RunDoctorDiagnosticsUseCase(checks, inspector, manifestRepository);
54
56
  const runInstallUseCase = new RunInstallUseCase(manifestRepository, hashCalculator);
@@ -12,6 +12,7 @@ export const CHECK_IDS = [
12
12
  "package-json-devdep-missing",
13
13
  "claude-skills-symlink",
14
14
  "codex-skills-symlink",
15
+ "wi-workflow-drift",
15
16
  ] as const;
16
17
 
17
18
  export type CheckId = (typeof CHECK_IDS)[number];
@@ -45,7 +45,16 @@ export class NodeFsFileInspectorAdapter implements FileInspectorPort {
45
45
  async listFiles(absolutePath: string): Promise<string[]> {
46
46
  try {
47
47
  const entries = await readdir(absolutePath, { withFileTypes: true });
48
- return entries.filter((entry) => entry.isFile()).map((entry) => join(absolutePath, entry.name));
48
+ const files: string[] = [];
49
+ for (const entry of entries) {
50
+ const entryPath = join(absolutePath, entry.name);
51
+ if (entry.isFile()) {
52
+ files.push(entryPath);
53
+ } else if (entry.isDirectory()) {
54
+ files.push(...(await this.listFiles(entryPath)));
55
+ }
56
+ }
57
+ return files;
49
58
  } catch {
50
59
  return [];
51
60
  }
@@ -8,7 +8,13 @@
8
8
  * 起動時に config-foundation で設定を解決し、他Unit に注入する(Cross-unit wiring)。
9
9
  */
10
10
 
11
- import { readFile as fsReadFile, readlink as fsReadlink, writeFile as fsWriteFile } from "node:fs/promises";
11
+ import {
12
+ mkdir as fsMkdir,
13
+ readFile as fsReadFile,
14
+ readdir as fsReaddir,
15
+ readlink as fsReadlink,
16
+ writeFile as fsWriteFile,
17
+ } from "node:fs/promises";
12
18
  import { dirname, join, resolve } from "node:path";
13
19
  import { createAdrFoundationModule } from "./adr-foundation/composition-root.js";
14
20
  import { createBiomeAstEngineModule } from "./biome-ast-engine/composition-root.js";
@@ -138,9 +144,12 @@ Usage: phasegate <command> [options]
138
144
  Setup:
139
145
  init Initialize project: deploy skills + design docs + phasegate.config.json
140
146
  (--name <project-name>, --preset <full|standard|minimal|custom>,
141
- --skills <core|all>, --agent <claude|codex|both>, --with-husky, --with-ci, --yes)
147
+ --skills <core|all>, --agent <claude|codex|both>, --workflow <standard|strict>,
148
+ --with-husky, --with-ci, --yes)
142
149
  update-skills Alias for reconcile (kept for compatibility)
143
150
  doctor Diagnose silent installation failures (--json, --strict, --report-out <path>)
151
+ scaffold-wi <unit> <type> Create docs/inception/{unit}/WI-XXX/description.md
152
+ emit-agent-rules Print AGENTS.md / CLAUDE.md WI workflow rules block
144
153
  install Install phasegate managed files (--dry-run|--apply, --force)
145
154
  uninstall Uninstall phasegate managed files (--dry-run|--apply, --force)
146
155
  reconcile Reconcile phasegate managed files (--dry-run|--apply, --force)
@@ -232,6 +241,103 @@ function hasFlag(args: readonly string[], flag: string): boolean {
232
241
  return args.includes(flag);
233
242
  }
234
243
 
244
+ type WorkflowMode = "standard" | "strict";
245
+ type ScaffoldWorkItemType = "story" | "issue" | "chore";
246
+
247
+ function parseWorkflowMode(value: string | undefined): WorkflowMode {
248
+ return value === "strict" ? "strict" : "standard";
249
+ }
250
+
251
+ function parseScaffoldWorkItemType(value: string | undefined): ScaffoldWorkItemType | null {
252
+ if (value === "story" || value === "issue" || value === "chore") return value;
253
+ return null;
254
+ }
255
+
256
+ function emitAgentRulesBlock(): string {
257
+ return [
258
+ "## PhaseGate WI Workflow (auto-generated; do not edit by hand)",
259
+ "- All plans/designs/implementations require a WI directory first.",
260
+ "- Path: `docs/inception/{unit}/WI-XXX/description.md` with required frontmatter.",
261
+ "- Use `phasegate scaffold-wi <unit> <type>` to create one.",
262
+ "- Plans written under `docs/inception/codding_plan/` are legacy; new plans go in WI dirs.",
263
+ ].join("\n");
264
+ }
265
+
266
+ async function listFilesRecursive(root: string): Promise<string[]> {
267
+ try {
268
+ const entries = await fsReaddir(root, { withFileTypes: true });
269
+ const files: string[] = [];
270
+ for (const entry of entries) {
271
+ const path = join(root, entry.name);
272
+ if (entry.isFile()) {
273
+ files.push(path);
274
+ } else if (entry.isDirectory()) {
275
+ files.push(...(await listFilesRecursive(path)));
276
+ }
277
+ }
278
+ return files;
279
+ } catch {
280
+ return [];
281
+ }
282
+ }
283
+
284
+ async function nextWorkItemId(rootDir: string): Promise<string> {
285
+ const files = await listFilesRecursive(join(rootDir, "docs", "inception"));
286
+ let max = 0;
287
+ for (const file of files) {
288
+ const match = file.match(/\/WI-(\d{3})\/description\.md$/);
289
+ if (match) max = Math.max(max, Number(match[1]));
290
+ }
291
+ return `WI-${String(max + 1).padStart(3, "0")}`;
292
+ }
293
+
294
+ async function countLegacyPlansWithoutWorkItems(rootDir: string): Promise<number> {
295
+ const files = await listFilesRecursive(join(rootDir, "docs", "inception"));
296
+ const hasWorkItem = files.some((file) => /\/WI-\d{3}\/description\.md$/.test(file));
297
+ if (hasWorkItem) return 0;
298
+ return files.filter((file) => file.includes("/codding_plan/") || file.endsWith("_plan.md")).length;
299
+ }
300
+
301
+ async function scaffoldInceptionRoots(rootDir: string, unit: string | null = null): Promise<void> {
302
+ await fsMkdir(join(rootDir, "docs", "inception", "_shared"), { recursive: true });
303
+ await fsMkdir(join(rootDir, "docs", "inception", "_cross"), { recursive: true });
304
+ if (unit && unit !== "_cross" && unit !== "_shared") {
305
+ await fsMkdir(join(rootDir, "docs", "inception", unit), { recursive: true });
306
+ await fsWriteFile(join(rootDir, "docs", "inception", unit, ".gitkeep"), "", "utf8").catch(() => undefined);
307
+ }
308
+ }
309
+
310
+ async function scaffoldWorkItem(rootDir: string, unit: string, type: ScaffoldWorkItemType): Promise<string> {
311
+ const id = await nextWorkItemId(rootDir);
312
+ await scaffoldInceptionRoots(rootDir, unit);
313
+ const targetBase = unit === "_cross" ? join(rootDir, "docs", "inception", "_cross") : join(rootDir, "docs", "inception", unit);
314
+ const targetDir = join(targetBase, id);
315
+ await fsMkdir(targetDir, { recursive: true });
316
+ const descriptionPath = join(targetDir, "description.md");
317
+ const titleScope = unit === "_cross" ? "Cross-cutting" : unit;
318
+ const content = [
319
+ "---",
320
+ `id: ${id}`,
321
+ `type: ${type}`,
322
+ "severity: normal",
323
+ "status: drafted",
324
+ "---",
325
+ "",
326
+ `# ${id}: ${titleScope} work item`,
327
+ "",
328
+ "## Context",
329
+ "",
330
+ "TBD",
331
+ "",
332
+ "## Acceptance Criteria",
333
+ "",
334
+ "- [ ] TBD",
335
+ "",
336
+ ].join("\n");
337
+ await fsWriteFile(descriptionPath, content, "utf8");
338
+ return descriptionPath;
339
+ }
340
+
235
341
  async function createFileManifestRecord(
236
342
  rootDir: string,
237
343
  relativePath: string,
@@ -349,6 +455,7 @@ Options:
349
455
  --preset <full|standard|minimal|custom> Phase dependency preset (default: "standard")
350
456
  --skills <core|all> Skill set to deploy (default: "all")
351
457
  --agent <claude|codex|both> Agent integration target (default: "claude")
458
+ --workflow <standard|strict> Workflow enforcement defaults (default: "standard")
352
459
  --with-husky Install Husky pre-commit hooks
353
460
  --with-ci Install GitHub Actions workflows
354
461
  --yes Skip confirmation prompts
@@ -777,7 +884,16 @@ async function main(): Promise<void> {
777
884
  console.log("Warning: phasegate init is deprecated and will be removed in v1.0.");
778
885
  console.log("Use phasegate install for idempotent setup with structured merge.");
779
886
  console.log("Existing legacy init behavior is preserved. Run phasegate doctor to verify installation state.");
780
- const KNOWN_INIT_FLAGS = ["--name", "--preset", "--skills", "--agent", "--with-husky", "--with-ci", "--yes"];
887
+ const KNOWN_INIT_FLAGS = [
888
+ "--name",
889
+ "--preset",
890
+ "--skills",
891
+ "--agent",
892
+ "--workflow",
893
+ "--with-husky",
894
+ "--with-ci",
895
+ "--yes",
896
+ ];
781
897
  const flagError = validateKnownFlags(args, KNOWN_INIT_FLAGS);
782
898
  if (flagError) {
783
899
  console.error(flagError);
@@ -808,6 +924,12 @@ async function main(): Promise<void> {
808
924
  process.exit(2);
809
925
  }
810
926
  const agent = agentRaw;
927
+ const workflowRaw = parseFlag(args, "--workflow");
928
+ if (workflowRaw !== undefined && workflowRaw !== "standard" && workflowRaw !== "strict") {
929
+ console.error(`Invalid --workflow value: "${workflowRaw}". Use "standard" or "strict".`);
930
+ process.exit(2);
931
+ }
932
+ const workflow = parseWorkflowMode(workflowRaw);
811
933
  const deployClaude = agent === "claude" || agent === "both";
812
934
  const deployCodex = agent === "codex" || agent === "both";
813
935
  const result = await deploySkills(harnessRoot, rootDir, skillSet);
@@ -817,7 +939,13 @@ async function main(): Promise<void> {
817
939
  codex: deployCodex,
818
940
  });
819
941
  const withCi = hasFlag(args, "--with-ci");
820
- const configResult = await initHarnessConfig(rootDir, projectName, phasePreset, { ciEnabled: withCi });
942
+ const configResult = await initHarnessConfig(rootDir, projectName, phasePreset, {
943
+ ciEnabled: withCi,
944
+ workflow,
945
+ });
946
+ if (workflow === "strict") {
947
+ await scaffoldInceptionRoots(rootDir);
948
+ }
821
949
  const hooksResult = deployClaude
822
950
  ? await deployHookScripts(harnessRoot, rootDir)
823
951
  : {
@@ -891,6 +1019,9 @@ async function main(): Promise<void> {
891
1019
  }
892
1020
  if (configResult.created) {
893
1021
  console.log(`✓ phasegate.config.json created`);
1022
+ if (workflow === "strict") {
1023
+ console.log(`✓ strict workflow configured (quickMode.relaxedGates: [], allowedCategories: ["chore"])`);
1024
+ }
894
1025
  } else {
895
1026
  console.log(` phasegate.config.json already exists, skipped`);
896
1027
  }
@@ -966,6 +1097,13 @@ async function main(): Promise<void> {
966
1097
  }
967
1098
  }
968
1099
  console.log(`✓ Harness v${result.version} initialized (agent: ${agent})`);
1100
+ const legacyPlanCount = await countLegacyPlansWithoutWorkItems(rootDir);
1101
+ if (legacyPlanCount > 0) {
1102
+ console.log("");
1103
+ console.log(`Detected ${legacyPlanCount} legacy plan file(s) with no WI directories.`);
1104
+ console.log("Run migration? [Y/n]");
1105
+ console.log(" phasegate migrate work-items --apply");
1106
+ }
969
1107
  console.log("");
970
1108
  console.log("Next steps:");
971
1109
  if (skillSet === "core") {
@@ -1035,6 +1173,25 @@ async function main(): Promise<void> {
1035
1173
  break;
1036
1174
  }
1037
1175
 
1176
+ case "emit-agent-rules": {
1177
+ console.log(emitAgentRulesBlock());
1178
+ process.exit(0);
1179
+ break;
1180
+ }
1181
+
1182
+ case "scaffold-wi": {
1183
+ const unit = args[1];
1184
+ const type = parseScaffoldWorkItemType(args[2]);
1185
+ if (!unit || !type) {
1186
+ console.error("Usage: phasegate scaffold-wi <unit|_cross> <story|issue|chore>");
1187
+ process.exit(2);
1188
+ }
1189
+ const descriptionPath = await scaffoldWorkItem(rootDir, unit, type);
1190
+ console.log(`Created ${descriptionPath}`);
1191
+ process.exit(0);
1192
+ break;
1193
+ }
1194
+
1038
1195
  case "install": {
1039
1196
  const KNOWN_INSTALL_FLAGS = ["--dry-run", "--apply", "--force", "--json"];
1040
1197
  const flagError = validateKnownFlags(args, KNOWN_INSTALL_FLAGS);
@@ -422,6 +422,7 @@ export async function deployHookScripts(harnessRoot: string, projectRoot: string
422
422
 
423
423
  export interface InitHarnessConfigOptions {
424
424
  ciEnabled?: boolean;
425
+ workflow?: "standard" | "strict";
425
426
  }
426
427
 
427
428
  export async function initHarnessConfig(
@@ -438,6 +439,7 @@ export async function initHarnessConfig(
438
439
  // ファイルが存在しない場合はテンプレートを作成
439
440
  }
440
441
 
442
+ const strictWorkflow = options.workflow === "strict";
441
443
  const template = {
442
444
  project: {
443
445
  name: projectName,
@@ -447,7 +449,12 @@ export async function initHarnessConfig(
447
449
  preset: "clean",
448
450
  },
449
451
  layers: {},
450
- quickMode: {},
452
+ quickMode: strictWorkflow
453
+ ? {
454
+ allowedCategories: ["chore"],
455
+ relaxedGates: [],
456
+ }
457
+ : {},
451
458
  phaseDependencies: {
452
459
  preset: phasePreset ?? "default",
453
460
  override: false,
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: implementation-planner
3
- description: "Unit仕様とドメインモデル設計を元に実装計画を立てる。ストーリーIDや機能名から関連Unitを特定し、API設計・レイヤー別実装方針を整理してmdファイルで出力する。使用タイミング: 実装計画を立てて、US-XXXの実装方針を決めて、この機能の設計を整理して、など実装前の計画策定時。"
3
+ description: "Unit仕様とドメインモデル設計を元に実装計画を立てる。WI IDや機能名から関連Unitを特定し、API設計・レイヤー別実装方針を整理してmdファイルで出力する。使用タイミング: 実装計画を立てて、WI-XXXの実装方針を決めて、この機能の設計を整理して、など実装前の計画策定時。"
4
4
  model: sonnet
5
5
  review: opus
6
6
  ---
@@ -35,6 +35,11 @@ UnitドキュメントとConstructionのドメインモデル設計を元に、*
35
35
 
36
36
  ## ワークフロー
37
37
 
38
+ ## Pre-flight check (BLOCKING)
39
+
40
+ Before generating any plan, verify `docs/inception/{unit}/WI-XXX/description.md` exists.
41
+ If not, halt and ask the user to create the WI first, or offer to run `phasegate scaffold-wi <unit> <story|issue|chore>`.
42
+
38
43
  ```
39
44
  入力解析 → Unit特定 → ドメインモデル確認 → 既存実装確認 → 計画作成 → 出力
40
45
  ```
@@ -42,7 +47,7 @@ UnitドキュメントとConstructionのドメインモデル設計を元に、*
42
47
  ### Step 1: 入力解析
43
48
 
44
49
  ユーザー入力から抽出:
45
- - ストーリーID(US-XXX形式)
50
+ - WI ID(WI-XXX形式)
46
51
  - 機能名・タスク説明
47
52
  - 優先度・制約条件
48
53
 
@@ -85,6 +90,7 @@ UnitドキュメントとConstructionのドメインモデル設計を元に、*
85
90
  計画をmdファイルとして出力。パスの推奨:
86
91
  ```
87
92
  docs/inception/{task_id}_plan.md
93
+ docs/inception/{unit}/WI-XXX/tdd_implementation_plan.md
88
94
  ```
89
95
 
90
96
  **[Question][Answer]セクション必須**: 不明点や確認事項をまとめ、ユーザーからのフィードバックを受け取れるようにする。
@@ -18,6 +18,11 @@ Unit単位でアーキテクチャの各層(DB → ドメイン → ユース
18
18
 
19
19
  ## 前提条件チェック
20
20
 
21
+ ## Pre-flight check (BLOCKING)
22
+
23
+ Before generating any plan, verify `docs/inception/{unit}/WI-XXX/description.md` exists.
24
+ If not, halt and ask the user to create the WI first, or offer to run `phasegate scaffold-wi <unit> <story|issue|chore>`.
25
+
21
26
  ### 必須インプット(存在しなければ`[Question]`で提供を要求)
22
27
 
23
28
  - **横断モード:**
@@ -95,7 +100,7 @@ Unit単位でアーキテクチャの各層(DB → ドメイン → ユース
95
100
  設計方針・スコープ・不明点を整理し、人間の承認を得る。
96
101
 
97
102
  ### 出力ファイル
98
- `docs/inception/{unit}/logical_design_plan.md`(横断)または `docs/inception/{unit}/{story_id}/logical_design_plan.md`(ストーリー固有)
103
+ `docs/inception/{unit}/WI-XXX/logical_design_plan.md`
99
104
 
100
105
  ### 計画ファイルの構成
101
106
 
@@ -173,7 +178,7 @@ Unit単位でアーキテクチャの各層(DB → ドメイン → ユース
173
178
  | 種別 | 配置先 |
174
179
  |------|--------|
175
180
  | 横断成果物 | `docs/product/construction/{unit}/logical_design.md` |
176
- | ストーリー固有成果物 | `docs/inception/{unit}/{story_id}/logical_design.md` |
181
+ | ストーリー固有成果物 | `docs/inception/{unit}/WI-XXX/logical_design.md` |
177
182
 
178
183
  > **注意**: ストーリー固有の設計は `docs/inception/` に配置する(`docs/folder_management_rules.md` のルール準拠)。`docs/product/construction/` にはUnit全体の共有設計のみを配置する。
179
184
 
@@ -194,22 +199,22 @@ traceability:
194
199
  ---
195
200
  ```
196
201
 
197
- `initial_creation: true` は「新規作成であり、後述の `@story-id` 注釈が必須」であることを示す。
202
+ `initial_creation: true` は「新規作成であり、後述の `@work-item-id` 注釈が必須」であることを示す。
198
203
 
199
- ### 2. `@story-id` インライン注釈
204
+ ### 2. `@work-item-id` インライン注釈
200
205
 
201
- ユーザーストーリーに紐づく設計要素の直前に `@story-id HXX-XX` を独立行で記述する。
206
+ WI に紐づく設計要素の直前に `@work-item-id WI-XXX` を独立行で記述する。
202
207
 
203
208
  ```markdown
204
- @story-id H03-02
209
+ @work-item-id WI-001
205
210
  ### ユースケース: 注文を確定する
206
211
  ```
207
212
 
208
213
  形式ルール:
209
214
  - **独立行** — 他のテキストと混在させない
210
215
  - **直後に設計要素** — 空行を挟まない
211
- - **StoryCatalog 存在** — `HXX-XX` は `docs/product/user_stories.md` に存在する ID
212
- - **複数ストーリー時** — 注釈行を連続で並べ、最後の直後に設計要素を置く
216
+ - **WorkItem 存在** — `WI-XXX` は `docs/inception/{unit}/WI-XXX/description.md` に存在する ID
217
+ - **複数WI時** — 注釈行を連続で並べ、最後の直後に設計要素を置く
213
218
 
214
219
  ---
215
220
 
@@ -11,6 +11,11 @@ review: opus
11
11
 
12
12
  ## 前提条件チェック
13
13
 
14
+ ## Pre-flight check (BLOCKING)
15
+
16
+ Before generating any plan, verify `docs/inception/{unit}/WI-XXX/description.md` exists.
17
+ If not, halt and ask the user to create the WI first, or offer to run `phasegate scaffold-wi <unit> <story|issue|chore>`.
18
+
14
19
  ### 必須インプット(存在しなければ`[Question]`で提供を要求)
15
20
  - **要求文書** — 何を作るかを記述した文書。形式は問わない(議事録、要件メモ、口頭要約のテキスト等)
16
21