phasegate 0.83.0 → 0.107.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +313 -0
  2. package/README.ja.md +15 -7
  3. package/README.md +24 -4
  4. package/docs/ADR/ADR-014-presentation-domain-dependency.md +82 -0
  5. package/docs/ADR/ADR-015-architecture-preset.md +183 -0
  6. package/docs/guide/codex-integration.md +7 -2
  7. package/docs/guide/installation.md +10 -2
  8. package/docs/guide/preset-selection.md +170 -0
  9. package/docs/guide/quick-vs-full-mode.md +3 -3
  10. package/docs/guide/retrofit-adoption.md +19 -2
  11. package/docs/guide/skills-overview.md +1 -1
  12. package/package.json +7 -1
  13. package/scripts/harness/adr-foundation/application/dto/application-errors.ts +0 -13
  14. package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +108 -100
  15. package/scripts/harness/agent-integration/domain/ports/error-guidance-query-port.ts +0 -8
  16. package/scripts/harness/agent-integration/domain/ports/phase-gate-query-port.ts +3 -3
  17. package/scripts/harness/agent-integration/domain/value-objects/write-target-scope.ts +26 -30
  18. package/scripts/harness/agent-integration/infrastructure/adapters/file-system-story-reflection-query-adapter.ts +6 -2
  19. package/scripts/harness/biome-ast-engine/application/dto/analyze-import-graph-input.ts +3 -0
  20. package/scripts/harness/biome-ast-engine/application/dto/resolve-enabled-rules-output.ts +2 -0
  21. package/scripts/harness/biome-ast-engine/application/mappers/resolve-enabled-rules-output-mapper.ts +4 -1
  22. package/scripts/harness/biome-ast-engine/application/usecases/analyze-import-graph-usecase.ts +4 -1
  23. package/scripts/harness/biome-ast-engine/application/usecases/execute-lint-usecase.ts +2 -0
  24. package/scripts/harness/biome-ast-engine/application/usecases/resolve-enabled-rules-usecase.ts +31 -3
  25. package/scripts/harness/biome-ast-engine/composition-root.ts +10 -2
  26. package/scripts/harness/biome-ast-engine/domain/ports/rule-config-provider-port.ts +16 -0
  27. package/scripts/harness/biome-ast-engine/domain/ports/source-module-analyzer-port.ts +5 -1
  28. package/scripts/harness/biome-ast-engine/domain/services/lint-runner.ts +5 -1
  29. package/scripts/harness/biome-ast-engine/domain/services/rule-definition-registry.ts +1 -0
  30. package/scripts/harness/biome-ast-engine/domain/value-objects/architecture-spec.ts +38 -0
  31. package/scripts/harness/biome-ast-engine/domain/value-objects/layer-boundary.ts +7 -8
  32. package/scripts/harness/biome-ast-engine/domain/value-objects/layer-name.ts +15 -23
  33. package/scripts/harness/biome-ast-engine/domain/value-objects/source-module-snapshot.ts +11 -4
  34. package/scripts/harness/biome-ast-engine/infrastructure/adapters/harness-config-provider-adapter.ts +29 -3
  35. package/scripts/harness/biome-ast-engine/infrastructure/adapters/typescript-source-module-analyzer-adapter.ts +22 -15
  36. package/scripts/harness/biome-ast-engine/infrastructure/mappers/source-module-snapshot-mapper.ts +26 -17
  37. package/scripts/harness/config-foundation/application/dto/resolved-config-output.ts +1 -0
  38. package/scripts/harness/config-foundation/application/usecases/load-resolved-config-use-case.ts +34 -2
  39. package/scripts/harness/config-foundation/application/usecases/migrate-schema-use-case.ts +89 -0
  40. package/scripts/harness/config-foundation/composition-root.ts +8 -0
  41. package/scripts/harness/config-foundation/domain/harness-config.ts +6 -0
  42. package/scripts/harness/config-foundation/domain/services/architecture-resolution-service.ts +257 -0
  43. package/scripts/harness/config-foundation/domain/value-objects/architecture-config.ts +66 -0
  44. package/scripts/harness/config-foundation/domain/value-objects/architecture-preset-catalog.ts +75 -0
  45. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +546 -0
  46. package/scripts/harness/config-foundation/infrastructure/validators/ajv-config-schema-validator.ts +18 -6
  47. package/scripts/harness/config-foundation/presentation/cli/migrate-schema-command-handler.ts +83 -0
  48. package/scripts/harness/integrations/pre-commit.ts +211 -52
  49. package/scripts/harness/main.ts +460 -340
  50. package/scripts/harness/phase-dependency-model/domain/ports/story-reflection-file-system-port.ts +2 -4
  51. package/scripts/harness/phase-dependency-model/domain/services/story-reflection-checker.ts +54 -17
  52. package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.ts +154 -36
  53. package/scripts/harness/setup/skill-deployer.ts +140 -102
  54. package/scripts/harness/skill-quality/domain/errors/skill-quality-error.ts +24 -23
  55. package/scripts/harness/skill-quality/domain/value-objects/commit-message.ts +23 -7
  56. package/scripts/harness/traceability-model/application/usecases/apply-work-item-migration-usecase.ts +52 -0
  57. package/scripts/harness/traceability-model/application/usecases/plan-work-item-migration-usecase.ts +29 -0
  58. package/scripts/harness/traceability-model/application/usecases/validate-design-story-annotations-usecase.ts +83 -18
  59. package/scripts/harness/traceability-model/composition-root.ts +48 -30
  60. package/scripts/harness/traceability-model/domain/ports/design-document-port.ts +9 -15
  61. package/scripts/harness/traceability-model/domain/ports/work-item-migration-apply-port.ts +11 -0
  62. package/scripts/harness/traceability-model/domain/ports/work-item-migration-source-port.ts +9 -0
  63. package/scripts/harness/traceability-model/domain/services/metadata-validator.ts +1 -1
  64. package/scripts/harness/traceability-model/domain/services/work-item-migration-planner.ts +162 -0
  65. package/scripts/harness/traceability-model/domain/value-objects/story-id.ts +3 -3
  66. package/scripts/harness/traceability-model/domain/value-objects/work-item-frontmatter.ts +57 -0
  67. package/scripts/harness/traceability-model/domain/value-objects/work-item-migration-candidate.ts +47 -0
  68. package/scripts/harness/traceability-model/infrastructure/gateways/file-system-work-item-migration-apply-gateway.ts +110 -0
  69. package/scripts/harness/traceability-model/infrastructure/gateways/file-system-work-item-migration-source-gateway.ts +182 -0
  70. package/scripts/harness/traceability-model/infrastructure/gateways/markdown-design-document-gateway.ts +29 -43
  71. package/scripts/harness/traceability-model/infrastructure/parsers/story-catalog-parser.ts +2 -2
  72. package/scripts/harness/traceability-model/infrastructure/parsers/work-item-frontmatter-parser.ts +136 -0
  73. package/scripts/harness/traceability-model/presentation/cli/migrate-work-items-command-handler.ts +186 -0
  74. package/skills/quick-implementor/SKILL.md +17 -1
  75. package/templates/.husky/commit-msg +1 -0
  76. package/scripts/harness/adr-foundation/application/dto/seed-adr-definition.ts +0 -21
  77. package/scripts/harness/adr-foundation/application/usecases/seed-initial-adrs-use-case.ts +0 -84
  78. package/scripts/harness/adr-foundation/infrastructure/seeds/initial-adr-definitions.ts +0 -161
@@ -1,4 +1,5 @@
1
1
  /**
2
+ * @unit harness-api
2
3
  * @layer presentation
3
4
  *
4
5
  * Phasegate CLI エントリポイント。
@@ -7,46 +8,73 @@
7
8
  * 起動時に config-foundation で設定を解決し、他Unit に注入する(Cross-unit wiring)。
8
9
  */
9
10
 
10
- import { dirname, join, resolve } from 'node:path';
11
- import { readFile as fsReadFile } from 'node:fs/promises';
12
- import { createConfigFoundationModule } from './config-foundation/composition-root.js';
13
- import { toPhaseConfigSection } from './config-foundation/application/mappers/phase-config-section-mapper.js';
14
- import { createHarnessErrorModule } from './harness-error/composition-root.js';
15
- import { createTraceabilityModelModule } from './traceability-model/composition-root.js';
16
- import { createPhaseDependencyModelModule } from './phase-dependency-model/composition-root.js';
17
- import { HarnessConfigPhaseConfigProvider, type PhaseConfigSection as PhaseDepConfigSection } from './phase-dependency-model/infrastructure/config/harness-config-phase-config-provider.js';
18
- import { StoryReflectionChecker } from './phase-dependency-model/domain/services/story-reflection-checker.js';
19
- import { CheckStoryReflectionUseCase } from './phase-dependency-model/application/usecases/check-story-reflection-usecase.js';
20
- import { FileSystemStoryReflectionAdapter } from './phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.js';
21
- import { StoryReflectionStatusPresenter } from './phase-dependency-model/presentation/cli/story-reflection-status-presenter.js';
22
- import { StoryReflectionResult } from './phase-dependency-model/domain/values/story-reflection-result.js';
23
- import { createAdrFoundationModule } from './adr-foundation/composition-root.js';
24
- import { createBiomeAstEngineModule } from './biome-ast-engine/composition-root.js';
25
- import { createValidatorSystemModule } from './validator-system/composition-root.js';
26
- import { createQuickModeCompositionRoot } from './quick-mode/composition-root.js';
27
- import { createHarnessApiModule } from './harness-api/composition-root.js';
28
- import { buildCiGovernance } from './ci-governance/composition-root.js';
29
- import { createSkillQualityHandlers } from './skill-quality/composition-root.js';
30
- import { buildRegressionSuite } from './regression-suite/composition-root.js';
31
- import { buildPhase2Extensions } from './phase2-extensions/composition-root.js';
32
- import { deploySkills, deployHookScripts, getDeployedVersion, getHarnessVersion, initHarnessConfig, deployDesignDocs, deployHuskyHook, deployCodexHooks, SKILL_CATEGORIES, getCategoryForSkill } from './setup/skill-deployer.js';
33
- import type { SkillSet } from './setup/skill-deployer.js';
34
- import type { HarnessConfigV2 } from './config-foundation/domain/harness-config.js';
35
- import { ConfigValidationError } from './config-foundation/domain/errors/config-validation-error.js';
36
- import { ConfigNotFoundError, ConfigPersistenceError } from './config-foundation/infrastructure/repositories/file-system-config-repository.js';
11
+ import { access, readFile as fsReadFile } from "node:fs/promises";
12
+ import { dirname, join, resolve } from "node:path";
13
+ import { createAdrFoundationModule } from "./adr-foundation/composition-root.js";
14
+ import { createBiomeAstEngineModule } from "./biome-ast-engine/composition-root.js";
15
+ import { buildCiGovernance } from "./ci-governance/composition-root.js";
16
+ import { toPhaseConfigSection } from "./config-foundation/application/mappers/phase-config-section-mapper.js";
17
+ import { createConfigFoundationModule } from "./config-foundation/composition-root.js";
18
+ import { ConfigValidationError } from "./config-foundation/domain/errors/config-validation-error.js";
19
+ import type { HarnessConfigV2 } from "./config-foundation/domain/harness-config.js";
20
+ import {
21
+ ConfigNotFoundError,
22
+ ConfigPersistenceError,
23
+ } from "./config-foundation/infrastructure/repositories/file-system-config-repository.js";
24
+ import { createHarnessApiModule } from "./harness-api/composition-root.js";
25
+ import { createHarnessErrorModule } from "./harness-error/composition-root.js";
26
+ import { CheckStoryReflectionUseCase } from "./phase-dependency-model/application/usecases/check-story-reflection-usecase.js";
27
+ import { createPhaseDependencyModelModule } from "./phase-dependency-model/composition-root.js";
28
+ import { StoryReflectionChecker } from "./phase-dependency-model/domain/services/story-reflection-checker.js";
29
+ import { StoryReflectionResult } from "./phase-dependency-model/domain/values/story-reflection-result.js";
30
+ import {
31
+ HarnessConfigPhaseConfigProvider,
32
+ type PhaseConfigSection as PhaseDepConfigSection,
33
+ } from "./phase-dependency-model/infrastructure/config/harness-config-phase-config-provider.js";
34
+ import { FileSystemStoryReflectionAdapter } from "./phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.js";
35
+ import { StoryReflectionStatusPresenter } from "./phase-dependency-model/presentation/cli/story-reflection-status-presenter.js";
36
+ import { buildPhase2Extensions } from "./phase2-extensions/composition-root.js";
37
+ import { createQuickModeCompositionRoot } from "./quick-mode/composition-root.js";
38
+ import { buildRegressionSuite } from "./regression-suite/composition-root.js";
39
+ import type { SkillSet } from "./setup/skill-deployer.js";
40
+ import {
41
+ deployAgentSkillLinks,
42
+ deployCodexHooks,
43
+ deployDesignDocs,
44
+ deployHookScripts,
45
+ deployHuskyCommitMsgHook,
46
+ deployHuskyHook,
47
+ deploySkills,
48
+ getCategoryForSkill,
49
+ getDeployedVersion,
50
+ getHarnessVersion,
51
+ initHarnessConfig,
52
+ } from "./setup/skill-deployer.js";
53
+ import { createSkillQualityHandlers } from "./skill-quality/composition-root.js";
54
+ import { createTraceabilityModelModule } from "./traceability-model/composition-root.js";
55
+ import { createValidatorSystemModule } from "./validator-system/composition-root.js";
37
56
 
38
57
  /**
39
58
  * main.ts (scripts/harness/main.ts) から2階層上がパッケージルート。
40
59
  * process.argv[1] は tsx 実行時にスクリプトの絶対パスになる。
41
60
  */
42
61
  function getHarnessRoot(): string {
43
- return resolve(dirname(process.argv[1]), '../..');
62
+ return resolve(dirname(process.argv[1]), "../..");
44
63
  }
45
64
 
46
65
  function getProjectRoot(): string {
47
66
  return process.cwd();
48
67
  }
49
68
 
69
+ async function pathExists(path: string): Promise<boolean> {
70
+ try {
71
+ await access(path);
72
+ return true;
73
+ } catch {
74
+ return false;
75
+ }
76
+ }
77
+
50
78
  function printUsage(): void {
51
79
  const usage = `
52
80
  Usage: phasegate <command> [options]
@@ -60,6 +88,7 @@ Commands:
60
88
  enable-feature <name> Enable a harness feature
61
89
  disable-feature <name> Disable a harness feature
62
90
  list-features List available features
91
+ migrate Migrate phasegate.config.json (--schema v3, --config <path>)
63
92
 
64
93
  render-errors Render harness errors (--format human|agent|ci)
65
94
  validate-fix Validate fix examples (--code <code>)
@@ -111,6 +140,7 @@ Commands:
111
140
  p2:check-initial-creation Detect long-lived initial_creation:true docs (--pattern <glob>, --format text|json)
112
141
  hook <pre-tool-use|post-tool-use|stop|session-start|user-prompt-submit> Run agent hook (reads JSON from stdin; writes JSON to stdout for session-start/user-prompt-submit)
113
142
  pre-commit Run L2 pre-commit validators on staged files
143
+ commit-msg <message-file> Validate commit message trailers against staged files
114
144
  delegate-sonnet [...args] Delegate task to Sonnet 4.6 (forwards args to scripts/delegate-sonnet.sh)
115
145
 
116
146
  Skills:
@@ -137,16 +167,13 @@ function hasFlag(args: readonly string[], flag: string): boolean {
137
167
  }
138
168
 
139
169
  /** フラグとその値を除いた位置引数のみを返す */
140
- function parsePositionalArgs(
141
- args: readonly string[],
142
- flagsWithValues: readonly string[] = [],
143
- ): string[] {
170
+ function parsePositionalArgs(args: readonly string[], flagsWithValues: readonly string[] = []): string[] {
144
171
  const result: string[] = [];
145
172
  const valueFlags = new Set(flagsWithValues);
146
173
 
147
174
  for (let i = 0; i < args.length; i++) {
148
175
  const arg = args[i];
149
- if (arg.startsWith('--')) {
176
+ if (arg.startsWith("--")) {
150
177
  if (valueFlags.has(arg)) {
151
178
  i++; // skip flag value
152
179
  }
@@ -158,51 +185,49 @@ function parsePositionalArgs(
158
185
  return result;
159
186
  }
160
187
 
161
- type RenderFormat = 'human' | 'agent' | 'ci';
188
+ type RenderFormat = "human" | "agent" | "ci";
162
189
 
163
190
  function toRenderFormat(value: string): RenderFormat {
164
- if (value === 'human' || value === 'agent' || value === 'ci') return value;
165
- return 'human';
191
+ if (value === "human" || value === "agent" || value === "ci") return value;
192
+ return "human";
166
193
  }
167
194
 
168
- type ListFormat = 'human' | 'json';
195
+ type ListFormat = "human" | "json";
169
196
 
170
197
  function toListFormat(value: string): ListFormat {
171
- if (value === 'human' || value === 'json') return value;
172
- return 'human';
198
+ if (value === "human" || value === "json") return value;
199
+ return "human";
173
200
  }
174
201
 
175
- type LayerIdFilter = 'L0' | 'L1' | 'L2' | 'L3' | 'L4';
202
+ type LayerIdFilter = "L0" | "L1" | "L2" | "L3" | "L4";
176
203
 
177
204
  function toLayerFilter(value: string | undefined): LayerIdFilter | undefined {
178
- if (value === 'L0' || value === 'L1' || value === 'L2' || value === 'L3' || value === 'L4') return value;
205
+ if (value === "L0" || value === "L1" || value === "L2" || value === "L3" || value === "L4") return value;
179
206
  return undefined;
180
207
  }
181
208
 
182
- type AdrStatus = 'Proposed' | 'Accepted' | 'Deprecated' | 'Superseded';
209
+ type AdrStatus = "Proposed" | "Accepted" | "Deprecated" | "Superseded";
183
210
 
184
211
  function isAdrStatus(s: string): s is AdrStatus {
185
- return s === 'Proposed' || s === 'Accepted' || s === 'Deprecated' || s === 'Superseded';
212
+ return s === "Proposed" || s === "Accepted" || s === "Deprecated" || s === "Superseded";
186
213
  }
187
214
 
188
215
  function toAdrStatuses(csv: string | undefined): readonly AdrStatus[] | undefined {
189
216
  if (!csv) return undefined;
190
- return csv.split(',').filter(isAdrStatus);
217
+ return csv.split(",").filter(isAdrStatus);
191
218
  }
192
219
 
193
- type SuiteIdValue = 'k-requirements' | 'gng-gate' | 'v0-migration' | 'agent-independence';
220
+ type SuiteIdValue = "k-requirements" | "gng-gate" | "v0-migration" | "agent-independence";
194
221
 
195
- const VALID_SUITE_IDS: readonly SuiteIdValue[] = [
196
- 'k-requirements', 'gng-gate', 'v0-migration', 'agent-independence',
197
- ];
198
- const DEFAULT_REGRESSION_SUITES = 'k-requirements,gng-gate';
222
+ const VALID_SUITE_IDS: readonly SuiteIdValue[] = ["k-requirements", "gng-gate", "v0-migration", "agent-independence"];
223
+ const DEFAULT_REGRESSION_SUITES = "k-requirements,gng-gate";
199
224
  const DEFAULT_COVERAGE_THRESHOLD = 90;
200
225
 
201
226
  function parseSuiteIds(raw: string): SuiteIdValue[] {
202
- const ids = raw.split(',').filter(Boolean);
227
+ const ids = raw.split(",").filter(Boolean);
203
228
  for (const id of ids) {
204
229
  if (!VALID_SUITE_IDS.includes(id as SuiteIdValue)) {
205
- throw new Error(`Invalid suite ID: '${id}'. Valid values: ${VALID_SUITE_IDS.join(', ')}`);
230
+ throw new Error(`Invalid suite ID: '${id}'. Valid values: ${VALID_SUITE_IDS.join(", ")}`);
206
231
  }
207
232
  }
208
233
  return ids as SuiteIdValue[];
@@ -216,26 +241,24 @@ function parseCoverageThreshold(raw: string | undefined): number {
216
241
  return n;
217
242
  }
218
243
 
219
- type InitPhasePreset = 'full' | 'standard' | 'minimal' | 'custom';
244
+ type InitPhasePreset = "full" | "standard" | "minimal" | "custom";
220
245
 
221
246
  function parseInitPhasePreset(value: string | undefined): InitPhasePreset | undefined {
222
247
  if (value === undefined) return undefined;
223
- if (value === 'full' || value === 'standard' || value === 'minimal' || value === 'custom') {
248
+ if (value === "full" || value === "standard" || value === "minimal" || value === "custom") {
224
249
  return value;
225
250
  }
226
251
  return undefined;
227
252
  }
228
253
 
229
- type RuleSeverity = 'error' | 'warning' | 'off';
254
+ type RuleSeverity = "error" | "warning" | "off";
230
255
 
231
256
  function toRuleSeverity(value: string): RuleSeverity {
232
- if (value === 'error' || value === 'warning' || value === 'off') return value;
233
- return 'error';
257
+ if (value === "error" || value === "warning" || value === "off") return value;
258
+ return "error";
234
259
  }
235
260
 
236
- function toRuleSeverityMap(
237
- rules: Record<string, string>,
238
- ): Record<string, RuleSeverity> {
261
+ function toRuleSeverityMap(rules: Record<string, string>): Record<string, RuleSeverity> {
239
262
  const result: Record<string, RuleSeverity> = {};
240
263
  for (const [key, value] of Object.entries(rules)) {
241
264
  result[key] = toRuleSeverity(value);
@@ -253,17 +276,30 @@ function toL1Config(resolvedConfig: HarnessConfigV2) {
253
276
  };
254
277
  }
255
278
 
279
+ /**
280
+ * HarnessConfigV2 (resolved) から biome-ast-engine が期待する architecture 情報を抽出する。
281
+ * architecture が未設定の場合は undefined を返し、biome-ast-engine 側の default (clean) に委ねる。
282
+ */
283
+ function toArchitectureInput(resolvedConfig: HarnessConfigV2) {
284
+ if (!resolvedConfig.architecture) {
285
+ return undefined;
286
+ }
287
+ return {
288
+ preset: resolvedConfig.architecture.preset,
289
+ layers: resolvedConfig.architecture.layers,
290
+ allowedDependencies: resolvedConfig.architecture.allowedDependencies,
291
+ };
292
+ }
293
+
256
294
  /**
257
295
  * phasegate.config.json を直接読み、storyReflection 設定解決用の provider を返す。
258
296
  * config-foundation の HarnessConfigV2 は storyReflection 未サポートのため raw JSON 経由。
259
297
  */
260
- async function loadStoryReflectionProvider(
261
- rootDir: string,
262
- ): Promise<HarnessConfigPhaseConfigProvider | null> {
263
- const configPath = join(rootDir, 'phasegate.config.json');
298
+ async function loadStoryReflectionProvider(rootDir: string): Promise<HarnessConfigPhaseConfigProvider | null> {
299
+ const configPath = join(rootDir, "phasegate.config.json");
264
300
  let raw: {
265
301
  phaseDependencies?: {
266
- preset?: 'default' | 'full' | 'standard' | 'minimal' | 'custom';
302
+ preset?: "default" | "full" | "standard" | "minimal" | "custom";
267
303
  override?: boolean;
268
304
  storyReflection?: {
269
305
  enabled?: boolean;
@@ -274,9 +310,9 @@ async function loadStoryReflectionProvider(
274
310
  };
275
311
  let content: string;
276
312
  try {
277
- content = await fsReadFile(configPath, 'utf8');
313
+ content = await fsReadFile(configPath, "utf8");
278
314
  } catch (error) {
279
- if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
315
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") {
280
316
  return null;
281
317
  }
282
318
  const message = error instanceof Error ? error.message : String(error);
@@ -300,14 +336,11 @@ async function loadStoryReflectionProvider(
300
336
  };
301
337
  return new HarnessConfigPhaseConfigProvider({
302
338
  config: section,
303
- defaultOutputDir: raw.reporting?.outputDir ?? '.harness/reports',
339
+ defaultOutputDir: raw.reporting?.outputDir ?? ".harness/reports",
304
340
  });
305
341
  }
306
342
 
307
- async function printStoryReflectionValidationSummary(
308
- rootDir: string,
309
- unit: string | undefined,
310
- ): Promise<void> {
343
+ async function printStoryReflectionValidationSummary(rootDir: string, unit: string | undefined): Promise<void> {
311
344
  const provider = await loadStoryReflectionProvider(rootDir);
312
345
  if (provider === null) return;
313
346
  const config = await provider.getStoryReflectionConfig();
@@ -320,9 +353,7 @@ async function printStoryReflectionValidationSummary(
320
353
  const useCase = new CheckStoryReflectionUseCase({ checker });
321
354
  result = await useCase.execute({ unitId: unit, config });
322
355
  }
323
- console.log(
324
- presenter.formatValidationSummary({ config, preset: policy.preset, result }),
325
- );
356
+ console.log(presenter.formatValidationSummary({ config, preset: policy.preset, result }));
326
357
  }
327
358
 
328
359
  async function printStoryReflectionStatusLine(rootDir: string): Promise<void> {
@@ -334,10 +365,29 @@ async function printStoryReflectionStatusLine(rootDir: string): Promise<void> {
334
365
  console.log(presenter.formatStatusLine({ config, preset: policy.preset }));
335
366
  }
336
367
 
368
+ let v2SchemaWarningEmitted = false;
369
+
370
+ function emitV2SchemaWarningOnce(sourcePath: string): void {
371
+ if (v2SchemaWarningEmitted) return;
372
+ v2SchemaWarningEmitted = true;
373
+ process.stderr.write(
374
+ [
375
+ `Warning: ${sourcePath} は v2 schema(architecture キー無し)として検出されました。`,
376
+ " v0.86.0 以降は architecture.preset による層構造の明示を推奨しています。",
377
+ " 自動 upgrade: npx phasegate migrate --schema v3",
378
+ " 詳細: docs/guide/preset-selection.md",
379
+ "",
380
+ ].join("\n"),
381
+ );
382
+ }
383
+
337
384
  async function loadResolvedConfig(): Promise<HarnessConfigV2 | undefined> {
338
385
  try {
339
386
  const configModule = createConfigFoundationModule();
340
387
  const result = await configModule.usecases.loadResolvedConfigUseCase.execute();
388
+ if (result.schemaVersion === "v2") {
389
+ emitV2SchemaWarningOnce(result.sourcePath);
390
+ }
341
391
  return result.config;
342
392
  } catch (error) {
343
393
  if (error instanceof ConfigValidationError) {
@@ -361,7 +411,7 @@ async function main(): Promise<void> {
361
411
  const args = process.argv.slice(2);
362
412
  const command = args[0];
363
413
 
364
- if (!command || command === '--help' || command === 'help') {
414
+ if (!command || command === "--help" || command === "help") {
365
415
  printUsage();
366
416
  process.exit(0);
367
417
  }
@@ -369,13 +419,13 @@ async function main(): Promise<void> {
369
419
  const rootDir = getProjectRoot();
370
420
  const harnessRoot = getHarnessRoot();
371
421
 
372
- if (command === '--version' || command === 'version') {
422
+ if (command === "--version" || command === "version") {
373
423
  const version = await getHarnessVersion(harnessRoot);
374
424
  console.log(`phasegate v${version}`);
375
425
  process.exit(0);
376
426
  }
377
427
 
378
- const json = hasFlag(args, '--json');
428
+ const json = hasFlag(args, "--json");
379
429
 
380
430
  // Cross-unit wiring: 設定を先に解決し、各Unit に注入する
381
431
  const resolvedConfig = await loadResolvedConfig();
@@ -383,48 +433,51 @@ async function main(): Promise<void> {
383
433
  try {
384
434
  switch (command) {
385
435
  // ── harness setup ──
386
- case 'init': {
387
- const projectName = parseFlag(args, '--name') ?? 'my-project';
388
- const rawPhasePreset = parseFlag(args, '--preset');
436
+ case "init": {
437
+ const projectName = parseFlag(args, "--name") ?? "my-project";
438
+ const rawPhasePreset = parseFlag(args, "--preset");
389
439
  if (
390
- rawPhasePreset !== undefined
391
- && rawPhasePreset !== 'full'
392
- && rawPhasePreset !== 'standard'
393
- && rawPhasePreset !== 'minimal'
394
- && rawPhasePreset !== 'custom'
440
+ rawPhasePreset !== undefined &&
441
+ rawPhasePreset !== "full" &&
442
+ rawPhasePreset !== "standard" &&
443
+ rawPhasePreset !== "minimal" &&
444
+ rawPhasePreset !== "custom"
395
445
  ) {
396
446
  console.error(`Invalid --preset value: "${rawPhasePreset}". Use "full", "standard", "minimal", or "custom".`);
397
447
  process.exit(2);
398
448
  }
399
449
  const phasePreset = parseInitPhasePreset(rawPhasePreset);
400
- const skillSetRaw = parseFlag(args, '--skills') ?? 'all';
401
- if (skillSetRaw !== 'core' && skillSetRaw !== 'all') {
450
+ const skillSetRaw = parseFlag(args, "--skills") ?? "all";
451
+ if (skillSetRaw !== "core" && skillSetRaw !== "all") {
402
452
  console.error(`Invalid --skills value: "${skillSetRaw}". Use "core" or "all".`);
403
453
  process.exit(2);
404
454
  }
405
455
  const skillSet: SkillSet = skillSetRaw;
406
- const agentRaw = parseFlag(args, '--agent') ?? 'claude';
407
- if (agentRaw !== 'claude' && agentRaw !== 'codex' && agentRaw !== 'both') {
456
+ const agentRaw = parseFlag(args, "--agent") ?? "claude";
457
+ if (agentRaw !== "claude" && agentRaw !== "codex" && agentRaw !== "both") {
408
458
  console.error(`Invalid --agent value: "${agentRaw}". Use "claude", "codex", or "both".`);
409
459
  process.exit(2);
410
460
  }
411
461
  const agent = agentRaw;
412
- const deployClaude = agent === 'claude' || agent === 'both';
413
- const deployCodex = agent === 'codex' || agent === 'both';
462
+ const deployClaude = agent === "claude" || agent === "both";
463
+ const deployCodex = agent === "codex" || agent === "both";
414
464
  const result = await deploySkills(harnessRoot, rootDir, skillSet);
465
+ const skillLinkResult = await deployAgentSkillLinks(rootDir, {
466
+ claude: deployClaude,
467
+ codex: deployCodex,
468
+ });
415
469
  const configResult = await initHarnessConfig(rootDir, projectName, phasePreset);
416
470
  const hooksResult = deployClaude
417
471
  ? await deployHookScripts(harnessRoot, rootDir)
418
472
  : { scriptsDeployed: 0, settingsCreated: false };
419
- const codexResult = deployCodex
420
- ? await deployCodexHooks(harnessRoot, rootDir)
421
- : null;
473
+ const codexResult = deployCodex ? await deployCodexHooks(harnessRoot, rootDir) : null;
422
474
  const designDocsResult = await deployDesignDocs(harnessRoot, rootDir);
423
- const withHusky = hasFlag(args, '--with-husky');
424
- const huskyResult = withHusky
425
- ? await deployHuskyHook(harnessRoot, rootDir)
426
- : null;
427
- console.log(`✓ Skills deployed to ${result.targetDir} (${result.deployedSkills.length} skills, set: ${skillSet})`);
475
+ const withHusky = hasFlag(args, "--with-husky");
476
+ const huskyResult = withHusky ? await deployHuskyHook(harnessRoot, rootDir) : null;
477
+ const huskyCommitMsgResult = withHusky ? await deployHuskyCommitMsgHook(harnessRoot, rootDir) : null;
478
+ console.log(
479
+ `✓ Skills deployed to ${result.targetDir} (${result.deployedSkills.length} skills, set: ${skillSet})`,
480
+ );
428
481
  if (configResult.created) {
429
482
  console.log(`✓ phasegate.config.json created`);
430
483
  } else {
@@ -438,6 +491,13 @@ async function main(): Promise<void> {
438
491
  } else if (hooksResult.scriptsDeployed > 0) {
439
492
  console.log(` .claude/settings.json already exists, skipped`);
440
493
  }
494
+ if (skillLinkResult.claude !== null) {
495
+ if (skillLinkResult.claude.created) {
496
+ console.log(`✓ .claude/skills linked to skills/`);
497
+ } else {
498
+ console.log(` .claude/skills already exists, skipped`);
499
+ }
500
+ }
441
501
  if (codexResult !== null) {
442
502
  if (codexResult.created) {
443
503
  console.log(`✓ .codex/hooks.json deployed`);
@@ -445,6 +505,13 @@ async function main(): Promise<void> {
445
505
  console.log(` .codex/hooks.json already exists, skipped`);
446
506
  }
447
507
  }
508
+ if (skillLinkResult.codex !== null) {
509
+ if (skillLinkResult.codex.created) {
510
+ console.log(`✓ .codex/skills linked to skills/`);
511
+ } else {
512
+ console.log(` .codex/skills already exists, skipped`);
513
+ }
514
+ }
448
515
  if (designDocsResult.copiedFiles.length > 0) {
449
516
  console.log(`✓ Design docs deployed (${designDocsResult.copiedFiles.length} files)`);
450
517
  }
@@ -458,53 +525,71 @@ async function main(): Promise<void> {
458
525
  console.log(` .husky/pre-commit already exists, skipped`);
459
526
  }
460
527
  }
528
+ if (huskyCommitMsgResult !== null) {
529
+ if (huskyCommitMsgResult.created) {
530
+ console.log(`✓ .husky/commit-msg deployed`);
531
+ } else {
532
+ console.log(` .husky/commit-msg already exists, skipped`);
533
+ }
534
+ }
461
535
  console.log(`✓ Harness v${result.version} initialized (agent: ${agent})`);
462
- console.log('');
463
- console.log('Next steps:');
464
- if (skillSet === 'core') {
465
- console.log(' 1. Core skills only — quality defense tools are ready');
536
+ console.log("");
537
+ console.log("Next steps:");
538
+ if (skillSet === "core") {
539
+ console.log(" 1. Core skills only — quality defense tools are ready");
466
540
  } else {
467
- console.log(' 1. Run the product-architect skill to start AIDLC');
541
+ console.log(" 1. Run the product-architect skill to start AIDLC");
468
542
  }
469
- console.log(' 2. Customize phasegate.config.json if needed');
543
+ console.log(" 2. Customize phasegate.config.json if needed");
470
544
  if (deployClaude) {
471
- console.log(' 3. Edit .claude/scripts/hook-config.json to set target directories');
545
+ console.log(" 3. Edit .claude/scripts/hook-config.json to set target directories");
472
546
  }
473
547
  if (deployCodex) {
474
- console.log(` ${deployClaude ? '4' : '3'}. Enable Codex hooks: codex features enable codex_hooks`);
475
- console.log(` ${deployClaude ? '5' : '4'}. (Recommended) Install pre-commit backstop: rerun with --with-husky or set up husky manually`);
548
+ console.log(` ${deployClaude ? "4" : "3"}. Enable Codex hooks: codex features enable codex_hooks`);
549
+ console.log(
550
+ ` ${deployClaude ? "5" : "4"}. (Recommended) Install pre-commit backstop: rerun with --with-husky or set up husky manually`,
551
+ );
476
552
  console.log(` See docs/guide/codex-integration.md for the native apply_patch limitation.`);
477
553
  }
478
554
  process.exit(0);
479
555
  break;
480
556
  }
481
557
 
482
- case 'update-skills': {
558
+ case "update-skills": {
483
559
  const deployed = await getDeployedVersion(rootDir);
484
560
  const current = await getHarnessVersion(harnessRoot);
485
- const previousSkillSet: SkillSet = deployed?.skillSet ?? 'all';
486
- const overrideSkillSet = parseFlag(args, '--skills');
487
- const updateSkillSet: SkillSet = overrideSkillSet === 'core' || overrideSkillSet === 'all'
488
- ? overrideSkillSet
489
- : previousSkillSet;
561
+ const previousSkillSet: SkillSet = deployed?.skillSet ?? "all";
562
+ const overrideSkillSet = parseFlag(args, "--skills");
563
+ const updateSkillSet: SkillSet =
564
+ overrideSkillSet === "core" || overrideSkillSet === "all" ? overrideSkillSet : previousSkillSet;
490
565
  if (deployed) {
491
566
  console.log(`Previously deployed: v${deployed.version} (${deployed.deployedAt}, set: ${previousSkillSet})`);
492
567
  } else {
493
- console.log('No previously deployed skills found');
568
+ console.log("No previously deployed skills found");
494
569
  }
495
570
  console.log(`Current harness version: v${current}`);
496
571
  const result = await deploySkills(harnessRoot, rootDir, updateSkillSet);
572
+ const shouldLinkClaude =
573
+ (await pathExists(join(rootDir, ".claude", "settings.json"))) ||
574
+ (await pathExists(join(rootDir, ".claude", "skills")));
575
+ const shouldLinkCodex =
576
+ (await pathExists(join(rootDir, ".codex", "hooks.json"))) ||
577
+ (await pathExists(join(rootDir, ".codex", "skills")));
578
+ await deployAgentSkillLinks(rootDir, {
579
+ claude: shouldLinkClaude,
580
+ codex: shouldLinkCodex,
581
+ });
497
582
  console.log(`✓ Skills updated (${result.deployedSkills.length} skills redeployed, set: ${updateSkillSet})`);
498
583
  process.exit(0);
499
584
  break;
500
585
  }
501
586
 
502
587
  // ── config-foundation ──
503
- case 'enable-feature': {
588
+ case "enable-feature": {
504
589
  const mod = createConfigFoundationModule();
505
590
  const featureName = args[1];
506
- const list = hasFlag(args, '--list');
507
- const configPath = parseFlag(args, '--config');
591
+ const list = hasFlag(args, "--list");
592
+ const configPath = parseFlag(args, "--config");
508
593
  const result = await mod.handlers.enableFeatureCommandHandler.execute({
509
594
  featureName,
510
595
  list,
@@ -515,11 +600,11 @@ async function main(): Promise<void> {
515
600
  break;
516
601
  }
517
602
 
518
- case 'disable-feature': {
603
+ case "disable-feature": {
519
604
  const mod = createConfigFoundationModule();
520
605
  const featureName = args[1];
521
- const list = hasFlag(args, '--list');
522
- const configPath = parseFlag(args, '--config');
606
+ const list = hasFlag(args, "--list");
607
+ const configPath = parseFlag(args, "--config");
523
608
  const result = await mod.handlers.disableFeatureCommandHandler.execute({
524
609
  featureName,
525
610
  list,
@@ -530,7 +615,7 @@ async function main(): Promise<void> {
530
615
  break;
531
616
  }
532
617
 
533
- case 'list-features': {
618
+ case "list-features": {
534
619
  const mod = createConfigFoundationModule();
535
620
  const result = await mod.handlers.enableFeatureCommandHandler.execute({
536
621
  list: true,
@@ -540,11 +625,35 @@ async function main(): Promise<void> {
540
625
  break;
541
626
  }
542
627
 
628
+ case "migrate": {
629
+ if (args[1] === "work-items") {
630
+ const mod = createTraceabilityModelModule(rootDir);
631
+ const result = await mod.migrateWorkItemsCommandHandler.execute({
632
+ dryRun: hasFlag(args, "--dry-run"),
633
+ apply: hasFlag(args, "--apply"),
634
+ json,
635
+ });
636
+ console.log(result.text);
637
+ process.exit(result.exitCode);
638
+ break;
639
+ }
640
+ const mod = createConfigFoundationModule();
641
+ const targetVersion = parseFlag(args, "--schema") ?? "v3";
642
+ const configPath = parseFlag(args, "--config");
643
+ const result = await mod.handlers.migrateSchemaCommandHandler.execute({
644
+ targetVersion,
645
+ configPath,
646
+ });
647
+ console.log(result.output);
648
+ process.exit(result.exitCode);
649
+ break;
650
+ }
651
+
543
652
  // ── harness-error ──
544
- case 'render-errors': {
653
+ case "render-errors": {
545
654
  const mod = createHarnessErrorModule(rootDir);
546
- const format = toRenderFormat(parseFlag(args, '--format') ?? 'human');
547
- const failOnError = hasFlag(args, '--fail-on-error');
655
+ const format = toRenderFormat(parseFlag(args, "--format") ?? "human");
656
+ const failOnError = hasFlag(args, "--fail-on-error");
548
657
  const result = mod.renderHarnessErrorsHandler.execute({
549
658
  errors: [],
550
659
  format,
@@ -555,11 +664,11 @@ async function main(): Promise<void> {
555
664
  break;
556
665
  }
557
666
 
558
- case 'validate-fix': {
667
+ case "validate-fix": {
559
668
  const mod = createHarnessErrorModule(rootDir);
560
- const code = parseFlag(args, '--code');
561
- const failFast = hasFlag(args, '--fail-fast');
562
- const format = toListFormat(parseFlag(args, '--format') ?? 'human');
669
+ const code = parseFlag(args, "--code");
670
+ const failFast = hasFlag(args, "--fail-fast");
671
+ const format = toListFormat(parseFlag(args, "--format") ?? "human");
563
672
  const result = await mod.validateFixExampleHandler.execute({
564
673
  code,
565
674
  failFast,
@@ -570,10 +679,10 @@ async function main(): Promise<void> {
570
679
  break;
571
680
  }
572
681
 
573
- case 'list-errors': {
682
+ case "list-errors": {
574
683
  const mod = createHarnessErrorModule(rootDir);
575
- const format = toListFormat(parseFlag(args, '--format') ?? 'human');
576
- const layer = toLayerFilter(parseFlag(args, '--layer'));
684
+ const format = toListFormat(parseFlag(args, "--format") ?? "human");
685
+ const layer = toLayerFilter(parseFlag(args, "--layer"));
577
686
  const result = await mod.listErrorDefinitionsHandler.execute({
578
687
  format,
579
688
  layer,
@@ -584,7 +693,7 @@ async function main(): Promise<void> {
584
693
  }
585
694
 
586
695
  // ── traceability-model ──
587
- case 'validate-metadata': {
696
+ case "validate-metadata": {
588
697
  const mod = createTraceabilityModelModule(rootDir);
589
698
  const filePaths = parsePositionalArgs(args.slice(1));
590
699
  const result = await mod.validateMetadataCommandHandler.execute({
@@ -597,37 +706,34 @@ async function main(): Promise<void> {
597
706
  }
598
707
 
599
708
  // ── phase-dependency-model ──
600
- case 'check-phase-gate': {
601
- const phaseConfig = resolvedConfig
602
- ? toPhaseConfigSection(resolvedConfig)
603
- : undefined;
709
+ case "check-phase-gate": {
710
+ const phaseConfig = resolvedConfig ? toPhaseConfigSection(resolvedConfig) : undefined;
604
711
  const reportOutputDir = resolvedConfig?.reporting.outputDir;
605
712
  const mod = createPhaseDependencyModelModule({
606
713
  rootDir,
607
714
  phaseConfig,
608
715
  reportOutputDir,
609
716
  });
610
- const level = Number(parseFlag(args, '--level') ?? '1');
611
- const unitId = parseFlag(args, '--unit');
612
- const storyId = parseFlag(args, '--story');
613
- const targetFilePath = parseFlag(args, '--target-file');
614
- const result =
615
- await mod.checkPhaseGateCommandHandler.execute({
616
- targetLevel: level,
617
- unitId,
618
- storyId,
619
- targetFilePath,
620
- json,
621
- });
717
+ const level = Number(parseFlag(args, "--level") ?? "1");
718
+ const unitId = parseFlag(args, "--unit");
719
+ const storyId = parseFlag(args, "--story");
720
+ const targetFilePath = parseFlag(args, "--target-file");
721
+ const result = await mod.checkPhaseGateCommandHandler.execute({
722
+ targetLevel: level,
723
+ unitId,
724
+ storyId,
725
+ targetFilePath,
726
+ json,
727
+ });
622
728
  console.log(result.text);
623
729
  process.exit(result.exitCode);
624
730
  break;
625
731
  }
626
732
 
627
733
  // ── adr-foundation ──
628
- case 'list-adrs': {
734
+ case "list-adrs": {
629
735
  const mod = createAdrFoundationModule(rootDir);
630
- const statuses = toAdrStatuses(parseFlag(args, '--status'));
736
+ const statuses = toAdrStatuses(parseFlag(args, "--status"));
631
737
  const result = await mod.listAdrsCommandHandler.execute({
632
738
  statuses,
633
739
  json,
@@ -637,12 +743,10 @@ async function main(): Promise<void> {
637
743
  break;
638
744
  }
639
745
 
640
- case 'validate-adr': {
746
+ case "validate-adr": {
641
747
  const mod = createAdrFoundationModule(rootDir);
642
- const all = hasFlag(args, '--all');
643
- const adrRef = args.find(
644
- (a) => !a.startsWith('--') && a !== command,
645
- );
748
+ const all = hasFlag(args, "--all");
749
+ const adrRef = args.find((a) => !a.startsWith("--") && a !== command);
646
750
  const result = await mod.validateAdrCommandHandler.execute({
647
751
  adrRef,
648
752
  all,
@@ -654,34 +758,26 @@ async function main(): Promise<void> {
654
758
  }
655
759
 
656
760
  // ── biome-ast-engine ──
657
- case 'lint': {
658
- const l1Config = resolvedConfig
659
- ? toL1Config(resolvedConfig)
660
- : undefined;
661
- const mod = createBiomeAstEngineModule(rootDir, { l1Config });
662
- const result = await mod.harnessLintCommandHandler.execute(
663
- args.slice(1),
664
- );
761
+ case "lint": {
762
+ const l1Config = resolvedConfig ? toL1Config(resolvedConfig) : undefined;
763
+ const architecture = resolvedConfig ? toArchitectureInput(resolvedConfig) : undefined;
764
+ const mod = createBiomeAstEngineModule(rootDir, { l1Config, architecture });
765
+ const result = await mod.harnessLintCommandHandler.execute(args.slice(1));
665
766
  console.log(result.text);
666
767
  process.exit(result.exitCode);
667
768
  break;
668
769
  }
669
770
 
670
771
  // ── validator-system ──
671
- case 'validate': {
772
+ case "validate": {
672
773
  const mod = createValidatorSystemModule();
673
- const layer = parseFlag(args, '--layer') as 'L0' | 'L2' | 'L3' | 'L4' | 'all' | undefined;
674
- const unit = parseFlag(args, '--unit');
675
- const phase = parseFlag(args, '--phase');
676
- const format = parseFlag(args, '--format') as 'human' | 'agent' | 'ci' | undefined;
677
- const failOnWarning = hasFlag(args, '--fail-on-warning');
678
- const noL4 = hasFlag(args, '--no-l4');
679
- const targetPaths = parsePositionalArgs(args.slice(1), [
680
- '--layer',
681
- '--unit',
682
- '--phase',
683
- '--format',
684
- ]);
774
+ const layer = parseFlag(args, "--layer") as "L0" | "L2" | "L3" | "L4" | "all" | undefined;
775
+ const unit = parseFlag(args, "--unit");
776
+ const phase = parseFlag(args, "--phase");
777
+ const format = parseFlag(args, "--format") as "human" | "agent" | "ci" | undefined;
778
+ const failOnWarning = hasFlag(args, "--fail-on-warning");
779
+ const noL4 = hasFlag(args, "--no-l4");
780
+ const targetPaths = parsePositionalArgs(args.slice(1), ["--layer", "--unit", "--phase", "--format"]);
685
781
  const result = await mod.handlers.runValidators.execute({
686
782
  layer,
687
783
  unit,
@@ -692,7 +788,7 @@ async function main(): Promise<void> {
692
788
  targetPaths,
693
789
  });
694
790
  console.log(result.output);
695
- if (layer === 'L2' || layer === 'all') {
791
+ if (layer === "L2" || layer === "all") {
696
792
  await printStoryReflectionValidationSummary(rootDir, unit);
697
793
  }
698
794
  process.exit(result.exitCode);
@@ -700,14 +796,14 @@ async function main(): Promise<void> {
700
796
  }
701
797
 
702
798
  // ── quick-mode / ci-check ──
703
- case 'ci-check': {
704
- const quick = hasFlag(args, '--quick');
799
+ case "ci-check": {
800
+ const quick = hasFlag(args, "--quick");
705
801
  if (quick) {
706
802
  const mod = createQuickModeCompositionRoot();
707
- const failOnReject = hasFlag(args, '--fail-on-reject');
708
- const dryRun = hasFlag(args, '--dry-run');
709
- const files = parseFlag(args, '--files');
710
- const format = parseFlag(args, '--format') as 'human' | 'json' | 'agent' | undefined;
803
+ const failOnReject = hasFlag(args, "--fail-on-reject");
804
+ const dryRun = hasFlag(args, "--dry-run");
805
+ const files = parseFlag(args, "--files");
806
+ const format = parseFlag(args, "--format") as "human" | "json" | "agent" | undefined;
711
807
  await mod.handler.handle({ quick: true, failOnReject, dryRun, files, format });
712
808
  } else {
713
809
  const mod = createHarnessApiModule();
@@ -719,31 +815,33 @@ async function main(): Promise<void> {
719
815
  }
720
816
 
721
817
  // ── quick-mode / check-change-category (H10-05) ──
722
- case 'check-change-category': {
723
- if (hasFlag(args, '--help')) {
724
- process.stdout.write([
725
- 'Usage: phasegate check-change-category --paths <csv> [options]',
726
- '',
727
- 'Classify changed file paths into quick-mode categories and report',
728
- 'whether Full Mode is required.',
729
- '',
730
- 'Options:',
731
- ' --paths <csv> Comma-separated file paths to classify.',
732
- ' --format <human|json> Output format. Default: human.',
733
- ' --fail-on-full-required Exit with code 1 when Full Mode is required.',
734
- ' --help Show this help.',
735
- '',
736
- 'Examples:',
737
- ' phasegate check-change-category --paths src/foo.ts,src/bar.ts',
738
- ' phasegate check-change-category --paths src/foo.ts --format json',
739
- '',
740
- ].join('\n'));
818
+ case "check-change-category": {
819
+ if (hasFlag(args, "--help")) {
820
+ process.stdout.write(
821
+ [
822
+ "Usage: phasegate check-change-category --paths <csv> [options]",
823
+ "",
824
+ "Classify changed file paths into quick-mode categories and report",
825
+ "whether Full Mode is required.",
826
+ "",
827
+ "Options:",
828
+ " --paths <csv> Comma-separated file paths to classify.",
829
+ " --format <human|json> Output format. Default: human.",
830
+ " --fail-on-full-required Exit with code 1 when Full Mode is required.",
831
+ " --help Show this help.",
832
+ "",
833
+ "Examples:",
834
+ " phasegate check-change-category --paths src/foo.ts,src/bar.ts",
835
+ " phasegate check-change-category --paths src/foo.ts --format json",
836
+ "",
837
+ ].join("\n"),
838
+ );
741
839
  return;
742
840
  }
743
841
  const mod = createQuickModeCompositionRoot();
744
- const paths = parseFlag(args, '--paths');
745
- const format = parseFlag(args, '--format') as 'human' | 'json' | undefined;
746
- const failOnFullRequired = hasFlag(args, '--fail-on-full-required');
842
+ const paths = parseFlag(args, "--paths");
843
+ const format = parseFlag(args, "--format") as "human" | "json" | undefined;
844
+ const failOnFullRequired = hasFlag(args, "--fail-on-full-required");
747
845
  const result = await mod.checkChangeCategoryHandler.handle({
748
846
  paths,
749
847
  format,
@@ -754,7 +852,7 @@ async function main(): Promise<void> {
754
852
  }
755
853
 
756
854
  // ── harness-api ──
757
- case 'phasegate:check-ready': {
855
+ case "phasegate:check-ready": {
758
856
  const mod = createHarnessApiModule();
759
857
  const flags: Record<string, boolean | string> = {};
760
858
  if (json) flags.json = true;
@@ -762,37 +860,39 @@ async function main(): Promise<void> {
762
860
  break;
763
861
  }
764
862
 
765
- case 'phasegate:check-phase': {
863
+ case "phasegate:check-phase": {
766
864
  // ISSUE-005 P2-6: --help / --json を positional として食わないようにする
767
- if (hasFlag(args, '--help')) {
768
- process.stdout.write([
769
- 'Usage: phasegate phasegate:check-phase [options]',
770
- '',
771
- 'Check phase gate for a specific unit.',
772
- '',
773
- 'Options:',
774
- ' --unit <unitId> Target unit ID (e.g., harness-api). If omitted,',
775
- ' the first positional argument is used.',
776
- ' --json Output result as JSON.',
777
- ' --help Show this help.',
778
- '',
779
- 'Examples:',
780
- ' phasegate phasegate:check-phase --unit harness-api',
781
- ' phasegate phasegate:check-phase harness-api --json',
782
- '',
783
- ].join('\n'));
865
+ if (hasFlag(args, "--help")) {
866
+ process.stdout.write(
867
+ [
868
+ "Usage: phasegate phasegate:check-phase [options]",
869
+ "",
870
+ "Check phase gate for a specific unit.",
871
+ "",
872
+ "Options:",
873
+ " --unit <unitId> Target unit ID (e.g., harness-api). If omitted,",
874
+ " the first positional argument is used.",
875
+ " --json Output result as JSON.",
876
+ " --help Show this help.",
877
+ "",
878
+ "Examples:",
879
+ " phasegate phasegate:check-phase --unit harness-api",
880
+ " phasegate phasegate:check-phase harness-api --json",
881
+ "",
882
+ ].join("\n"),
883
+ );
784
884
  return;
785
885
  }
786
886
  const mod = createHarnessApiModule();
787
- const positional = args[1] && !args[1].startsWith('--') ? args[1] : undefined;
788
- const unit = parseFlag(args, '--unit') ?? positional ?? '';
887
+ const positional = args[1] && !args[1].startsWith("--") ? args[1] : undefined;
888
+ const unit = parseFlag(args, "--unit") ?? positional ?? "";
789
889
  const flags: Record<string, boolean | string> = {};
790
890
  if (json) flags.json = true;
791
891
  await mod.handlers.checkPhase.handle({ unit }, flags);
792
892
  break;
793
893
  }
794
894
 
795
- case 'phasegate:ci-check': {
895
+ case "phasegate:ci-check": {
796
896
  const mod = createHarnessApiModule();
797
897
  const flags: Record<string, boolean | string> = {};
798
898
  if (json) flags.json = true;
@@ -800,7 +900,7 @@ async function main(): Promise<void> {
800
900
  break;
801
901
  }
802
902
 
803
- case 'phasegate:detect-drift': {
903
+ case "phasegate:detect-drift": {
804
904
  const mod = createHarnessApiModule();
805
905
  const flags: Record<string, boolean | string> = {};
806
906
  if (json) flags.json = true;
@@ -808,7 +908,7 @@ async function main(): Promise<void> {
808
908
  break;
809
909
  }
810
910
 
811
- case 'phasegate:status': {
911
+ case "phasegate:status": {
812
912
  const mod = createHarnessApiModule();
813
913
  const flags: Record<string, boolean | string> = {};
814
914
  if (json) flags.json = true;
@@ -817,17 +917,17 @@ async function main(): Promise<void> {
817
917
  break;
818
918
  }
819
919
 
820
- case 'phasegate:lint': {
920
+ case "phasegate:lint": {
821
921
  const mod = createHarnessApiModule();
822
922
  const flags: Record<string, boolean | string> = {};
823
923
  if (json) flags.json = true;
824
- const target = parseFlag(args, '--target');
924
+ const target = parseFlag(args, "--target");
825
925
  if (target) flags.target = target;
826
926
  await mod.handlers.lint.handle({}, flags);
827
927
  break;
828
928
  }
829
929
 
830
- case 'phasegate:complete-check': {
930
+ case "phasegate:complete-check": {
831
931
  const mod = createHarnessApiModule();
832
932
  const flags: Record<string, boolean | string> = {};
833
933
  if (json) flags.json = true;
@@ -835,9 +935,9 @@ async function main(): Promise<void> {
835
935
  break;
836
936
  }
837
937
 
838
- case 'phasegate:impact-analysis': {
938
+ case "phasegate:impact-analysis": {
839
939
  const mod = createHarnessApiModule();
840
- const storyId = args[1] && !args[1].startsWith('--') ? args[1] : (parseFlag(args, '--story-id') ?? '');
940
+ const storyId = args[1] && !args[1].startsWith("--") ? args[1] : (parseFlag(args, "--story-id") ?? "");
841
941
  const flags: Record<string, boolean | string> = {};
842
942
  if (json) flags.json = true;
843
943
  await mod.handlers.impactAnalysis.handle({ storyId }, flags);
@@ -845,8 +945,8 @@ async function main(): Promise<void> {
845
945
  }
846
946
 
847
947
  // ── ci-governance ──
848
- case 'ci:generate-template': {
849
- if (hasFlag(args, '--help')) {
948
+ case "ci:generate-template": {
949
+ if (hasFlag(args, "--help")) {
850
950
  console.log(`Usage: phasegate ci:generate-template [options]
851
951
 
852
952
  Generates a CI template configuration.
@@ -866,47 +966,50 @@ Examples:
866
966
  process.exit(0);
867
967
  }
868
968
  const mod = buildCiGovernance(rootDir);
869
- const presetId = parseFlag(args, '--preset') ?? 'default';
870
- const templateType = parseFlag(args, '--type') ?? 'aidlc-gate';
871
- const render = hasFlag(args, '--render');
872
- const format = json ? 'json' : 'human';
969
+ const presetId = parseFlag(args, "--preset") ?? "default";
970
+ const templateType = parseFlag(args, "--type") ?? "aidlc-gate";
971
+ const render = hasFlag(args, "--render");
972
+ const format = json ? "json" : "human";
873
973
  const result = await mod.generateCiTemplateHandler.handle({ presetId, templateType, render, format });
874
974
  console.log(result.output);
875
975
  process.exit(result.exitCode);
876
976
  break;
877
977
  }
878
978
 
879
- case 'ci:migrate-agents-md': {
979
+ case "ci:migrate-agents-md": {
880
980
  const mod = buildCiGovernance(rootDir);
881
- const dryRun = hasFlag(args, '--dry-run');
882
- const validateOnly = hasFlag(args, '--validate-only');
883
- const format = json ? 'json' : 'human';
981
+ const dryRun = hasFlag(args, "--dry-run");
982
+ const validateOnly = hasFlag(args, "--validate-only");
983
+ const format = json ? "json" : "human";
884
984
  const result = await mod.migrateAgentsMdHandler.handle({ dryRun, validateOnly, format });
885
985
  console.log(result.output);
886
986
  process.exit(result.exitCode);
887
987
  break;
888
988
  }
889
989
 
890
- case 'ci:check-repetition': {
990
+ case "ci:check-repetition": {
891
991
  const mod = buildCiGovernance(rootDir);
892
- const errorCode = parseFlag(args, '--code') ?? '';
893
- const reset = hasFlag(args, '--reset');
894
- const format = json ? 'json' : 'human';
992
+ const errorCode = parseFlag(args, "--code") ?? "";
993
+ const reset = hasFlag(args, "--reset");
994
+ const format = json ? "json" : "human";
895
995
  const result = await mod.checkRepetitionHandler.handle({ errorCode, reset, format });
896
996
  console.log(result.output);
897
997
  process.exit(result.exitCode);
898
998
  break;
899
999
  }
900
1000
 
901
- case 'baseline': {
1001
+ case "baseline": {
902
1002
  const mod = buildCiGovernance(rootDir);
903
- const dryRun = hasFlag(args, '--dry-run');
904
- const force = hasFlag(args, '--force');
905
- const pathsFlag = parseFlag(args, '--paths');
1003
+ const dryRun = hasFlag(args, "--dry-run");
1004
+ const force = hasFlag(args, "--force");
1005
+ const pathsFlag = parseFlag(args, "--paths");
906
1006
  const include = pathsFlag
907
- ? pathsFlag.split(',').map((s) => s.trim()).filter(Boolean)
1007
+ ? pathsFlag
1008
+ .split(",")
1009
+ .map((s) => s.trim())
1010
+ .filter(Boolean)
908
1011
  : undefined;
909
- const format = json ? 'json' : 'human';
1012
+ const format = json ? "json" : "human";
910
1013
  const result = await mod.createBaselineHandler.handle({
911
1014
  include,
912
1015
  dryRun,
@@ -918,12 +1021,12 @@ Examples:
918
1021
  break;
919
1022
  }
920
1023
 
921
- case 'scaffold-design': {
1024
+ case "scaffold-design": {
922
1025
  const mod = buildCiGovernance(rootDir, harnessRoot);
923
- const unit = parseFlag(args, '--unit') ?? '';
924
- const phase = parseFlag(args, '--phase') ?? '';
925
- const force = hasFlag(args, '--force');
926
- const format = json ? 'json' : 'human';
1026
+ const unit = parseFlag(args, "--unit") ?? "";
1027
+ const phase = parseFlag(args, "--phase") ?? "";
1028
+ const force = hasFlag(args, "--force");
1029
+ const format = json ? "json" : "human";
927
1030
  const result = await mod.scaffoldDesignHandler.handle({
928
1031
  unit,
929
1032
  phase,
@@ -936,58 +1039,57 @@ Examples:
936
1039
  }
937
1040
 
938
1041
  // ── skill-quality ──
939
- case 'skill:execute-tdd-cycle': {
1042
+ case "skill:execute-tdd-cycle": {
940
1043
  const mod = createSkillQualityHandlers();
941
- const unit = parseFlag(args, '--unit') ?? '';
942
- const storyId = parseFlag(args, '--story') ?? '';
943
- const description = parseFlag(args, '--desc') ?? '';
944
- const phaseRaw = parseFlag(args, '--phase') ?? 'RED';
945
- const phase = (phaseRaw === 'RED' || phaseRaw === 'GREEN' || phaseRaw === 'REFACTOR')
946
- ? phaseRaw
947
- : 'RED' as const;
948
- const passed = hasFlag(args, '--passed');
1044
+ const unit = parseFlag(args, "--unit") ?? "";
1045
+ const storyId = parseFlag(args, "--story") ?? "";
1046
+ const description = parseFlag(args, "--desc") ?? "";
1047
+ const phaseRaw = parseFlag(args, "--phase") ?? "RED";
1048
+ const phase =
1049
+ phaseRaw === "RED" || phaseRaw === "GREEN" || phaseRaw === "REFACTOR" ? phaseRaw : ("RED" as const);
1050
+ const passed = hasFlag(args, "--passed");
949
1051
  const result = await mod.executeTddCycleHandler.handle({ unit, storyId, description, phase, passed });
950
1052
  console.log(result.message);
951
1053
  process.exit(result.exitCode);
952
1054
  break;
953
1055
  }
954
1056
 
955
- case 'skill:check-coverage': {
1057
+ case "skill:check-coverage": {
956
1058
  const mod = createSkillQualityHandlers();
957
- const storyId = parseFlag(args, '--story') ?? '';
958
- const format = json ? 'json' : 'human';
1059
+ const storyId = parseFlag(args, "--story") ?? "";
1060
+ const format = json ? "json" : "human";
959
1061
  const result = await mod.checkCoverageHandler.handle({ storyId, format });
960
1062
  console.log(result.message);
961
1063
  process.exit(result.exitCode);
962
1064
  break;
963
1065
  }
964
1066
 
965
- case 'skill:collect-lessons': {
1067
+ case "skill:collect-lessons": {
966
1068
  const mod = createSkillQualityHandlers();
967
- const storyId = parseFlag(args, '--story') ?? '';
968
- const sourcesRaw = parseFlag(args, '--sources') ?? '';
969
- const sources = sourcesRaw ? sourcesRaw.split(',') : [];
970
- const writeArtifact = hasFlag(args, '--write-artifact');
1069
+ const storyId = parseFlag(args, "--story") ?? "";
1070
+ const sourcesRaw = parseFlag(args, "--sources") ?? "";
1071
+ const sources = sourcesRaw ? sourcesRaw.split(",") : [];
1072
+ const writeArtifact = hasFlag(args, "--write-artifact");
971
1073
  const result = await mod.collectLessonsHandler.handle({ storyId, sources, writeArtifact });
972
1074
  console.log(result.message);
973
1075
  process.exit(result.exitCode);
974
1076
  break;
975
1077
  }
976
1078
 
977
- case 'skill:apply-cascade-update': {
1079
+ case "skill:apply-cascade-update": {
978
1080
  const mod = createSkillQualityHandlers();
979
- const storyId = parseFlag(args, '--story') ?? '';
980
- const dryRun = hasFlag(args, '--dry-run');
1081
+ const storyId = parseFlag(args, "--story") ?? "";
1082
+ const dryRun = hasFlag(args, "--dry-run");
981
1083
  const result = await mod.applyCascadeUpdateHandler.handle({ storyId, dryRun });
982
1084
  console.log(result.message);
983
1085
  process.exit(result.exitCode);
984
1086
  break;
985
1087
  }
986
1088
 
987
- case 'skill:validate-structure': {
1089
+ case "skill:validate-structure": {
988
1090
  const mod = createSkillQualityHandlers();
989
- const skillFile = parseFlag(args, '--file') ?? '';
990
- const format = json ? 'json' : 'human';
1091
+ const skillFile = parseFlag(args, "--file") ?? "";
1092
+ const format = json ? "json" : "human";
991
1093
  const result = await mod.validateSkillStructureHandler.handle({ skillFile, format });
992
1094
  console.log(result.message);
993
1095
  process.exit(result.exitCode);
@@ -995,60 +1097,70 @@ Examples:
995
1097
  }
996
1098
 
997
1099
  // ── regression-suite ──
998
- case 'regression:run-k-requirements': {
1100
+ case "regression:run-k-requirements": {
999
1101
  const mod = buildRegressionSuite(rootDir);
1000
1102
  const result = await mod.runKRequirementsRegressionUseCase.execute();
1001
- const output = json ? JSON.stringify(result, null, 2) : `K-Requirements: ${result.passedCount}/${result.totalCount} passed`;
1103
+ const output = json
1104
+ ? JSON.stringify(result, null, 2)
1105
+ : `K-Requirements: ${result.passedCount}/${result.totalCount} passed`;
1002
1106
  console.log(output);
1003
1107
  process.exit(result.failedCount > 0 ? 1 : 0);
1004
1108
  break;
1005
1109
  }
1006
1110
 
1007
- case 'regression:run-gng-gate': {
1111
+ case "regression:run-gng-gate": {
1008
1112
  const mod = buildRegressionSuite(rootDir);
1009
1113
  const result = await mod.runGngGateRegressionUseCase.execute();
1010
- const output = json ? JSON.stringify(result, null, 2) : `GnG Gate: ${result.passedCount}/${result.totalCount} passed`;
1114
+ const output = json
1115
+ ? JSON.stringify(result, null, 2)
1116
+ : `GnG Gate: ${result.passedCount}/${result.totalCount} passed`;
1011
1117
  console.log(output);
1012
1118
  process.exit(result.failedCount > 0 ? 1 : 0);
1013
1119
  break;
1014
1120
  }
1015
1121
 
1016
- case 'regression:run-agent-guard': {
1122
+ case "regression:run-agent-guard": {
1017
1123
  const mod = buildRegressionSuite(rootDir);
1018
1124
  const result = await mod.runAgentIndependenceGuardUseCase.execute();
1019
- const output = json ? JSON.stringify(result, null, 2) : `Agent Independence: ${result.passedCount}/${result.totalCount} passed`;
1125
+ const output = json
1126
+ ? JSON.stringify(result, null, 2)
1127
+ : `Agent Independence: ${result.passedCount}/${result.totalCount} passed`;
1020
1128
  console.log(output);
1021
1129
  process.exit(result.failedCount > 0 ? 1 : 0);
1022
1130
  break;
1023
1131
  }
1024
1132
 
1025
- case 'regression:run-k14-k15': {
1133
+ case "regression:run-k14-k15": {
1026
1134
  const mod = buildRegressionSuite(rootDir);
1027
1135
  const result = await mod.runK14K15RegressionUseCase.execute();
1028
- const output = json ? JSON.stringify(result, null, 2) : `K14/K15: ${result.passedCount}/${result.totalCount} passed`;
1136
+ const output = json
1137
+ ? JSON.stringify(result, null, 2)
1138
+ : `K14/K15: ${result.passedCount}/${result.totalCount} passed`;
1029
1139
  console.log(output);
1030
1140
  process.exit(result.failedCount > 0 ? 1 : 0);
1031
1141
  break;
1032
1142
  }
1033
1143
 
1034
- case 'regression:configure-ci-gate': {
1144
+ case "regression:configure-ci-gate": {
1035
1145
  const mod = buildRegressionSuite(rootDir);
1036
- const requiredSuiteIds = parseSuiteIds(parseFlag(args, '--suites') ?? DEFAULT_REGRESSION_SUITES);
1037
- const threshold = parseCoverageThreshold(parseFlag(args, '--threshold'));
1146
+ const requiredSuiteIds = parseSuiteIds(parseFlag(args, "--suites") ?? DEFAULT_REGRESSION_SUITES);
1147
+ const threshold = parseCoverageThreshold(parseFlag(args, "--threshold"));
1038
1148
  const result = await mod.configureCiGateUseCase.execute({
1039
1149
  requiredSuiteIds,
1040
1150
  coverageThreshold: threshold,
1041
- executionMode: 'sequential',
1151
+ executionMode: "sequential",
1042
1152
  });
1043
- const output = json ? JSON.stringify(result, null, 2) : `CI gate configured: suites=${result.requiredSuiteIds.join(',')}, threshold=${result.coverageThreshold}%`;
1153
+ const output = json
1154
+ ? JSON.stringify(result, null, 2)
1155
+ : `CI gate configured: suites=${result.requiredSuiteIds.join(",")}, threshold=${result.coverageThreshold}%`;
1044
1156
  console.log(output);
1045
1157
  process.exit(0);
1046
1158
  break;
1047
1159
  }
1048
1160
 
1049
- case 'regression:analyze-migration': {
1161
+ case "regression:analyze-migration": {
1050
1162
  const mod = buildRegressionSuite(rootDir);
1051
- const dryRun = !hasFlag(args, '--no-dry-run');
1163
+ const dryRun = !hasFlag(args, "--no-dry-run");
1052
1164
  const result = await mod.analyzeV0MigrationUseCase.execute({ dryRun });
1053
1165
  const output = json
1054
1166
  ? JSON.stringify(result, null, 2)
@@ -1058,9 +1170,9 @@ Examples:
1058
1170
  break;
1059
1171
  }
1060
1172
 
1061
- case 'regression:migrate-v0-tests': {
1173
+ case "regression:migrate-v0-tests": {
1062
1174
  const mod = buildRegressionSuite(rootDir);
1063
- const confirm = hasFlag(args, '--confirm');
1175
+ const confirm = hasFlag(args, "--confirm");
1064
1176
  const result = await mod.migrateV0TestsUseCase.execute({ confirmExecute: confirm });
1065
1177
  const output = json
1066
1178
  ? JSON.stringify(result, null, 2)
@@ -1071,7 +1183,7 @@ Examples:
1071
1183
  }
1072
1184
 
1073
1185
  // ── phase2-extensions ──
1074
- case 'p2:check-freshness': {
1186
+ case "p2:check-freshness": {
1075
1187
  const mod = buildPhase2Extensions(rootDir, resolvedConfig ?? undefined);
1076
1188
  const p2args = args.slice(1);
1077
1189
  const result = await mod.checkFreshnessHandler.handle(p2args);
@@ -1080,7 +1192,7 @@ Examples:
1080
1192
  break;
1081
1193
  }
1082
1194
 
1083
- case 'p2:validate-pointers': {
1195
+ case "p2:validate-pointers": {
1084
1196
  const mod = buildPhase2Extensions(rootDir, resolvedConfig ?? undefined);
1085
1197
  const p2args = args.slice(1);
1086
1198
  const result = await mod.validatePointersHandler.handle(p2args);
@@ -1089,7 +1201,7 @@ Examples:
1089
1201
  break;
1090
1202
  }
1091
1203
 
1092
- case 'p2:generate-e2e-template': {
1204
+ case "p2:generate-e2e-template": {
1093
1205
  const mod = buildPhase2Extensions(rootDir, resolvedConfig ?? undefined);
1094
1206
  const p2args = args.slice(1);
1095
1207
  const result = await mod.generateE2ETemplateHandler.handle(p2args);
@@ -1098,7 +1210,7 @@ Examples:
1098
1210
  break;
1099
1211
  }
1100
1212
 
1101
- case 'p2:check-initial-creation': {
1213
+ case "p2:check-initial-creation": {
1102
1214
  const mod = buildPhase2Extensions(rootDir, resolvedConfig ?? undefined);
1103
1215
  const p2args = args.slice(1);
1104
1216
  const result = await mod.checkInitialCreationExpirationHandler.handle(p2args);
@@ -1108,19 +1220,19 @@ Examples:
1108
1220
  }
1109
1221
 
1110
1222
  // ── agent integration / hooks ──
1111
- case 'hook': {
1223
+ case "hook": {
1112
1224
  const subCommand = args[1];
1113
- const usage = 'Usage: phasegate hook <pre-tool-use|post-tool-use|stop|session-start|user-prompt-submit>';
1225
+ const usage = "Usage: phasegate hook <pre-tool-use|post-tool-use|stop|session-start|user-prompt-submit>";
1114
1226
  if (!subCommand) {
1115
1227
  console.error(usage);
1116
1228
  process.exit(2);
1117
1229
  }
1118
1230
  const hookFileName: Record<string, string> = {
1119
- 'pre-tool-use': 'pre-tool-use-hook.js',
1120
- 'post-tool-use': 'post-tool-use-hook.js',
1121
- 'stop': 'stop-hook.js',
1122
- 'session-start': 'session-start-hook.js',
1123
- 'user-prompt-submit': 'user-prompt-submit-hook.js',
1231
+ "pre-tool-use": "pre-tool-use-hook.js",
1232
+ "post-tool-use": "post-tool-use-hook.js",
1233
+ stop: "stop-hook.js",
1234
+ "session-start": "session-start-hook.js",
1235
+ "user-prompt-submit": "user-prompt-submit-hook.js",
1124
1236
  };
1125
1237
  const fileName = hookFileName[subCommand];
1126
1238
  if (!fileName) {
@@ -1128,13 +1240,13 @@ Examples:
1128
1240
  console.error(usage);
1129
1241
  process.exit(2);
1130
1242
  }
1131
- const hookPath = join(harnessRoot, 'scripts/harness/agent-integration/presentation', fileName);
1243
+ const hookPath = join(harnessRoot, "scripts/harness/agent-integration/presentation", fileName);
1132
1244
  await import(hookPath);
1133
1245
  break;
1134
1246
  }
1135
1247
 
1136
- case 'pre-commit': {
1137
- const preCommitPath = join(harnessRoot, 'scripts/harness/integrations/pre-commit.js');
1248
+ case "pre-commit": {
1249
+ const preCommitPath = join(harnessRoot, "scripts/harness/integrations/pre-commit.js");
1138
1250
  const preCommitMod = (await import(preCommitPath)) as {
1139
1251
  runPreCommitCli: () => Promise<void>;
1140
1252
  };
@@ -1142,33 +1254,42 @@ Examples:
1142
1254
  break;
1143
1255
  }
1144
1256
 
1145
- case 'delegate-sonnet': {
1146
- const { spawn } = await import('node:child_process');
1147
- const scriptPath = join(harnessRoot, 'scripts/delegate-sonnet.sh');
1257
+ case "commit-msg": {
1258
+ const preCommitPath = join(harnessRoot, "scripts/harness/integrations/pre-commit.js");
1259
+ const preCommitMod = (await import(preCommitPath)) as {
1260
+ runCommitMsgCli: (commitMessagePath: string | undefined) => Promise<void>;
1261
+ };
1262
+ await preCommitMod.runCommitMsgCli(args[1]);
1263
+ break;
1264
+ }
1265
+
1266
+ case "delegate-sonnet": {
1267
+ const { spawn } = await import("node:child_process");
1268
+ const scriptPath = join(harnessRoot, "scripts/delegate-sonnet.sh");
1148
1269
  const forwardArgs = args.slice(1);
1149
- const child = spawn('bash', [scriptPath, ...forwardArgs], { stdio: 'inherit' });
1270
+ const child = spawn("bash", [scriptPath, ...forwardArgs], { stdio: "inherit" });
1150
1271
  await new Promise<void>((_, reject) => {
1151
- child.on('exit', (code) => {
1272
+ child.on("exit", (code) => {
1152
1273
  process.exit(code ?? 1);
1153
1274
  });
1154
- child.on('error', reject);
1275
+ child.on("error", reject);
1155
1276
  });
1156
1277
  break;
1157
1278
  }
1158
1279
 
1159
1280
  // ── skills ──
1160
- case 'skills': {
1281
+ case "skills": {
1161
1282
  const subCommand = args[1];
1162
- const skillsRoot = join(harnessRoot, 'skills');
1283
+ const skillsRoot = join(harnessRoot, "skills");
1163
1284
 
1164
- if (subCommand === 'list') {
1165
- const { promises: fs } = await import('node:fs');
1285
+ if (subCommand === "list") {
1286
+ const { promises: fs } = await import("node:fs");
1166
1287
  const entries = await fs.readdir(skillsRoot, { withFileTypes: true });
1167
1288
  const skills: string[] = [];
1168
1289
  for (const entry of entries) {
1169
1290
  if (entry.isDirectory()) {
1170
1291
  try {
1171
- await fs.access(join(skillsRoot, entry.name, 'SKILL.md'));
1292
+ await fs.access(join(skillsRoot, entry.name, "SKILL.md"));
1172
1293
  skills.push(entry.name);
1173
1294
  } catch {
1174
1295
  // skip directories without SKILL.md
@@ -1179,39 +1300,39 @@ Examples:
1179
1300
 
1180
1301
  const grouped: Record<string, string[]> = { core: [], aidlc: [], utility: [], unknown: [] };
1181
1302
  for (const name of skills) {
1182
- const cat = getCategoryForSkill(name) ?? 'unknown';
1303
+ const cat = getCategoryForSkill(name) ?? "unknown";
1183
1304
  grouped[cat].push(name);
1184
1305
  }
1185
1306
 
1186
1307
  console.log(`Available skills (${skills.length}):\n`);
1187
1308
 
1188
1309
  const labels: Record<string, string> = {
1189
- core: 'Core — Quality Defense',
1190
- aidlc: 'AIDLC — Development Workflow',
1191
- utility: 'Utility',
1310
+ core: "Core — Quality Defense",
1311
+ aidlc: "AIDLC — Development Workflow",
1312
+ utility: "Utility",
1192
1313
  };
1193
- for (const cat of ['core', 'aidlc', 'utility', 'unknown'] as const) {
1314
+ for (const cat of ["core", "aidlc", "utility", "unknown"] as const) {
1194
1315
  if (grouped[cat].length === 0) continue;
1195
- const label = labels[cat] ?? 'Other';
1316
+ const label = labels[cat] ?? "Other";
1196
1317
  console.log(` [${label}] (${grouped[cat].length})`);
1197
1318
  for (const name of grouped[cat]) {
1198
1319
  console.log(` /${name}`);
1199
1320
  }
1200
- console.log('');
1321
+ console.log("");
1201
1322
  }
1202
1323
  process.exit(0);
1203
1324
  }
1204
1325
 
1205
- if (subCommand === 'info') {
1326
+ if (subCommand === "info") {
1206
1327
  const skillName = args[2];
1207
1328
  if (!skillName) {
1208
- console.error('Usage: phasegate skills info <skill-name>');
1329
+ console.error("Usage: phasegate skills info <skill-name>");
1209
1330
  process.exit(2);
1210
1331
  }
1211
- const { promises: fs } = await import('node:fs');
1212
- const skillMdPath = join(skillsRoot, skillName, 'SKILL.md');
1332
+ const { promises: fs } = await import("node:fs");
1333
+ const skillMdPath = join(skillsRoot, skillName, "SKILL.md");
1213
1334
  try {
1214
- const content = await fs.readFile(skillMdPath, 'utf-8');
1335
+ const content = await fs.readFile(skillMdPath, "utf-8");
1215
1336
  console.log(content);
1216
1337
  process.exit(0);
1217
1338
  } catch {
@@ -1221,7 +1342,7 @@ Examples:
1221
1342
  }
1222
1343
  }
1223
1344
 
1224
- console.error('Usage: phasegate skills <list|info <name>>');
1345
+ console.error("Usage: phasegate skills <list|info <name>>");
1225
1346
  process.exit(2);
1226
1347
  break;
1227
1348
  }
@@ -1232,8 +1353,7 @@ Examples:
1232
1353
  process.exit(2);
1233
1354
  }
1234
1355
  } catch (error: unknown) {
1235
- const message =
1236
- error instanceof Error ? error.message : 'Unknown error occurred';
1356
+ const message = error instanceof Error ? error.message : "Unknown error occurred";
1237
1357
  console.error(`Fatal: ${message}`);
1238
1358
  process.exit(2);
1239
1359
  }