phasegate 0.160.5 → 0.160.7

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.
Files changed (24) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/docs/templates/ci/agent-context-refresh.yml +12 -8
  3. package/docs/templates/ci/consistency-check.yml +11 -7
  4. package/package.json +1 -1
  5. package/scripts/delegate-sonnet.sh +21 -7
  6. package/scripts/harness/ci-governance/application/usecases/refresh-claude-md-usecase.ts +6 -6
  7. package/scripts/harness/ci-governance/domain/services/claude-md-composer.ts +1 -1
  8. package/scripts/harness/ci-governance/infrastructure/adapters/yaml-template-renderer-adapter.ts +1 -0
  9. package/scripts/harness/ci-governance/presentation/formatters/ci-template-formatter.ts +3 -1
  10. package/scripts/harness/config-foundation/domain/harness-config.ts +1 -1
  11. package/scripts/harness/config-foundation/domain/value-objects/planning-mode-config.ts +2 -2
  12. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +4 -2
  13. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +4 -2
  14. package/scripts/harness/installation/application/checks/wi-workflow-drift-check.ts +2 -0
  15. package/scripts/harness/installation/application/usecases/run-install.ts +2 -1
  16. package/scripts/harness/installation/application/usecases/run-reconcile.ts +9 -8
  17. package/scripts/harness/installation/application/usecases/run-uninstall.ts +13 -2
  18. package/scripts/harness/installation/presentation/cli/uninstall-handler.ts +4 -2
  19. package/scripts/harness/main.ts +67 -6
  20. package/scripts/harness/phase-dependency-model/application/services/phase-info-resolver.ts +1 -1
  21. package/scripts/harness/phase-dependency-model/domain/models/phase-structure.ts +1 -1
  22. package/scripts/harness/phase-dependency-model/domain/values/planning-mode.ts +2 -2
  23. package/scripts/harness/skill-quality/application/usecases/apply-cascade-update-usecase.ts +2 -1
  24. package/scripts/harness/skill-quality/presentation/handlers/apply-cascade-update-handler.ts +10 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.160.6] - 2026-05-14
11
+
12
+ ### Fixed
13
+
14
+ - **WI-190..WI-196 — post-0.160.5 dogfood regressions** — aligns agent context refresh with reconcile rendering, adds retrofit planning-mode config plans, makes cascade update dry-runs non-mutating and explicit, fixes doctor `_shared` drift counts, removes pnpm-only scheduled CI templates, exposes `migrate work-items` help, and accepts positional `delegate-sonnet` prompts.
15
+
10
16
  ## [0.160.5] - 2026-05-14
11
17
 
12
18
  ### Fixed
@@ -1,4 +1,5 @@
1
1
  # Phasegate — Agent context refresh workflow
2
+ # @work-item-id WI-194
2
3
  #
3
4
  # 使い方:
4
5
  # このファイルを .github/workflows/agent-context-refresh.yml にコピーして使用する。
@@ -32,18 +33,21 @@ jobs:
32
33
  uses: actions/setup-node@v4
33
34
  with:
34
35
  node-version: '20'
35
- cache: 'pnpm'
36
-
37
- - name: Install pnpm
38
- uses: pnpm/action-setup@v4
39
- with:
40
- version: 9
41
36
 
42
37
  - name: Install dependencies
43
- run: pnpm install --frozen-lockfile
38
+ run: |
39
+ if [ -f pnpm-lock.yaml ]; then
40
+ pnpm install --frozen-lockfile
41
+ elif [ -f yarn.lock ]; then
42
+ yarn install --immutable || yarn install --frozen-lockfile
43
+ elif [ -f package-lock.json ] || [ -f npm-shrinkwrap.json ]; then
44
+ npm ci
45
+ else
46
+ npm install
47
+ fi
44
48
 
45
49
  - name: Refresh agent context
46
- run: pnpm run harness ci:auto-refresh-agent-context --apply
50
+ run: npx phasegate ci:auto-refresh-agent-context --apply
47
51
 
48
52
  - name: Create pull request
49
53
  uses: peter-evans/create-pull-request@v6
@@ -1,4 +1,5 @@
1
1
  # Phasegate — 週次整合性チェックワークフロー
2
+ # @work-item-id WI-194
2
3
  #
3
4
  # 使い方:
4
5
  # このファイルを .github/workflows/consistency-check.yml にコピーして使用する。
@@ -31,15 +32,18 @@ jobs:
31
32
  uses: actions/setup-node@v4
32
33
  with:
33
34
  node-version: '20'
34
- cache: 'pnpm'
35
-
36
- - name: Install pnpm
37
- uses: pnpm/action-setup@v4
38
- with:
39
- version: 9
40
35
 
41
36
  - name: Install dependencies
42
- run: pnpm install --frozen-lockfile
37
+ run: |
38
+ if [ -f pnpm-lock.yaml ]; then
39
+ pnpm install --frozen-lockfile
40
+ elif [ -f yarn.lock ]; then
41
+ yarn install --immutable || yarn install --frozen-lockfile
42
+ elif [ -f package-lock.json ] || [ -f npm-shrinkwrap.json ]; then
43
+ npm ci
44
+ else
45
+ npm install
46
+ fi
43
47
 
44
48
  # L4-001: Drift Detection(設計⇔コード双方向乖離)
45
49
  - name: L4 Drift Detection
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phasegate",
3
- "version": "0.160.5",
3
+ "version": "0.160.7",
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",
@@ -4,11 +4,12 @@
4
4
  # Usage:
5
5
  # bash scripts/delegate-sonnet.sh --prompt "プロンプト" --output "出力パス"
6
6
  # bash scripts/delegate-sonnet.sh --prompt-file /tmp/prompt.md --output "出力パス"
7
+ # bash scripts/delegate-sonnet.sh "プロンプト" --output "出力パス"
7
8
  #
8
9
  # Options:
9
10
  # --prompt 委任プロンプト(直接指定、2000文字以下推奨)
10
11
  # --prompt-file 委任プロンプトファイル(長文の場合)
11
- # --output 出力ファイルパス
12
+ # --output 出力ファイルパス(省略時: .phasegate/delegate-sonnet-output.md)
12
13
  # --max-turns 最大ターン数(デフォルト: 30)
13
14
  # --dry-run プロンプトを表示するだけで実行しない
14
15
 
@@ -20,6 +21,7 @@ PROMPT_FILE=""
20
21
  OUTPUT_PATH=""
21
22
  MAX_TURNS=30
22
23
  DRY_RUN=false
24
+ POSITIONAL_ARGS=()
23
25
 
24
26
  while [[ $# -gt 0 ]]; do
25
27
  case $1 in
@@ -43,23 +45,35 @@ while [[ $# -gt 0 ]]; do
43
45
  DRY_RUN=true
44
46
  shift
45
47
  ;;
48
+ --)
49
+ shift
50
+ if [[ $# -gt 0 ]]; then
51
+ POSITIONAL_ARGS+=("$@")
52
+ fi
53
+ break
54
+ ;;
46
55
  *)
47
- echo "Unknown option: $1" >&2
48
- exit 1
56
+ if [[ "$1" == --* ]]; then
57
+ echo "Unknown option: $1" >&2
58
+ exit 1
59
+ fi
60
+ POSITIONAL_ARGS+=("$1")
61
+ shift
49
62
  ;;
50
63
  esac
51
64
  done
52
65
 
66
+ if [[ -z "$PROMPT" && ${#POSITIONAL_ARGS[@]} -gt 0 ]]; then
67
+ PROMPT="${POSITIONAL_ARGS[*]}"
68
+ fi
69
+
53
70
  # --- バリデーション ---
54
71
  if [[ -z "$PROMPT" && -z "$PROMPT_FILE" ]]; then
55
72
  echo "Error: --prompt or --prompt-file is required" >&2
56
73
  exit 1
57
74
  fi
58
75
 
59
- if [[ -z "$OUTPUT_PATH" ]]; then
60
- echo "Error: --output is required" >&2
61
- exit 1
62
- fi
76
+ OUTPUT_PATH="${OUTPUT_PATH:-.phasegate/delegate-sonnet-output.md}"
63
77
 
64
78
  if [[ -n "$PROMPT_FILE" && ! -f "$PROMPT_FILE" ]]; then
65
79
  echo "Error: Prompt file not found: $PROMPT_FILE" >&2
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @layer application
3
3
  * @unit ci-governance
4
+ * @work-item-id WI-190
4
5
  */
5
6
 
6
7
  import type { AgentContextDocumentPort } from '../../domain/ports/agent-context-document-port.js';
@@ -12,12 +13,11 @@ const CLAUDE_MD_PATH = 'CLAUDE.md';
12
13
  const CLAUDE_MD_TEMPLATE_PATH = 'docs/templates/agent-context/CLAUDE.md.template.md';
13
14
 
14
15
  const PHASEGATE_COMMANDS = [
15
- 'phasegate init --with-ci',
16
- 'phasegate ci:auto-refresh-agent-context --dry-run',
17
- 'phasegate ci:auto-refresh-agent-context --apply',
18
- 'phasegate refresh-claude-md --apply',
19
- 'phasegate p2:check-agent-context',
16
+ 'phasegate doctor',
20
17
  'phasegate phasegate:check-ready',
18
+ 'phasegate validate --layer L2 --format human',
19
+ 'phasegate setup:agent --dry-run',
20
+ 'phasegate config:plan --intent l4-strict --dry-run',
21
21
  ];
22
22
 
23
23
  const PHASE_PRESETS = ['minimal', 'standard', 'full', 'custom'];
@@ -37,7 +37,7 @@ export class RefreshClaudeMdUseCase {
37
37
  ]);
38
38
  const nextContent = this.composer.compose(template, existing, {
39
39
  commands: PHASEGATE_COMMANDS,
40
- skills,
40
+ skills: ['all bundled skills'],
41
41
  presets: PHASE_PRESETS,
42
42
  });
43
43
  const changed = existing !== nextContent;
@@ -11,7 +11,7 @@ export interface ClaudeMdTemplateValues {
11
11
 
12
12
  const USER_SECTION_START = '<!-- phasegate:user-section:start -->';
13
13
  const USER_SECTION_END = '<!-- phasegate:user-section:end -->';
14
- const DEFAULT_USER_SECTION = 'プロジェクト固有の指示をここに記載してください。';
14
+ const DEFAULT_USER_SECTION = 'Project-specific agent instructions go here.';
15
15
 
16
16
  export class ClaudeMdComposer {
17
17
  compose(template: string, existing: string | null, values: ClaudeMdTemplateValues): string {
@@ -2,6 +2,7 @@
2
2
  * @layer infrastructure
3
3
  * @unit ci-governance
4
4
  * @work-item-id WI-182 / WI-183
5
+ * @work-item-id WI-194
5
6
  *
6
7
  * TemplateRendererPort実装(YAML書き出し)
7
8
  */
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @layer presentation
3
3
  * @unit ci-governance
4
+ * @work-item-id WI-200
4
5
  */
5
6
 
6
7
  import type { GenerateCiTemplateOutput } from '../../application/dto/generate-ci-template-output.js';
@@ -15,12 +16,13 @@ export class CiTemplateFormatter {
15
16
  lines.push(` [${err.code}] ${err.message}`);
16
17
  }
17
18
  } else {
18
- lines.push('✓ CI Template Generated');
19
+ lines.push('✓ CI Template Plan Ready');
19
20
  lines.push(` Template Type: ${output.templateType}`);
20
21
  lines.push(` Preset: ${output.presetRef}`);
21
22
  lines.push(` Trigger: ${output.triggerCondition}`);
22
23
  lines.push(` Validators: ${output.targetValidatorIds.join(', ')}`);
23
24
  lines.push(` Fail on Warning: ${output.failOnWarning}`);
25
+ lines.push(' Output: no file written; use --render to print template YAML');
24
26
  }
25
27
 
26
28
  return lines.join('\n');
@@ -29,7 +29,7 @@ import type {
29
29
 
30
30
  export type LayerId = 'L1' | 'L2' | 'L3' | 'L4';
31
31
  export type PresetId = 'minimal' | 'standard' | 'strict';
32
- export type PlanningModeValue = 'interactive' | 'embedded-qa';
32
+ export type PlanningModeValue = 'interactive' | 'embedded-qa' | 'manual';
33
33
  export type { PhaseDependenciesPresetId };
34
34
  type DeepPartial<T> = {
35
35
  [K in keyof T]?: T[K] extends Array<infer TItem>
@@ -3,11 +3,11 @@
3
3
  * @unit config-foundation
4
4
  *
5
5
  * Planning Mode設定を表す値オブジェクト
6
- * defaultMode と perPhase の値は列挙値 "interactive" | "embedded-qa" のみ許容
6
+ * defaultMode と perPhase の値は列挙値 "interactive" | "embedded-qa" | "manual" のみ許容
7
7
  */
8
8
  import { ConfigValidationError } from '../errors/config-validation-error.js';
9
9
 
10
- const VALID_MODES = ['interactive', 'embedded-qa'] as const;
10
+ const VALID_MODES = ['interactive', 'embedded-qa', 'manual'] as const;
11
11
  type PlanningMode = (typeof VALID_MODES)[number];
12
12
 
13
13
  export interface PlanningModeConfigProps {
@@ -384,7 +384,8 @@
384
384
  "type": "string",
385
385
  "enum": [
386
386
  "interactive",
387
- "embedded-qa"
387
+ "embedded-qa",
388
+ "manual"
388
389
  ]
389
390
  },
390
391
  "perPhase": {
@@ -393,7 +394,8 @@
393
394
  "type": "string",
394
395
  "enum": [
395
396
  "interactive",
396
- "embedded-qa"
397
+ "embedded-qa",
398
+ "manual"
397
399
  ]
398
400
  }
399
401
  }
@@ -394,7 +394,8 @@
394
394
  "type": "string",
395
395
  "enum": [
396
396
  "interactive",
397
- "embedded-qa"
397
+ "embedded-qa",
398
+ "manual"
398
399
  ]
399
400
  },
400
401
  "perPhase": {
@@ -403,7 +404,8 @@
403
404
  "type": "string",
404
405
  "enum": [
405
406
  "interactive",
406
- "embedded-qa"
407
+ "embedded-qa",
408
+ "manual"
407
409
  ]
408
410
  }
409
411
  }
@@ -2,6 +2,7 @@
2
2
  // @layer application
3
3
  // @work-item-id WI-143
4
4
  // @work-item-id WI-187
5
+ // @work-item-id WI-193
5
6
 
6
7
  import { join, relative, sep } from "node:path";
7
8
  import { DiagnosticFinding } from "../../domain/diagnostic-finding.js";
@@ -61,5 +62,6 @@ function isWorkItemDescription(path: string): boolean {
61
62
  function isAdHocPlan(path: string): boolean {
62
63
  if (!path.startsWith("docs/inception/")) return false;
63
64
  if (/\/WI-\d{3}\//.test(path)) return false;
65
+ if (path.startsWith("docs/inception/_shared/") && path.endsWith(".md")) return true;
64
66
  return path.includes("/codding_plan/") || path.endsWith("_plan.md");
65
67
  }
@@ -2,6 +2,7 @@
2
2
  // @layer application
3
3
  // @work-item-id WI-146
4
4
  // @work-item-id WI-174
5
+ // @work-item-id WI-198
5
6
  // @work-item-id WI-175
6
7
  // @work-item-id WI-177
7
8
  // @work-item-id WI-182
@@ -190,7 +191,7 @@ function renderAgentContextTemplate(
190
191
  .replaceAll("{{PHASEGATE_HUSKY_STATE}}", options.includeHusky ? "managed" : "not managed by this setup run")
191
192
  .replaceAll("{{PHASEGATE_CI_STATE}}", options.includeCi ? "managed" : "not managed by this setup run")
192
193
  .replaceAll("{{PHASEGATE_COMMANDS}}", commands)
193
- .replaceAll("{{PHASEGATE_SKILLS}}", options.skillSet === "core" ? "- core skills" : "- all bundled skills")
194
+ .replaceAll("{{PHASEGATE_SKILLS}}", options.skillSet === "core" ? "- `core skills`" : "- `all bundled skills`")
194
195
  .replaceAll("{{PHASEGATE_PRESETS}}", "- `minimal`\n- `standard`\n- `full`\n- `custom`")
195
196
  .replaceAll("{{PHASEGATE_USER_SECTION}}", "Project-specific agent instructions go here.");
196
197
  }
@@ -2,6 +2,7 @@
2
2
  // @layer application
3
3
  // @work-item-id WI-148
4
4
  // @work-item-id WI-174
5
+ // @work-item-id WI-198
5
6
 
6
7
  import { access, chmod, copyFile, lstat, mkdir, readFile, readlink, symlink, writeFile } from "node:fs/promises";
7
8
  import { dirname, join, relative, resolve } from "node:path";
@@ -156,12 +157,12 @@ function reconcileManagedMarkdown(existing: string | null, incoming: string): st
156
157
 
157
158
  function renderAgentContextTemplate(template: string): string {
158
159
  const commands = [
159
- "- `phasegate doctor`",
160
- "- `phasegate phasegate:check-ready`",
161
- "- `phasegate validate --layer L2 --format human`",
162
- "- `phasegate setup:agent --dry-run`",
163
- "- `phasegate config:plan --intent l4-strict --dry-run`",
164
- ].join("\n");
160
+ "phasegate doctor",
161
+ "phasegate phasegate:check-ready",
162
+ "phasegate validate --layer L2 --format human",
163
+ "phasegate setup:agent --dry-run",
164
+ "phasegate config:plan --intent l4-strict --dry-run",
165
+ ].map((command) => `- \`${command}\``).join("\n");
165
166
  return template
166
167
  .replaceAll("{{PHASEGATE_AGENT}}", "both")
167
168
  .replaceAll("{{PHASEGATE_SKILLS_MODE}}", "all")
@@ -169,7 +170,7 @@ function renderAgentContextTemplate(template: string): string {
169
170
  .replaceAll("{{PHASEGATE_HUSKY_STATE}}", "managed")
170
171
  .replaceAll("{{PHASEGATE_CI_STATE}}", "managed")
171
172
  .replaceAll("{{PHASEGATE_COMMANDS}}", commands)
172
- .replaceAll("{{PHASEGATE_SKILLS}}", "- all bundled skills")
173
+ .replaceAll("{{PHASEGATE_SKILLS}}", "- `all bundled skills`")
173
174
  .replaceAll("{{PHASEGATE_PRESETS}}", "- `minimal`\n- `standard`\n- `full`\n- `custom`")
174
175
  .replaceAll("{{PHASEGATE_USER_SECTION}}", "Project-specific agent instructions go here.");
175
176
  }
@@ -299,7 +300,7 @@ export class RunReconcileUseCase {
299
300
  const matchesManifest = currentHash.equals(entry.hash);
300
301
  const rawTemplate = target.templatePath ? await readFile(join(input.harnessRoot, target.templatePath), "utf8") : "";
301
302
  const template = target.strategy === "markdown-managed" ? renderAgentContextTemplate(rawTemplate) : rawTemplate;
302
- const next = entry.mode === "created" && target.strategy !== "package-json"
303
+ const next = entry.mode === "created" && target.strategy !== "package-json" && target.strategy !== "markdown-managed"
303
304
  ? template
304
305
  : this.reconcileContent(target, before, template, input.phasegateVersion);
305
306
  const changed = before !== next;
@@ -2,6 +2,7 @@
2
2
  // @layer application
3
3
  // @work-item-id WI-147
4
4
  // @work-item-id WI-174
5
+ // @work-item-id WI-199
5
6
 
6
7
  import { access, copyFile, lstat, mkdir, readFile, readlink, rm, rmdir, writeFile } from "node:fs/promises";
7
8
  import { dirname, join, relative, resolve } from "node:path";
@@ -19,6 +20,7 @@ export interface UninstallPlanItem {
19
20
  readonly repairMode: RepairMode;
20
21
  readonly strategy: StrategyType;
21
22
  readonly changed: boolean;
23
+ readonly protected: boolean;
22
24
  readonly summary: string;
23
25
  readonly diff: string;
24
26
  readonly skillHint: string | null;
@@ -46,6 +48,7 @@ const SHELL_END = "# === phasegate managed (END) ===";
46
48
  const MARKDOWN_BEGIN = "<!-- phasegate:managed-section:start -->";
47
49
  const MARKDOWN_END = "<!-- phasegate:managed-section:end -->";
48
50
  const PHASEGATE_SCRIPT_PREFIX = "phasegate:";
51
+ const PROTECTED_UNINSTALL_PATHS = new Set(["package.json", "package-lock.json"]);
49
52
 
50
53
  function isRecord(value: unknown): value is Record<string, unknown> {
51
54
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -175,7 +178,7 @@ export class RunUninstallUseCase {
175
178
  const outcome = await this.planEntry(input, entry);
176
179
  outcomes.push({ entry, ...outcome });
177
180
  plan.push(outcome.item);
178
- if (input.apply && outcome.item.changed && (outcome.item.repairMode === "ai-assisted" || outcome.item.repairMode === "manual") && !input.force) {
181
+ if (input.apply && outcome.item.changed && this.requiresForce(outcome.item) && !input.force) {
179
182
  refused.push({ ...outcome.item, action: "refuse" });
180
183
  }
181
184
  }
@@ -372,6 +375,14 @@ export class RunUninstallUseCase {
372
375
  diff: string,
373
376
  skillHint: string | null,
374
377
  ): UninstallPlanItem {
375
- return { path, action, repairMode, strategy, changed, summary, diff, skillHint };
378
+ return { path, action, repairMode, strategy, changed, protected: this.isProtectedPath(path), summary, diff, skillHint };
379
+ }
380
+
381
+ private requiresForce(item: UninstallPlanItem): boolean {
382
+ return item.protected || item.repairMode === "ai-assisted" || item.repairMode === "manual";
383
+ }
384
+
385
+ private isProtectedPath(path: string): boolean {
386
+ return PROTECTED_UNINSTALL_PATHS.has(path);
376
387
  }
377
388
  }
@@ -1,6 +1,7 @@
1
1
  // @unit installation
2
2
  // @layer presentation
3
3
  // @work-item-id WI-147
4
+ // @work-item-id WI-199
4
5
 
5
6
  import type { RunUninstallUseCase } from "../../application/usecases/run-uninstall.js";
6
7
 
@@ -33,14 +34,15 @@ export class UninstallHandler {
33
34
  input.apply ? "phasegate uninstall apply" : "phasegate uninstall dry-run",
34
35
  ...result.plan.map((item) => {
35
36
  const hint = item.skillHint ? `; hint: ${item.skillHint}` : "";
36
- return `- ${item.path}: ${item.action} (${item.repairMode}, ${item.strategy}); diff: ${item.diff}${hint}`;
37
+ const protectedMarker = item.protected ? "; protected: true" : "";
38
+ return `- ${item.path}: ${item.action} (${item.repairMode}, ${item.strategy}${protectedMarker}); diff: ${item.diff}${hint}`;
37
39
  }),
38
40
  ];
39
41
  if (result.backupDir !== null) lines.push(`backups: ${result.backupDir}`);
40
42
  if (result.archivedManifestPath !== null) lines.push(`archived manifest: ${result.archivedManifestPath}`);
41
43
  if (result.refused.length > 0) {
42
44
  lines.push("");
43
- lines.push("Refused ai-assisted/manual targets. Re-run with --force after reviewing the hint.");
45
+ lines.push("Refused protected, ai-assisted, or manual targets. Re-run with --force after reviewing the plan.");
44
46
  }
45
47
  return {
46
48
  stdout: lines.join("\n"),
@@ -8,6 +8,11 @@
8
8
  * @work-item-id WI-176
9
9
  * @work-item-id WI-184
10
10
  * @work-item-id WI-189
11
+ * @work-item-id WI-191
12
+ * @work-item-id WI-195
13
+ * @work-item-id WI-196
14
+ * @work-item-id WI-197
15
+ * @work-item-id WI-200
11
16
  *
12
17
  * Phasegate CLI エントリポイント。
13
18
  * 各Unitの Composition Root からハンドラーを取得し、コマンドに応じてディスパッチする。
@@ -171,6 +176,7 @@ Commands:
171
176
  disable-feature <name> Disable a harness feature
172
177
  list-features List available features
173
178
  migrate Migrate phasegate.config.json (--schema v3, --config <path>)
179
+ migrate work-items Migrate legacy inception work item directories (--dry-run|--apply)
174
180
  work-items:status Report or apply derived WI frontmatter status (--dry-run|--apply, --id, --fail-on-stale, --json)
175
181
 
176
182
  render-errors Render harness errors (--format human|agent|ci)
@@ -211,7 +217,7 @@ Gate semantics:
211
217
  refresh-claude-md Refresh CLAUDE.md standard sections (--dry-run, --apply, --json)
212
218
  p2:check-agent-context Check AGENTS.md / CLAUDE.md freshness (--threshold-days <n>, --json)
213
219
  setup:agent Plan agent-driven setup (--intent <minimal|recommended|strict|ci-only|agent-hooks|retrofit>, --agent <claude|codex|both>, --dry-run|--apply, --json)
214
- config:plan Plan safe config changes (--intent <l4-strict|codex-hooks|ci-fail-on-warning|baseline-reset|quick-mode-strict>, --dry-run, --json)
220
+ config:plan Plan safe config changes (--intent <l4-strict|codex-hooks|ci-fail-on-warning|baseline-reset|quick-mode-strict|retrofit-bootstrap|planning-mode-relax>, --dry-run, --json)
215
221
  ci:check-repetition Check error repetition (--code <errorCode>, --reset, --json)
216
222
  baseline Create retrofit baseline snapshot (--dry-run, --force, --paths <glob,glob,...>, --json)
217
223
  scaffold-design Scaffold a design doc (--unit <id>, --phase <logical|domain|uiux|unit-test|it-test>, --dry-run|--apply, --force, --json)
@@ -528,7 +534,7 @@ Options:
528
534
  Produce an agent-readable configuration change plan.
529
535
 
530
536
  Intents:
531
- l4-strict, codex-hooks, ci-fail-on-warning, baseline-reset, quick-mode-strict
537
+ l4-strict, codex-hooks, ci-fail-on-warning, baseline-reset, quick-mode-strict, retrofit-bootstrap, planning-mode-relax
532
538
 
533
539
  Options:
534
540
  --dry-run
@@ -562,10 +568,11 @@ Options:
562
568
  --help, -h Show this help`,
563
569
  migrate: `Usage: phasegate migrate [options]
564
570
 
565
- Migrate phasegate.config.json from older schema versions. Backs up the original to phasegate.config.json.bak.
571
+ Migrate phasegate.config.json from older schema versions, or migrate legacy inception work item directories.
566
572
 
567
573
  Options:
568
574
  --dry-run Preview changes without writing
575
+ --apply Apply migration when supported
569
576
  --help, -h Show this help`,
570
577
  "list-errors": `Usage: phasegate list-errors [options]
571
578
 
@@ -769,7 +776,7 @@ function parseCoverageThreshold(raw: string | undefined): number {
769
776
  type InitPhasePreset = "full" | "standard" | "minimal" | "custom";
770
777
  type AgentTarget = "claude" | "codex" | "both";
771
778
  type SetupIntent = "minimal" | "recommended" | "strict" | "ci-only" | "agent-hooks" | "retrofit";
772
- type ConfigChangeIntent = "l4-strict" | "codex-hooks" | "ci-fail-on-warning" | "baseline-reset" | "quick-mode-strict";
779
+ type ConfigChangeIntent = "l4-strict" | "codex-hooks" | "ci-fail-on-warning" | "baseline-reset" | "quick-mode-strict" | "retrofit-bootstrap" | "planning-mode-relax";
773
780
  type SetupCompletenessStatus = "configured" | "planned" | "manual" | "not-applicable" | "unknown";
774
781
 
775
782
  interface SetupCompletenessEntry {
@@ -847,7 +854,9 @@ function parseConfigChangeIntent(value: string | undefined): ConfigChangeIntent
847
854
  value === "codex-hooks" ||
848
855
  value === "ci-fail-on-warning" ||
849
856
  value === "baseline-reset" ||
850
- value === "quick-mode-strict"
857
+ value === "quick-mode-strict" ||
858
+ value === "retrofit-bootstrap" ||
859
+ value === "planning-mode-relax"
851
860
  ) {
852
861
  return value;
853
862
  }
@@ -1216,6 +1225,14 @@ function buildConfigPatchPreview(intent: ConfigChangeIntent, before: unknown | n
1216
1225
  ],
1217
1226
  "codex-hooks": [],
1218
1227
  "baseline-reset": [],
1228
+ "retrofit-bootstrap": [
1229
+ { pointer: "/planningMode/default", path: ["planningMode", "default"], value: "manual" },
1230
+ { pointer: "/phaseDependencies/override", path: ["phaseDependencies", "override"], value: true },
1231
+ { pointer: "/quickMode/relaxedGates", path: ["quickMode", "relaxedGates"], value: ["phase-gate"] },
1232
+ ],
1233
+ "planning-mode-relax": [
1234
+ { pointer: "/planningMode/default", path: ["planningMode", "default"], value: "manual" },
1235
+ ],
1219
1236
  };
1220
1237
  const changes = configIntents[intent];
1221
1238
  if (changes.length === 0) {
@@ -1303,6 +1320,22 @@ async function buildConfigChangePlan(rootDir: string, intent: ConfigChangeIntent
1303
1320
  validations: ["phasegate ci-check --quick --dry-run", "phasegate phasegate:check-ready"],
1304
1321
  risks: ["More changes will require Full Mode validation before commit."],
1305
1322
  },
1323
+ "retrofit-bootstrap": {
1324
+ targets: ["phasegate.config.json: planningMode.default", "phasegate.config.json: phaseDependencies.override", "phasegate.config.json: quickMode.relaxedGates"],
1325
+ managedTargets: ["phasegate.config.json"],
1326
+ externalActions: [],
1327
+ commands: ["phasegate baseline --dry-run", "phasegate config:plan --intent retrofit-bootstrap --json"],
1328
+ validations: ["phasegate validate-metadata docs/inception/_shared/*.md", "phasegate check-phase-gate --level 2"],
1329
+ risks: ["Manual planning mode accepts existing retrofit planning evidence; review the patch before applying it to avoid weakening greenfield projects."],
1330
+ },
1331
+ "planning-mode-relax": {
1332
+ targets: ["phasegate.config.json: planningMode.default"],
1333
+ managedTargets: ["phasegate.config.json"],
1334
+ externalActions: [],
1335
+ commands: ["phasegate config:plan --intent planning-mode-relax --json"],
1336
+ validations: ["phasegate check-phase-gate --level 2", "phasegate phasegate:check-ready"],
1337
+ risks: ["Manual planning mode reduces PhaseGate's QA enforcement for plan documents until strict planning is restored."],
1338
+ },
1306
1339
  };
1307
1340
  const before = await readProjectJson(rootDir, "phasegate.config.json");
1308
1341
  return {
@@ -2371,6 +2404,16 @@ async function main(): Promise<void> {
2371
2404
  break;
2372
2405
  }
2373
2406
 
2407
+ case "status": {
2408
+ console.error("Warning: 'phasegate status' is deprecated; use 'phasegate phasegate:status'.");
2409
+ const mod = createHarnessApiModule();
2410
+ const flags: Record<string, boolean | string> = {};
2411
+ if (json) flags.json = true;
2412
+ await mod.handlers.status.handle({}, flags);
2413
+ if (!json) await printStoryReflectionStatusLine(rootDir);
2414
+ break;
2415
+ }
2416
+
2374
2417
  case "phasegate:lint": {
2375
2418
  const mod = createHarnessApiModule();
2376
2419
  const flags: Record<string, boolean | string> = {};
@@ -2389,6 +2432,15 @@ async function main(): Promise<void> {
2389
2432
  break;
2390
2433
  }
2391
2434
 
2435
+ case "complete-check": {
2436
+ console.error("Warning: 'phasegate complete-check' is deprecated; use 'phasegate phasegate:complete-check'.");
2437
+ const mod = createHarnessApiModule();
2438
+ const flags: Record<string, boolean | string> = {};
2439
+ if (json) flags.json = true;
2440
+ await mod.handlers.completeCheck.handle({}, flags);
2441
+ break;
2442
+ }
2443
+
2392
2444
  case "phasegate:impact-analysis": {
2393
2445
  const mod = createHarnessApiModule();
2394
2446
  const storyId = args[1] && !args[1].startsWith("--") ? args[1] : (parseFlag(args, "--story-id") ?? "");
@@ -2435,6 +2487,11 @@ Examples:
2435
2487
  phasegate ci:generate-template --preset strict --type pre-commit --render`);
2436
2488
  process.exit(0);
2437
2489
  }
2490
+ const flagError = validateKnownFlags(args.slice(1), ["--preset", "--type", "--render", "--json", "--help"]);
2491
+ if (flagError !== null) {
2492
+ console.error(flagError);
2493
+ process.exit(2);
2494
+ }
2438
2495
  const mod = buildCiGovernance(rootDir, harnessRoot);
2439
2496
  const presetId = parseFlag(args, "--preset") ?? "standard";
2440
2497
  const templateType = parseFlag(args, "--type") ?? "aidlc-gate";
@@ -2587,7 +2644,7 @@ Examples:
2587
2644
  const mod = createSkillQualityHandlers();
2588
2645
  const storyId = parseFlag(args, "--story") ?? "";
2589
2646
  const dryRun = hasFlag(args, "--dry-run");
2590
- const result = await mod.applyCascadeUpdateHandler.handle({ storyId, dryRun });
2647
+ const result = await mod.applyCascadeUpdateHandler.handle({ storyId, dryRun, format: json ? "json" : "human" });
2591
2648
  console.log(result.message);
2592
2649
  process.exit(result.exitCode);
2593
2650
  break;
@@ -2780,6 +2837,10 @@ Examples:
2780
2837
  }
2781
2838
 
2782
2839
  case "delegate-sonnet": {
2840
+ if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
2841
+ printSubcommandHelp("delegate-sonnet");
2842
+ process.exit(0);
2843
+ }
2783
2844
  const { spawn } = await import("node:child_process");
2784
2845
  const scriptPath = join(harnessRoot, "scripts/delegate-sonnet.sh");
2785
2846
  const forwardArgs = args.slice(1);
@@ -125,7 +125,7 @@ export class PhaseInfoResolver {
125
125
  blockers.push(`QAが未完了です: ${node.nodeKey()}`);
126
126
  }
127
127
  if (!actual.planningModeMatch) {
128
- blockers.push(`Planning Mode要件を満たしていません: ${node.nodeKey()}`);
128
+ blockers.push(`Planning Mode evidence requirement is not satisfied: ${node.nodeKey()}`);
129
129
  }
130
130
  }
131
131
  }
@@ -270,7 +270,7 @@ export class PhaseStructure {
270
270
  }
271
271
 
272
272
  if (!planEvidence.planningModeMatch) {
273
- blockers.push(`Planning Mode要件を満たしていません: ${node.nodeKey()}`);
273
+ blockers.push(`Planning Mode evidence requirement is not satisfied: ${node.nodeKey()}`);
274
274
  }
275
275
  }
276
276
 
@@ -3,7 +3,7 @@
3
3
  * @unit phase-dependency-model
4
4
  */
5
5
 
6
- export type PlanningModeValue = 'interactive' | 'embedded-qa';
6
+ export type PlanningModeValue = 'interactive' | 'embedded-qa' | 'manual';
7
7
 
8
8
  export class InvalidPlanningModeError extends Error {
9
9
  constructor(value: string) {
@@ -21,7 +21,7 @@ export class PlanningMode {
21
21
  }
22
22
 
23
23
  static create(value: string): PlanningMode {
24
- if (value !== 'interactive' && value !== 'embedded-qa') {
24
+ if (value !== 'interactive' && value !== 'embedded-qa' && value !== 'manual') {
25
25
  throw new InvalidPlanningModeError(value);
26
26
  }
27
27
 
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @layer application
3
3
  * @unit skill-quality
4
+ * @work-item-id WI-192
4
5
  */
5
6
  import { CascadeUpdateResult } from '../../domain/value-objects/cascade-update-result.js';
6
7
  import type { CascadeUpdateService } from '../../domain/services/cascade-update-service.js';
@@ -35,7 +36,7 @@ export class ApplyCascadeUpdateUseCase {
35
36
  const updatedContent = content.includes(target.storyIdTag)
36
37
  ? content
37
38
  : `${content}\n${target.storyIdTag}`;
38
- if (!input.dryRun) {
39
+ if (!input.dryRun && updatedContent !== content) {
39
40
  await this.fileSystemPort.write(filePath, updatedContent);
40
41
  }
41
42
  updatedCount++;
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * @layer presentation
3
3
  * @unit skill-quality
4
+ * @work-item-id WI-192
4
5
  */
5
6
  import type { ApplyCascadeUpdateUseCase } from '../../application/usecases/apply-cascade-update-usecase.js';
6
7
 
7
8
  export interface ApplyCascadeUpdateArgs {
8
9
  storyId: string;
9
10
  dryRun?: boolean;
11
+ format?: 'human' | 'json';
10
12
  }
11
13
 
12
14
  export class ApplyCascadeUpdateHandler {
@@ -15,8 +17,15 @@ export class ApplyCascadeUpdateHandler {
15
17
  async handle(args: ApplyCascadeUpdateArgs): Promise<{ exitCode: number; message: string }> {
16
18
  try {
17
19
  const output = await this.useCase.execute({ storyId: args.storyId, dryRun: args.dryRun });
20
+ if (args.format === 'json') {
21
+ return {
22
+ exitCode: output.errors.length > 0 ? 1 : 0,
23
+ message: JSON.stringify({ dryRun: args.dryRun === true, ...output }, null, 2),
24
+ };
25
+ }
18
26
  const tagsLine = output.appliedStoryIds.join(', ');
19
- let msg = `Updated ${output.updatedCount} files with tags: ${tagsLine}`;
27
+ const verb = args.dryRun ? 'Would update' : 'Updated';
28
+ let msg = `${verb} ${output.updatedCount} files with tags: ${tagsLine}`;
20
29
 
21
30
  if (output.errors.length > 0) {
22
31
  const errLines = output.errors.map((e) => ` - ${e}`).join('\n');