phasegate 0.91.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.
- package/CHANGELOG.md +169 -0
- package/README.ja.md +15 -7
- package/README.md +24 -4
- package/docs/ADR/ADR-015-architecture-preset.md +183 -0
- package/docs/guide/codex-integration.md +7 -2
- package/docs/guide/installation.md +10 -2
- package/docs/guide/preset-selection.md +170 -0
- package/docs/guide/quick-vs-full-mode.md +3 -3
- package/docs/guide/retrofit-adoption.md +19 -2
- package/docs/guide/skills-overview.md +1 -1
- package/package.json +7 -1
- package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +108 -100
- package/scripts/harness/agent-integration/domain/ports/phase-gate-query-port.ts +3 -3
- package/scripts/harness/agent-integration/domain/value-objects/write-target-scope.ts +26 -30
- package/scripts/harness/agent-integration/infrastructure/adapters/file-system-story-reflection-query-adapter.ts +6 -2
- package/scripts/harness/biome-ast-engine/application/dto/analyze-import-graph-input.ts +3 -0
- package/scripts/harness/biome-ast-engine/application/dto/resolve-enabled-rules-output.ts +2 -0
- package/scripts/harness/biome-ast-engine/application/mappers/resolve-enabled-rules-output-mapper.ts +4 -1
- package/scripts/harness/biome-ast-engine/application/usecases/analyze-import-graph-usecase.ts +4 -1
- package/scripts/harness/biome-ast-engine/application/usecases/execute-lint-usecase.ts +2 -0
- package/scripts/harness/biome-ast-engine/application/usecases/resolve-enabled-rules-usecase.ts +31 -3
- package/scripts/harness/biome-ast-engine/composition-root.ts +10 -2
- package/scripts/harness/biome-ast-engine/domain/ports/rule-config-provider-port.ts +16 -0
- package/scripts/harness/biome-ast-engine/domain/ports/source-module-analyzer-port.ts +5 -1
- package/scripts/harness/biome-ast-engine/domain/services/lint-runner.ts +5 -1
- package/scripts/harness/biome-ast-engine/domain/value-objects/architecture-spec.ts +38 -0
- package/scripts/harness/biome-ast-engine/domain/value-objects/layer-boundary.ts +7 -8
- package/scripts/harness/biome-ast-engine/domain/value-objects/layer-name.ts +15 -23
- package/scripts/harness/biome-ast-engine/domain/value-objects/source-module-snapshot.ts +11 -4
- package/scripts/harness/biome-ast-engine/infrastructure/adapters/harness-config-provider-adapter.ts +29 -3
- package/scripts/harness/biome-ast-engine/infrastructure/adapters/typescript-source-module-analyzer-adapter.ts +22 -15
- package/scripts/harness/biome-ast-engine/infrastructure/mappers/source-module-snapshot-mapper.ts +26 -17
- package/scripts/harness/config-foundation/application/dto/resolved-config-output.ts +1 -0
- package/scripts/harness/config-foundation/application/usecases/load-resolved-config-use-case.ts +34 -2
- package/scripts/harness/config-foundation/application/usecases/migrate-schema-use-case.ts +89 -0
- package/scripts/harness/config-foundation/composition-root.ts +8 -0
- package/scripts/harness/config-foundation/domain/harness-config.ts +6 -0
- package/scripts/harness/config-foundation/domain/services/architecture-resolution-service.ts +257 -0
- package/scripts/harness/config-foundation/domain/value-objects/architecture-config.ts +66 -0
- package/scripts/harness/config-foundation/domain/value-objects/architecture-preset-catalog.ts +75 -0
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +546 -0
- package/scripts/harness/config-foundation/infrastructure/validators/ajv-config-schema-validator.ts +18 -6
- package/scripts/harness/config-foundation/presentation/cli/migrate-schema-command-handler.ts +83 -0
- package/scripts/harness/integrations/pre-commit.ts +211 -52
- package/scripts/harness/main.ts +460 -340
- package/scripts/harness/phase-dependency-model/domain/ports/story-reflection-file-system-port.ts +2 -4
- package/scripts/harness/phase-dependency-model/domain/services/story-reflection-checker.ts +54 -17
- package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.ts +154 -36
- package/scripts/harness/setup/skill-deployer.ts +140 -102
- package/scripts/harness/skill-quality/domain/errors/skill-quality-error.ts +24 -23
- package/scripts/harness/skill-quality/domain/value-objects/commit-message.ts +23 -7
- package/scripts/harness/traceability-model/application/usecases/apply-work-item-migration-usecase.ts +52 -0
- package/scripts/harness/traceability-model/application/usecases/plan-work-item-migration-usecase.ts +29 -0
- package/scripts/harness/traceability-model/application/usecases/validate-design-story-annotations-usecase.ts +83 -18
- package/scripts/harness/traceability-model/composition-root.ts +48 -30
- package/scripts/harness/traceability-model/domain/ports/design-document-port.ts +9 -15
- package/scripts/harness/traceability-model/domain/ports/work-item-migration-apply-port.ts +11 -0
- package/scripts/harness/traceability-model/domain/ports/work-item-migration-source-port.ts +9 -0
- package/scripts/harness/traceability-model/domain/services/work-item-migration-planner.ts +162 -0
- package/scripts/harness/traceability-model/domain/value-objects/work-item-frontmatter.ts +57 -0
- package/scripts/harness/traceability-model/domain/value-objects/work-item-migration-candidate.ts +47 -0
- package/scripts/harness/traceability-model/infrastructure/gateways/file-system-work-item-migration-apply-gateway.ts +110 -0
- package/scripts/harness/traceability-model/infrastructure/gateways/file-system-work-item-migration-source-gateway.ts +182 -0
- package/scripts/harness/traceability-model/infrastructure/gateways/markdown-design-document-gateway.ts +29 -43
- package/scripts/harness/traceability-model/infrastructure/parsers/work-item-frontmatter-parser.ts +136 -0
- package/scripts/harness/traceability-model/presentation/cli/migrate-work-items-command-handler.ts +186 -0
- package/skills/quick-implementor/SKILL.md +17 -1
- package/templates/.husky/commit-msg +1 -0
package/scripts/harness/main.ts
CHANGED
|
@@ -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 {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
import
|
|
34
|
-
import
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
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 =
|
|
188
|
+
type RenderFormat = "human" | "agent" | "ci";
|
|
162
189
|
|
|
163
190
|
function toRenderFormat(value: string): RenderFormat {
|
|
164
|
-
if (value ===
|
|
165
|
-
return
|
|
191
|
+
if (value === "human" || value === "agent" || value === "ci") return value;
|
|
192
|
+
return "human";
|
|
166
193
|
}
|
|
167
194
|
|
|
168
|
-
type ListFormat =
|
|
195
|
+
type ListFormat = "human" | "json";
|
|
169
196
|
|
|
170
197
|
function toListFormat(value: string): ListFormat {
|
|
171
|
-
if (value ===
|
|
172
|
-
return
|
|
198
|
+
if (value === "human" || value === "json") return value;
|
|
199
|
+
return "human";
|
|
173
200
|
}
|
|
174
201
|
|
|
175
|
-
type LayerIdFilter =
|
|
202
|
+
type LayerIdFilter = "L0" | "L1" | "L2" | "L3" | "L4";
|
|
176
203
|
|
|
177
204
|
function toLayerFilter(value: string | undefined): LayerIdFilter | undefined {
|
|
178
|
-
if (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 =
|
|
209
|
+
type AdrStatus = "Proposed" | "Accepted" | "Deprecated" | "Superseded";
|
|
183
210
|
|
|
184
211
|
function isAdrStatus(s: string): s is AdrStatus {
|
|
185
|
-
return s ===
|
|
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(
|
|
217
|
+
return csv.split(",").filter(isAdrStatus);
|
|
191
218
|
}
|
|
192
219
|
|
|
193
|
-
type SuiteIdValue =
|
|
220
|
+
type SuiteIdValue = "k-requirements" | "gng-gate" | "v0-migration" | "agent-independence";
|
|
194
221
|
|
|
195
|
-
const VALID_SUITE_IDS: readonly SuiteIdValue[] = [
|
|
196
|
-
|
|
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(
|
|
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 =
|
|
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 ===
|
|
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 =
|
|
254
|
+
type RuleSeverity = "error" | "warning" | "off";
|
|
230
255
|
|
|
231
256
|
function toRuleSeverity(value: string): RuleSeverity {
|
|
232
|
-
if (value ===
|
|
233
|
-
return
|
|
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
|
|
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?:
|
|
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,
|
|
313
|
+
content = await fsReadFile(configPath, "utf8");
|
|
278
314
|
} catch (error) {
|
|
279
|
-
if ((error as NodeJS.ErrnoException).code ===
|
|
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 ??
|
|
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 ===
|
|
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 ===
|
|
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,
|
|
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
|
|
387
|
-
const projectName = parseFlag(args,
|
|
388
|
-
const rawPhasePreset = parseFlag(args,
|
|
436
|
+
case "init": {
|
|
437
|
+
const projectName = parseFlag(args, "--name") ?? "my-project";
|
|
438
|
+
const rawPhasePreset = parseFlag(args, "--preset");
|
|
389
439
|
if (
|
|
390
|
-
rawPhasePreset !== undefined
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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,
|
|
401
|
-
if (skillSetRaw !==
|
|
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,
|
|
407
|
-
if (agentRaw !==
|
|
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 ===
|
|
413
|
-
const deployCodex = agent ===
|
|
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,
|
|
424
|
-
const huskyResult = withHusky
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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(
|
|
464
|
-
if (skillSet ===
|
|
465
|
-
console.log(
|
|
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(
|
|
541
|
+
console.log(" 1. Run the product-architect skill to start AIDLC");
|
|
468
542
|
}
|
|
469
|
-
console.log(
|
|
543
|
+
console.log(" 2. Customize phasegate.config.json if needed");
|
|
470
544
|
if (deployClaude) {
|
|
471
|
-
console.log(
|
|
545
|
+
console.log(" 3. Edit .claude/scripts/hook-config.json to set target directories");
|
|
472
546
|
}
|
|
473
547
|
if (deployCodex) {
|
|
474
|
-
console.log(` ${deployClaude ?
|
|
475
|
-
console.log(
|
|
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
|
|
558
|
+
case "update-skills": {
|
|
483
559
|
const deployed = await getDeployedVersion(rootDir);
|
|
484
560
|
const current = await getHarnessVersion(harnessRoot);
|
|
485
|
-
const previousSkillSet: SkillSet = deployed?.skillSet ??
|
|
486
|
-
const overrideSkillSet = parseFlag(args,
|
|
487
|
-
const updateSkillSet: SkillSet =
|
|
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(
|
|
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
|
|
588
|
+
case "enable-feature": {
|
|
504
589
|
const mod = createConfigFoundationModule();
|
|
505
590
|
const featureName = args[1];
|
|
506
|
-
const list = hasFlag(args,
|
|
507
|
-
const configPath = parseFlag(args,
|
|
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
|
|
603
|
+
case "disable-feature": {
|
|
519
604
|
const mod = createConfigFoundationModule();
|
|
520
605
|
const featureName = args[1];
|
|
521
|
-
const list = hasFlag(args,
|
|
522
|
-
const configPath = parseFlag(args,
|
|
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
|
|
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
|
|
653
|
+
case "render-errors": {
|
|
545
654
|
const mod = createHarnessErrorModule(rootDir);
|
|
546
|
-
const format = toRenderFormat(parseFlag(args,
|
|
547
|
-
const failOnError = hasFlag(args,
|
|
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
|
|
667
|
+
case "validate-fix": {
|
|
559
668
|
const mod = createHarnessErrorModule(rootDir);
|
|
560
|
-
const code = parseFlag(args,
|
|
561
|
-
const failFast = hasFlag(args,
|
|
562
|
-
const format = toListFormat(parseFlag(args,
|
|
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
|
|
682
|
+
case "list-errors": {
|
|
574
683
|
const mod = createHarnessErrorModule(rootDir);
|
|
575
|
-
const format = toListFormat(parseFlag(args,
|
|
576
|
-
const layer = toLayerFilter(parseFlag(args,
|
|
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
|
|
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
|
|
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,
|
|
611
|
-
const unitId = parseFlag(args,
|
|
612
|
-
const storyId = parseFlag(args,
|
|
613
|
-
const targetFilePath = parseFlag(args,
|
|
614
|
-
const result =
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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
|
|
734
|
+
case "list-adrs": {
|
|
629
735
|
const mod = createAdrFoundationModule(rootDir);
|
|
630
|
-
const statuses = toAdrStatuses(parseFlag(args,
|
|
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
|
|
746
|
+
case "validate-adr": {
|
|
641
747
|
const mod = createAdrFoundationModule(rootDir);
|
|
642
|
-
const all = hasFlag(args,
|
|
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
|
|
658
|
-
const l1Config = resolvedConfig
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
const
|
|
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
|
|
772
|
+
case "validate": {
|
|
672
773
|
const mod = createValidatorSystemModule();
|
|
673
|
-
const layer = parseFlag(args,
|
|
674
|
-
const unit = parseFlag(args,
|
|
675
|
-
const phase = parseFlag(args,
|
|
676
|
-
const format = parseFlag(args,
|
|
677
|
-
const failOnWarning = hasFlag(args,
|
|
678
|
-
const noL4 = hasFlag(args,
|
|
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 ===
|
|
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
|
|
704
|
-
const quick = hasFlag(args,
|
|
799
|
+
case "ci-check": {
|
|
800
|
+
const quick = hasFlag(args, "--quick");
|
|
705
801
|
if (quick) {
|
|
706
802
|
const mod = createQuickModeCompositionRoot();
|
|
707
|
-
const failOnReject = hasFlag(args,
|
|
708
|
-
const dryRun = hasFlag(args,
|
|
709
|
-
const files = parseFlag(args,
|
|
710
|
-
const format = parseFlag(args,
|
|
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
|
|
723
|
-
if (hasFlag(args,
|
|
724
|
-
process.stdout.write(
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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,
|
|
745
|
-
const format = parseFlag(args,
|
|
746
|
-
const failOnFullRequired = hasFlag(args,
|
|
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
|
|
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
|
|
863
|
+
case "phasegate:check-phase": {
|
|
766
864
|
// ISSUE-005 P2-6: --help / --json を positional として食わないようにする
|
|
767
|
-
if (hasFlag(args,
|
|
768
|
-
process.stdout.write(
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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(
|
|
788
|
-
const unit = parseFlag(args,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
938
|
+
case "phasegate:impact-analysis": {
|
|
839
939
|
const mod = createHarnessApiModule();
|
|
840
|
-
const storyId = args[1] && !args[1].startsWith(
|
|
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
|
|
849
|
-
if (hasFlag(args,
|
|
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,
|
|
870
|
-
const templateType = parseFlag(args,
|
|
871
|
-
const render = hasFlag(args,
|
|
872
|
-
const format = json ?
|
|
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
|
|
979
|
+
case "ci:migrate-agents-md": {
|
|
880
980
|
const mod = buildCiGovernance(rootDir);
|
|
881
|
-
const dryRun = hasFlag(args,
|
|
882
|
-
const validateOnly = hasFlag(args,
|
|
883
|
-
const format = json ?
|
|
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
|
|
990
|
+
case "ci:check-repetition": {
|
|
891
991
|
const mod = buildCiGovernance(rootDir);
|
|
892
|
-
const errorCode = parseFlag(args,
|
|
893
|
-
const reset = hasFlag(args,
|
|
894
|
-
const format = json ?
|
|
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
|
|
1001
|
+
case "baseline": {
|
|
902
1002
|
const mod = buildCiGovernance(rootDir);
|
|
903
|
-
const dryRun = hasFlag(args,
|
|
904
|
-
const force = hasFlag(args,
|
|
905
|
-
const pathsFlag = parseFlag(args,
|
|
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
|
|
1007
|
+
? pathsFlag
|
|
1008
|
+
.split(",")
|
|
1009
|
+
.map((s) => s.trim())
|
|
1010
|
+
.filter(Boolean)
|
|
908
1011
|
: undefined;
|
|
909
|
-
const format = json ?
|
|
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
|
|
1024
|
+
case "scaffold-design": {
|
|
922
1025
|
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
923
|
-
const unit = parseFlag(args,
|
|
924
|
-
const phase = parseFlag(args,
|
|
925
|
-
const force = hasFlag(args,
|
|
926
|
-
const format = json ?
|
|
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
|
|
1042
|
+
case "skill:execute-tdd-cycle": {
|
|
940
1043
|
const mod = createSkillQualityHandlers();
|
|
941
|
-
const unit = parseFlag(args,
|
|
942
|
-
const storyId = parseFlag(args,
|
|
943
|
-
const description = parseFlag(args,
|
|
944
|
-
const phaseRaw = parseFlag(args,
|
|
945
|
-
const phase =
|
|
946
|
-
? phaseRaw
|
|
947
|
-
|
|
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
|
|
1057
|
+
case "skill:check-coverage": {
|
|
956
1058
|
const mod = createSkillQualityHandlers();
|
|
957
|
-
const storyId = parseFlag(args,
|
|
958
|
-
const format = json ?
|
|
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
|
|
1067
|
+
case "skill:collect-lessons": {
|
|
966
1068
|
const mod = createSkillQualityHandlers();
|
|
967
|
-
const storyId = parseFlag(args,
|
|
968
|
-
const sourcesRaw = parseFlag(args,
|
|
969
|
-
const sources = sourcesRaw ? sourcesRaw.split(
|
|
970
|
-
const writeArtifact = hasFlag(args,
|
|
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
|
|
1079
|
+
case "skill:apply-cascade-update": {
|
|
978
1080
|
const mod = createSkillQualityHandlers();
|
|
979
|
-
const storyId = parseFlag(args,
|
|
980
|
-
const dryRun = hasFlag(args,
|
|
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
|
|
1089
|
+
case "skill:validate-structure": {
|
|
988
1090
|
const mod = createSkillQualityHandlers();
|
|
989
|
-
const skillFile = parseFlag(args,
|
|
990
|
-
const format = json ?
|
|
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
|
|
1100
|
+
case "regression:run-k-requirements": {
|
|
999
1101
|
const mod = buildRegressionSuite(rootDir);
|
|
1000
1102
|
const result = await mod.runKRequirementsRegressionUseCase.execute();
|
|
1001
|
-
const output = json
|
|
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
|
|
1111
|
+
case "regression:run-gng-gate": {
|
|
1008
1112
|
const mod = buildRegressionSuite(rootDir);
|
|
1009
1113
|
const result = await mod.runGngGateRegressionUseCase.execute();
|
|
1010
|
-
const output = json
|
|
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
|
|
1122
|
+
case "regression:run-agent-guard": {
|
|
1017
1123
|
const mod = buildRegressionSuite(rootDir);
|
|
1018
1124
|
const result = await mod.runAgentIndependenceGuardUseCase.execute();
|
|
1019
|
-
const output = json
|
|
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
|
|
1133
|
+
case "regression:run-k14-k15": {
|
|
1026
1134
|
const mod = buildRegressionSuite(rootDir);
|
|
1027
1135
|
const result = await mod.runK14K15RegressionUseCase.execute();
|
|
1028
|
-
const output = json
|
|
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
|
|
1144
|
+
case "regression:configure-ci-gate": {
|
|
1035
1145
|
const mod = buildRegressionSuite(rootDir);
|
|
1036
|
-
const requiredSuiteIds = parseSuiteIds(parseFlag(args,
|
|
1037
|
-
const threshold = parseCoverageThreshold(parseFlag(args,
|
|
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:
|
|
1151
|
+
executionMode: "sequential",
|
|
1042
1152
|
});
|
|
1043
|
-
const output = json
|
|
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
|
|
1161
|
+
case "regression:analyze-migration": {
|
|
1050
1162
|
const mod = buildRegressionSuite(rootDir);
|
|
1051
|
-
const dryRun = !hasFlag(args,
|
|
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
|
|
1173
|
+
case "regression:migrate-v0-tests": {
|
|
1062
1174
|
const mod = buildRegressionSuite(rootDir);
|
|
1063
|
-
const confirm = hasFlag(args,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1223
|
+
case "hook": {
|
|
1112
1224
|
const subCommand = args[1];
|
|
1113
|
-
const usage =
|
|
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
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
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,
|
|
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
|
|
1137
|
-
const preCommitPath = join(harnessRoot,
|
|
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
|
|
1146
|
-
const
|
|
1147
|
-
const
|
|
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(
|
|
1270
|
+
const child = spawn("bash", [scriptPath, ...forwardArgs], { stdio: "inherit" });
|
|
1150
1271
|
await new Promise<void>((_, reject) => {
|
|
1151
|
-
child.on(
|
|
1272
|
+
child.on("exit", (code) => {
|
|
1152
1273
|
process.exit(code ?? 1);
|
|
1153
1274
|
});
|
|
1154
|
-
child.on(
|
|
1275
|
+
child.on("error", reject);
|
|
1155
1276
|
});
|
|
1156
1277
|
break;
|
|
1157
1278
|
}
|
|
1158
1279
|
|
|
1159
1280
|
// ── skills ──
|
|
1160
|
-
case
|
|
1281
|
+
case "skills": {
|
|
1161
1282
|
const subCommand = args[1];
|
|
1162
|
-
const skillsRoot = join(harnessRoot,
|
|
1283
|
+
const skillsRoot = join(harnessRoot, "skills");
|
|
1163
1284
|
|
|
1164
|
-
if (subCommand ===
|
|
1165
|
-
const { promises: fs } = await import(
|
|
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,
|
|
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) ??
|
|
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:
|
|
1190
|
-
aidlc:
|
|
1191
|
-
utility:
|
|
1310
|
+
core: "Core — Quality Defense",
|
|
1311
|
+
aidlc: "AIDLC — Development Workflow",
|
|
1312
|
+
utility: "Utility",
|
|
1192
1313
|
};
|
|
1193
|
-
for (const cat of [
|
|
1314
|
+
for (const cat of ["core", "aidlc", "utility", "unknown"] as const) {
|
|
1194
1315
|
if (grouped[cat].length === 0) continue;
|
|
1195
|
-
const label = labels[cat] ??
|
|
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 ===
|
|
1326
|
+
if (subCommand === "info") {
|
|
1206
1327
|
const skillName = args[2];
|
|
1207
1328
|
if (!skillName) {
|
|
1208
|
-
console.error(
|
|
1329
|
+
console.error("Usage: phasegate skills info <skill-name>");
|
|
1209
1330
|
process.exit(2);
|
|
1210
1331
|
}
|
|
1211
|
-
const { promises: fs } = await import(
|
|
1212
|
-
const skillMdPath = join(skillsRoot, skillName,
|
|
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,
|
|
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(
|
|
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
|
}
|