phasegate 0.160.3 → 0.160.5

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/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.160.5] - 2026-05-14
11
+
12
+ ### Fixed
13
+
14
+ - **WI-189 — umbrella CLI UX cleanup** — aligns `validate --format json` with the global JSON contract, makes `scaffold-design` default to dry-run with explicit `--apply`, fixes bypass audit empty-range wording, and synchronizes public help for scaffold, quick-mode, and delegate commands.
15
+
16
+ ## [0.160.4] - 2026-05-14
17
+
18
+ ### Fixed
19
+
20
+ - **WI-184 — skill catalog CLI** — fixes `phasegate skills list` so guidance-category skills no longer crash an undefined accumulator, shares the `SKILL.md` catalog path with `skills info`, and covers empty skill catalogs.
21
+
10
22
  ## [0.160.3] - 2026-05-14
11
23
 
12
24
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phasegate",
3
- "version": "0.160.3",
3
+ "version": "0.160.5",
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",
@@ -1,8 +1,10 @@
1
1
  // @unit ci-governance
2
2
  // @layer application
3
+ // @work-item-id WI-189
3
4
 
4
5
  export interface ScaffoldDesignInput {
5
6
  readonly unit: string;
6
7
  readonly phase: string;
8
+ readonly dryRun?: boolean;
7
9
  readonly force?: boolean;
8
10
  }
@@ -1,11 +1,13 @@
1
1
  // @unit ci-governance
2
2
  // @layer application
3
+ // @work-item-id WI-189
3
4
 
4
5
  export interface ScaffoldDesignOutput {
5
6
  readonly targetPath: string;
6
7
  readonly templatePath: string;
7
8
  readonly unit: string;
8
9
  readonly phase: string;
10
+ readonly dryRun: boolean;
9
11
  readonly written: boolean;
10
12
  readonly alreadyExists: boolean;
11
13
  readonly overwritten: boolean;
@@ -1,5 +1,6 @@
1
1
  // @unit ci-governance
2
2
  // @layer application
3
+ // @work-item-id WI-189
3
4
 
4
5
  import { DesignPhase } from '../../domain/value-objects/design-phase.js';
5
6
  import type { TemplateRepositoryPort } from '../../domain/ports/template-repository-port.js';
@@ -21,18 +22,33 @@ export class ScaffoldDesignUseCase {
21
22
  }
22
23
  const phase = DesignPhase.create(input.phase);
23
24
  const unit = input.unit.trim();
25
+ const dryRun = input.dryRun === true;
24
26
  const force = input.force === true;
25
27
 
26
28
  const templatePath = this.templates.resolvePath(phase);
27
29
  const targetPath = this.writer.resolvePath(unit, phase);
28
30
  const alreadyExists = await this.writer.exists(unit, phase);
29
31
 
32
+ if (dryRun) {
33
+ return {
34
+ targetPath,
35
+ templatePath,
36
+ unit,
37
+ phase: phase.value,
38
+ dryRun: true,
39
+ written: false,
40
+ alreadyExists,
41
+ overwritten: false,
42
+ };
43
+ }
44
+
30
45
  if (alreadyExists && !force) {
31
46
  return {
32
47
  targetPath,
33
48
  templatePath,
34
49
  unit,
35
50
  phase: phase.value,
51
+ dryRun: false,
36
52
  written: false,
37
53
  alreadyExists: true,
38
54
  overwritten: false,
@@ -48,6 +64,7 @@ export class ScaffoldDesignUseCase {
48
64
  templatePath,
49
65
  unit,
50
66
  phase: phase.value,
67
+ dryRun: false,
51
68
  written: true,
52
69
  alreadyExists,
53
70
  overwritten: alreadyExists && force,
@@ -1,5 +1,6 @@
1
1
  // @unit ci-governance
2
2
  // @layer presentation
3
+ // @work-item-id WI-189
3
4
 
4
5
  import { DesignPhase } from '../../domain/value-objects/design-phase.js';
5
6
  import type { ScaffoldDesignUseCase } from '../../application/usecases/scaffold-design-usecase.js';
@@ -7,6 +8,8 @@ import type { ScaffoldDesignUseCase } from '../../application/usecases/scaffold-
7
8
  export interface ScaffoldDesignHandlerArgs {
8
9
  readonly unit?: string;
9
10
  readonly phase?: string;
11
+ readonly dryRun?: boolean;
12
+ readonly apply?: boolean;
10
13
  readonly force?: boolean;
11
14
  readonly format?: 'human' | 'json';
12
15
  }
@@ -21,6 +24,12 @@ export class ScaffoldDesignHandler {
21
24
 
22
25
  async handle(args: ScaffoldDesignHandlerArgs): Promise<ScaffoldDesignHandlerResult> {
23
26
  const format = args.format ?? 'human';
27
+ const apply = args.apply === true;
28
+ const dryRun = args.dryRun === true || !apply;
29
+
30
+ if (args.dryRun === true && apply) {
31
+ return this.fail(format, '--dry-run と --apply は同時に指定できません', 2);
32
+ }
24
33
 
25
34
  if (!args.unit || args.unit.trim().length === 0) {
26
35
  return this.fail(format, '--unit <unit-id> は必須です', 2);
@@ -45,6 +54,7 @@ export class ScaffoldDesignHandler {
45
54
  result = await this.useCase.execute({
46
55
  unit: args.unit,
47
56
  phase: args.phase,
57
+ dryRun,
48
58
  force: args.force,
49
59
  });
50
60
  } catch (err) {
@@ -61,10 +71,22 @@ export class ScaffoldDesignHandler {
61
71
 
62
72
  if (result.alreadyExists && !result.written) {
63
73
  return {
64
- exitCode: 2,
74
+ exitCode: result.dryRun ? 0 : 2,
75
+ output: [
76
+ result.dryRun ? `dry-run: 既に存在します: ${result.targetPath}` : `既に存在します: ${result.targetPath}`,
77
+ result.dryRun ? '書き込むには --apply を指定してください。' : '上書きするには --force を指定してください。',
78
+ ].join('\n'),
79
+ };
80
+ }
81
+
82
+ if (result.dryRun) {
83
+ return {
84
+ exitCode: 0,
65
85
  output: [
66
- `既に存在します: ${result.targetPath}`,
67
- '上書きするには --force を指定してください。',
86
+ `dry-run: 設計文書を生成予定: ${result.targetPath}`,
87
+ `テンプレ: ${result.templatePath}`,
88
+ `Unit: ${result.unit} / phase: ${result.phase}`,
89
+ '書き込むには --apply を指定してください。',
68
90
  ].join('\n'),
69
91
  };
70
92
  }
@@ -3,6 +3,7 @@
3
3
  * @layer presentation
4
4
  * @work-item-id WI-141
5
5
  * @work-item-id WI-109
6
+ * @work-item-id WI-189
6
7
  *
7
8
  * Pre-commit CLI entry.
8
9
  * Runs L2 validators against staged TypeScript files AND design-document
@@ -533,6 +534,11 @@ function hasCompleteBypassTrailerSet(results: readonly BypassTrailerValidationRe
533
534
  return results.some((result) => result.hasAnyBypassTrailer && result.complete);
534
535
  }
535
536
 
537
+ function toBypassAuditStdout(stdout: string, changedFiles: readonly string[]): string {
538
+ if (changedFiles.length > 0) return stdout;
539
+ return stdout.replace("No staged files to check. Skipping.", "No changed files in range to check. Skipping.");
540
+ }
541
+
536
542
  export async function runBypassAudit(
537
543
  deps: PreCommitDeps,
538
544
  options: BypassAuditOptions = {},
@@ -554,7 +560,7 @@ export async function runBypassAudit(
554
560
 
555
561
  const sections = [
556
562
  `${BOLD}[phasegate]${RESET} Bypass audit (${baseRef}..${headRef})`,
557
- result.stdout,
563
+ toBypassAuditStdout(result.stdout, changedFiles),
558
564
  ];
559
565
  let exitCode = result.exitCode;
560
566
  if (result.exitCode !== 0 && !hasCompleteBypassTrailerSet(bypassResults)) {
@@ -6,6 +6,8 @@
6
6
  * @work-item-id WI-171 / WI-172 / WI-173
7
7
  * @work-item-id WI-175
8
8
  * @work-item-id WI-176
9
+ * @work-item-id WI-184
10
+ * @work-item-id WI-189
9
11
  *
10
12
  * Phasegate CLI エントリポイント。
11
13
  * 各Unitの Composition Root からハンドラーを取得し、コマンドに応じてディスパッチする。
@@ -64,7 +66,9 @@ import {
64
66
  deploySkills,
65
67
  getCategoryForSkill,
66
68
  getHarnessVersion,
69
+ getSkillMarkdownPath,
67
70
  initHarnessConfig,
71
+ listAvailableSkillNames,
68
72
  } from "./setup/skill-deployer.js";
69
73
  import { createSkillQualityHandlers } from "./skill-quality/composition-root.js";
70
74
  import { createTraceabilityModelModule } from "./traceability-model/composition-root.js";
@@ -153,7 +157,8 @@ Setup:
153
157
  --with-husky, --with-ci, --yes)
154
158
  update-skills Alias for reconcile (kept for compatibility)
155
159
  doctor Diagnose silent installation failures (--json, --strict, --agent <claude|codex|both>, --report-out <path>)
156
- scaffold-wi <unit> <type> Create docs/inception/{unit}/WI-XXX/description.md
160
+ scaffold-wi <unit|_cross> <story|issue|chore>
161
+ Create docs/inception/{unit}/WI-XXX/description.md
157
162
  emit-agent-rules Print AGENTS.md / CLAUDE.md WI workflow rules block
158
163
  install Install phasegate managed files (--dry-run|--apply, --force)
159
164
  uninstall Uninstall phasegate managed files (--dry-run|--apply, --force)
@@ -180,8 +185,9 @@ Commands:
180
185
 
181
186
  lint Run lint checks (--json, --target <path>)
182
187
 
183
- validate Run validators (--layer L2|L3|L4|all; L0 prints runtime hook info, --unit, --format human|agent|ci)
188
+ validate Run validators (--layer L2|L3|L4|all; L0 prints runtime hook info, --unit, --format human|agent|ci|json, --json)
184
189
  ci-check CI check (--quick for quick mode, --fail-on-reject, --dry-run, --files)
190
+ check-change-category Classify changed paths for quick mode (--paths <csv>, --format human|json)
185
191
 
186
192
  phasegate:check-ready Check ready status (--json)
187
193
  phasegate:check-phase Check phase gate (--unit <unitId>, --json)
@@ -208,7 +214,7 @@ Gate semantics:
208
214
  config:plan Plan safe config changes (--intent <l4-strict|codex-hooks|ci-fail-on-warning|baseline-reset|quick-mode-strict>, --dry-run, --json)
209
215
  ci:check-repetition Check error repetition (--code <errorCode>, --reset, --json)
210
216
  baseline Create retrofit baseline snapshot (--dry-run, --force, --paths <glob,glob,...>, --json)
211
- scaffold-design Scaffold a design doc (--unit <id>, --phase <logical|domain|uiux|unit-test|it-test>, --force, --json)
217
+ scaffold-design Scaffold a design doc (--unit <id>, --phase <logical|domain|uiux|unit-test|it-test>, --dry-run|--apply, --force, --json)
212
218
 
213
219
  skill:execute-tdd-cycle Execute TDD cycle (--unit, --story, --desc, --phase RED|GREEN|REFACTOR, --passed)
214
220
  skill:check-coverage Check coverage (--story <storyId>, --json)
@@ -406,7 +412,8 @@ function parseValidateFormat(args: readonly string[]): "human" | "agent" | "ci"
406
412
  const raw = parseFlag(args, "--format");
407
413
  if (raw === undefined) return undefined;
408
414
  if (raw === "human" || raw === "agent" || raw === "ci") return raw;
409
- throw new Error(`Invalid --format value for validate: '${raw}'. Supported values: human, agent, ci.`);
415
+ if (raw === "json") return "ci";
416
+ throw new Error(`Invalid --format value for validate: '${raw}'. Supported values: human, agent, ci, json.`);
410
417
  }
411
418
 
412
419
  function levenshtein(a: string, b: string): number {
@@ -543,7 +550,8 @@ Run validators against the project. Without --layer, runs all enabled validator
543
550
 
544
551
  Options:
545
552
  --layer <L0|L2|L3|L4> Run only the specified validator layer; L0 prints runtime hook info
546
- --json Output machine-readable JSON
553
+ --format <human|agent|ci|json> Output format; json is an alias for ci JSON
554
+ --json Output machine-readable JSON when --format is omitted
547
555
  --help, -h Show this help`,
548
556
  lint: `Usage: phasegate lint [options]
549
557
 
@@ -623,6 +631,28 @@ Options:
623
631
  Examples:
624
632
  phasegate check-change-category --paths src/foo.ts,src/bar.ts
625
633
  phasegate check-change-category --paths src/foo.ts --format json`,
634
+ "scaffold-wi": `Usage: phasegate scaffold-wi <unit|_cross> <story|issue|chore>
635
+
636
+ Create docs/inception/{unit}/WI-XXX/description.md.
637
+
638
+ Arguments:
639
+ <unit|_cross> Unit id or _cross for cross-cutting work items.
640
+ <story|issue|chore> Work item type.
641
+
642
+ Options:
643
+ --help, -h Show this help`,
644
+ "scaffold-design": `Usage: phasegate scaffold-design --unit <id> --phase <phase> [options]
645
+
646
+ Scaffold a product construction design document.
647
+
648
+ Options:
649
+ --unit <id> Unit id under docs/product/construction.
650
+ --phase <phase> logical, domain, uiux, unit-test, or it-test.
651
+ --dry-run Preview target and template without writing (default).
652
+ --apply Write the scaffold.
653
+ --force Overwrite an existing target when applying.
654
+ --json Output machine-readable JSON.
655
+ --help, -h Show this help`,
626
656
  "ci:generate-template": `Usage: phasegate ci:generate-template [options]
627
657
 
628
658
  Generates a CI template configuration.
@@ -640,6 +670,15 @@ Options:
640
670
  Examples:
641
671
  phasegate ci:generate-template --type aidlc-gate
642
672
  phasegate ci:generate-template --preset strict --type pre-commit --render`,
673
+ "delegate-sonnet": `Usage: phasegate delegate-sonnet [...args]
674
+
675
+ Delegate a task to Sonnet 4.6 by forwarding all args to scripts/delegate-sonnet.sh.
676
+
677
+ Arguments:
678
+ [...args] Task text and options consumed by the delegate script.
679
+
680
+ Options:
681
+ --help, -h Show this help`,
643
682
  };
644
683
 
645
684
  function printSubcommandHelp(command: string): void {
@@ -2188,7 +2227,7 @@ async function main(): Promise<void> {
2188
2227
  const layer = parseFlag(args, "--layer") as "L0" | "L2" | "L3" | "L4" | "all" | undefined;
2189
2228
  const unit = parseFlag(args, "--unit");
2190
2229
  const phase = parseFlag(args, "--phase");
2191
- const format = parseValidateFormat(args);
2230
+ const format = parseValidateFormat(args) ?? (json ? "ci" : undefined);
2192
2231
  // WI-094 / ADR-017: --fail-on-warning / --no-fail-on-warning / 未指定→config値
2193
2232
  const failOnWarning = parseTriStateFlag(args, "--fail-on-warning", "--no-fail-on-warning");
2194
2233
  const noL4 = hasFlag(args, "--no-l4");
@@ -2203,7 +2242,7 @@ async function main(): Promise<void> {
2203
2242
  targetPaths,
2204
2243
  });
2205
2244
  console.log(result.output);
2206
- if (layer === "L2" || layer === "all") {
2245
+ if ((layer === "L2" || layer === "all") && format !== "ci") {
2207
2246
  await printStoryReflectionValidationSummary(rootDir, unit);
2208
2247
  }
2209
2248
  process.exit(result.exitCode);
@@ -2489,11 +2528,15 @@ Examples:
2489
2528
  const mod = buildCiGovernance(rootDir, harnessRoot);
2490
2529
  const unit = parseFlag(args, "--unit") ?? "";
2491
2530
  const phase = parseFlag(args, "--phase") ?? "";
2531
+ const dryRun = hasFlag(args, "--dry-run");
2532
+ const apply = hasFlag(args, "--apply");
2492
2533
  const force = hasFlag(args, "--force");
2493
2534
  const format = json ? "json" : "human";
2494
2535
  const result = await mod.scaffoldDesignHandler.handle({
2495
2536
  unit,
2496
2537
  phase,
2538
+ dryRun,
2539
+ apply,
2497
2540
  force,
2498
2541
  format,
2499
2542
  });
@@ -2753,25 +2796,16 @@ Examples:
2753
2796
  // ── skills ──
2754
2797
  case "skills": {
2755
2798
  const subCommand = args[1];
2756
- const skillsRoot = join(harnessRoot, "skills");
2757
2799
 
2758
2800
  if (subCommand === "list") {
2759
- const { promises: fs } = await import("node:fs");
2760
- const entries = await fs.readdir(skillsRoot, { withFileTypes: true });
2761
- const skills: string[] = [];
2762
- for (const entry of entries) {
2763
- if (entry.isDirectory()) {
2764
- try {
2765
- await fs.access(join(skillsRoot, entry.name, "SKILL.md"));
2766
- skills.push(entry.name);
2767
- } catch {
2768
- // skip directories without SKILL.md
2769
- }
2770
- }
2771
- }
2772
- skills.sort();
2773
-
2774
- const grouped: Record<string, string[]> = { core: [], aidlc: [], utility: [], unknown: [] };
2801
+ const skills = await listAvailableSkillNames(harnessRoot);
2802
+ const grouped: Record<"core" | "aidlc" | "utility" | "guidance" | "unknown", string[]> = {
2803
+ core: [],
2804
+ aidlc: [],
2805
+ utility: [],
2806
+ guidance: [],
2807
+ unknown: [],
2808
+ };
2775
2809
  for (const name of skills) {
2776
2810
  const cat = getCategoryForSkill(name) ?? "unknown";
2777
2811
  grouped[cat].push(name);
@@ -2783,8 +2817,9 @@ Examples:
2783
2817
  core: "Core — Quality Defense",
2784
2818
  aidlc: "AIDLC — Development Workflow",
2785
2819
  utility: "Utility",
2820
+ guidance: "Guidance",
2786
2821
  };
2787
- for (const cat of ["core", "aidlc", "utility", "unknown"] as const) {
2822
+ for (const cat of ["core", "aidlc", "utility", "guidance", "unknown"] as const) {
2788
2823
  if (grouped[cat].length === 0) continue;
2789
2824
  const label = labels[cat] ?? "Other";
2790
2825
  console.log(` [${label}] (${grouped[cat].length})`);
@@ -2803,7 +2838,7 @@ Examples:
2803
2838
  process.exit(2);
2804
2839
  }
2805
2840
  const { promises: fs } = await import("node:fs");
2806
- const skillMdPath = join(skillsRoot, skillName, "SKILL.md");
2841
+ const skillMdPath = getSkillMarkdownPath(harnessRoot, skillName);
2807
2842
  try {
2808
2843
  const content = await fs.readFile(skillMdPath, "utf-8");
2809
2844
  console.log(content);
@@ -2,6 +2,7 @@
2
2
  // @layer infrastructure
3
3
  // @work-item-id WI-086 / WI-087
4
4
  // @work-item-id WI-127
5
+ // @work-item-id WI-184
5
6
  // Note: import.meta.url を使わず、呼び出し元 (main.ts) がパスを解決して渡す設計。
6
7
 
7
8
  import { promises as fs } from "node:fs";
@@ -78,6 +79,33 @@ export function getCategoryForSkill(skillName: string): SkillCategory | null {
78
79
  return null;
79
80
  }
80
81
 
82
+ export function getSkillMarkdownPath(harnessRoot: string, skillName: string): string {
83
+ return join(harnessRoot, SKILLS_SOURCE_DIR, skillName, "SKILL.md");
84
+ }
85
+
86
+ export async function listAvailableSkillNames(harnessRoot: string): Promise<string[]> {
87
+ const skillsRoot = join(harnessRoot, SKILLS_SOURCE_DIR);
88
+ let entries;
89
+ try {
90
+ entries = await fs.readdir(skillsRoot, { withFileTypes: true });
91
+ } catch {
92
+ return [];
93
+ }
94
+
95
+ const skills: string[] = [];
96
+ for (const entry of entries) {
97
+ if (!entry.isDirectory()) continue;
98
+ try {
99
+ await fs.access(getSkillMarkdownPath(harnessRoot, entry.name));
100
+ skills.push(entry.name);
101
+ } catch {
102
+ // Directories without SKILL.md are not catalog entries.
103
+ }
104
+ }
105
+
106
+ return skills.sort();
107
+ }
108
+
81
109
  export interface DeployResult {
82
110
  deployedSkills: string[];
83
111
  targetDir: string;