phasegate 0.125.0 → 0.127.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
CHANGED
|
@@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.127.0] - 2026-05-08
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **WI-091 — `layers.L4.enabled: false` 無視 / `--help` がサブコマンドで no-op / drift-detect が括弧 qualifier で破綻する 3 件を解消 (GitHub Issue #4)** — 外部レポーター nakataj-mti が pnpm monorepo 環境 (defense `standard` / architecture `clean`) で報告した 5 件の bug+DX gap (`L4.enabled` 無視 / warning でも overall FAIL / `--help` 副作用走行 / `paths` 設定が L2-001 に未配線 / drift element の括弧 qualifier 破綻) のうち、リスクの低い 3 件 (#1, #3, #5 immediate) を本リリースで先行修正。残る #2 (severity 集計セマンティクス) と #4 (paths threading 完成) は後続 WI に切り出す方針 (本 description の `スコープ外` 参照)。
|
|
15
|
+
- **finding #1 — L4 enabled gate 追加 (`run-l4-validators-usecase.ts`)**: L3 (`run-l3-validators-usecase.ts:74-78`) と対称な `if (!layerConfig.enabled) return [];` ガードを `getLayerConfig` 直後に追加。`layers.L4.enabled: false` 設定で drift / consistency / dead-code service が呼ばれず、空配列を返すことで集計層で SKIP として表示される。dogfood 再現済 (`/tmp/phasegate-dogfood-wi091`, phasegate@0.126.0)。整合性テスト 2 ケース (`IT-UC-RunL4-007/008`) 追加。
|
|
16
|
+
- **finding #3 — `--help` / `-h` を全 subcommand に pre-dispatch で集約 (`main.ts`)**: 51 個の subcommand のうち 3 個のみが inline `--help` を持っており、残り ~48 個は silent ignore で `update-skills --help` → 8 skills 再 deploy / `phasegate:detect-drift --help` → drift 実 run / `validate --help` → phase gate 実走 という副作用走行を起こしていた。`main()` 内に `SUBCOMMAND_HELP` table (13 entry) と `printSubcommandHelp` helper を追加し、`switch(command)` の手前で `hasFlag(args, "--help") || hasFlag(args, "-h")` を最優先で解釈 → usage 出力 + exit 0。table 未登録の subcommand は `Usage: phasegate <cmd> [options]\n(use 'phasegate --help' for the full command reference)` の generic fallback で exit 0。dogfood 再現済 3 ケースが本 fix で停止することを spawn 経由 5 ケース (副作用ナシ確認込み) で検証。
|
|
17
|
+
- **finding #5 immediate — drift-detect の design heading から括弧 qualifier を normalize (`markdown-design-document-adapter.ts`)**: `extractConceptNames` が markdown heading から `(〜)` / `(〜)` qualifier (例: `(エンティティ・新規)`, `(legacy)`) を strip しないため code 側 class 名と exact match できず false-positive drift を出していた問題を解消。半角・全角括弧両対応、global flag で連続 / 複数 qualifier (`Foo(A)(B)` → `Foo`) も処理、strip 後 0 文字になる病的 heading は concepts に含めない。source code 側 (`biome-ast-source-code-analyzer-adapter.ts`) は AST node name から識別子のみ取得 (括弧含まず) のため design 側 normalize で十分。整合性テスト 4 ケース (`IT-REPO-DesignDoc-007〜010`) 追加。`pointers:` block 仕様による element → file path 明示は別 WI に切り出し。
|
|
18
|
+
- **既存 inline `--help` 処理は残置**: `main.ts` の 3 箇所 (line 982 / 1028 / 1112) は pre-dispatch で hit する関係で dead code 化するが本 commit では削除せず (テスト互換性確保のため)。clean-up は別 commit で漸進可。
|
|
19
|
+
- **テスト**: 全 3514 テスト (前回 3510 + 新規 11: finding #1 で 2 + finding #3 で 5 + finding #5 で 4) グリーン。L1 lint 違反なし。
|
|
20
|
+
- **スコープ外 (別 WI 起票予定)**: finding #2 (warning-severity でも overall FAIL の集計セマンティクス) は ADR レベルの後方互換戦略判断が必要なため story-implementor 案件として分離。finding #4 (`paths.designDocs` を L2-001 へ完全 threading) は WI-085 で `inceptionDocs` 側のみ通った threading 漏れの補完で phase-nodes 3 ファイル + traceability-model 2 ファイルの placeholder 化を伴う story-implementor 案件として分離。
|
|
21
|
+
|
|
22
|
+
## [0.126.0] - 2026-05-08
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **WI-090 — `phasegate init` が unknown flag を silent ignore する問題を解消** — 例えば `phasegate init --skill-set core` (typo: 正しくは `--skills core`) を実行すると、従来は `--skill-set` 値が無視されて default の `all` で deploy されていた。本リリースで unknown flag を **exit 2 + suggestion** として error 化。
|
|
27
|
+
- **新規ヘルパー**: `scripts/harness/main.ts` に `validateKnownFlags` / `findClosestFlag` / `levenshtein` の 3 関数を inline 追加 (presentation layer の zero-dep CLI parser に組み込み、commander/yargs などの外部依存は追加しない方針を維持)。
|
|
28
|
+
- **挙動**: `init` 冒頭で `KNOWN_INIT_FLAGS = ["--name", "--preset", "--skills", "--agent", "--with-husky", "--yes"]` と照合し、未知の `--xxx` または `--xxx=value` を検出すると Levenshtein 距離 ≤ 4 の closest flag を `Did you mean '...'?` で提示、該当無しなら known flags の列挙を提示して exit 2。
|
|
29
|
+
- **互換性**: 既存の正しい flag 利用 (`--skills core` / `--name foo` 等) には一切影響しない。`--yes` は既存 user の script 互換のため known flag として受理 (no-op)。
|
|
30
|
+
- **help line 修正**: `main.ts` の `printUsage()` で表示される `init` 説明行に `--skills <core|all>` と `--yes` を追記 (従来 `--skills` が help から欠落していた)。
|
|
31
|
+
- **テスト追加**: 4 integration ケース (`scripts/harness/__tests__/integration/harness-api/init-flag-validation.integration.test.ts`):
|
|
32
|
+
- `--skill-set core` typo は `Did you mean '--skills'?` を出して exit 2
|
|
33
|
+
- `--skill-set=core` (=value 形式の typo) も同様に検出
|
|
34
|
+
- `--xyz-totally-unknown` は known flags の列挙を出して exit 2
|
|
35
|
+
- 正しい組み合わせ `--name foo --skills all --agent claude --yes` は flag validation で reject されない
|
|
36
|
+
- 全 3503 テスト (前回 3499 + 新規 4) グリーン、L1 lint 違反なし。
|
|
37
|
+
- **スコープ外**: 他 subcommand (update-skills / migrate / lint / validate / etc.) への validateKnownFlags 展開は段階適用のため別 WI。`--help` per subcommand 実装も別 WI。
|
|
38
|
+
|
|
10
39
|
## [0.125.0] - 2026-05-08
|
|
11
40
|
|
|
12
41
|
### Changed
|
package/package.json
CHANGED
package/scripts/harness/main.ts
CHANGED
|
@@ -81,7 +81,8 @@ Usage: phasegate <command> [options]
|
|
|
81
81
|
|
|
82
82
|
Setup:
|
|
83
83
|
init Initialize project: deploy skills + design docs + phasegate.config.json
|
|
84
|
-
(--name <project-name>, --preset <full|standard|minimal|custom>,
|
|
84
|
+
(--name <project-name>, --preset <full|standard|minimal|custom>,
|
|
85
|
+
--skills <core|all>, --agent <claude|codex|both>, --with-husky, --yes)
|
|
85
86
|
update-skills Re-deploy skills from current harness version
|
|
86
87
|
|
|
87
88
|
Commands:
|
|
@@ -166,6 +167,180 @@ function hasFlag(args: readonly string[], flag: string): boolean {
|
|
|
166
167
|
return args.includes(flag);
|
|
167
168
|
}
|
|
168
169
|
|
|
170
|
+
function levenshtein(a: string, b: string): number {
|
|
171
|
+
const m = a.length;
|
|
172
|
+
const n = b.length;
|
|
173
|
+
if (m === 0) return n;
|
|
174
|
+
if (n === 0) return m;
|
|
175
|
+
const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
176
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
177
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
178
|
+
for (let i = 1; i <= m; i++) {
|
|
179
|
+
for (let j = 1; j <= n; j++) {
|
|
180
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
181
|
+
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return dp[m][n];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function findClosestFlag(input: string, known: readonly string[]): string | undefined {
|
|
188
|
+
let best: string | undefined;
|
|
189
|
+
let bestDist = Number.POSITIVE_INFINITY;
|
|
190
|
+
for (const flag of known) {
|
|
191
|
+
const dist = levenshtein(input, flag);
|
|
192
|
+
if (dist < bestDist) {
|
|
193
|
+
bestDist = dist;
|
|
194
|
+
best = flag;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return bestDist <= 4 ? best : undefined;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function validateKnownFlags(args: readonly string[], known: readonly string[]): string | null {
|
|
201
|
+
const knownSet = new Set(known);
|
|
202
|
+
let i = 0;
|
|
203
|
+
while (i < args.length) {
|
|
204
|
+
const arg = args[i];
|
|
205
|
+
if (!arg.startsWith("--")) {
|
|
206
|
+
i++;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const flagName = arg.includes("=") ? arg.slice(0, arg.indexOf("=")) : arg;
|
|
210
|
+
if (knownSet.has(flagName)) {
|
|
211
|
+
i++;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const suggestion = findClosestFlag(flagName, known);
|
|
215
|
+
return suggestion
|
|
216
|
+
? `Error: unknown flag '${flagName}'. Did you mean '${suggestion}'?`
|
|
217
|
+
: `Error: unknown flag '${flagName}'. Known flags: ${known.join(", ")}`;
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const SUBCOMMAND_HELP: Record<string, string> = {
|
|
223
|
+
init: `Usage: phasegate init [options]
|
|
224
|
+
|
|
225
|
+
Initialize phasegate in the current project: deploy skills + design docs + phasegate.config.json.
|
|
226
|
+
|
|
227
|
+
Options:
|
|
228
|
+
--name <project-name> Project name (default: "my-project")
|
|
229
|
+
--preset <full|standard|minimal|custom> Phase dependency preset (default: "standard")
|
|
230
|
+
--skills <core|all> Skill set to deploy (default: "all")
|
|
231
|
+
--agent <claude|codex|both> Agent integration target (default: "claude")
|
|
232
|
+
--with-husky Install Husky pre-commit hooks
|
|
233
|
+
--yes Skip confirmation prompts
|
|
234
|
+
--help, -h Show this help`,
|
|
235
|
+
"update-skills": `Usage: phasegate update-skills [options]
|
|
236
|
+
|
|
237
|
+
Redeploy skills in .claude/skills/ from the installed phasegate version. WARNING: overwrites existing skill files.
|
|
238
|
+
|
|
239
|
+
Options:
|
|
240
|
+
--skills <core|all> Skill set to deploy
|
|
241
|
+
--agent <claude|codex|both> Agent integration target
|
|
242
|
+
--help, -h Show this help`,
|
|
243
|
+
validate: `Usage: phasegate validate [options]
|
|
244
|
+
|
|
245
|
+
Run validators against the project. Without --layer, runs all enabled layers (L0/L2/L3/L4).
|
|
246
|
+
|
|
247
|
+
Options:
|
|
248
|
+
--layer <L0|L2|L3|L4> Run only the specified layer
|
|
249
|
+
--json Output machine-readable JSON
|
|
250
|
+
--help, -h Show this help`,
|
|
251
|
+
lint: `Usage: phasegate lint [options]
|
|
252
|
+
|
|
253
|
+
Run L1 Biome AST checks across the project.
|
|
254
|
+
|
|
255
|
+
Options:
|
|
256
|
+
--json Output machine-readable JSON
|
|
257
|
+
--help, -h Show this help`,
|
|
258
|
+
migrate: `Usage: phasegate migrate [options]
|
|
259
|
+
|
|
260
|
+
Migrate phasegate.config.json from older schema versions. Backs up the original to phasegate.config.json.bak.
|
|
261
|
+
|
|
262
|
+
Options:
|
|
263
|
+
--dry-run Preview changes without writing
|
|
264
|
+
--help, -h Show this help`,
|
|
265
|
+
"list-errors": `Usage: phasegate list-errors [options]
|
|
266
|
+
|
|
267
|
+
List validator error catalog entries.
|
|
268
|
+
|
|
269
|
+
Options:
|
|
270
|
+
--layer <L0|L1|L2|L3|L4> Filter by layer
|
|
271
|
+
--format <table|json> Output format (default: "table")
|
|
272
|
+
--help, -h Show this help`,
|
|
273
|
+
"phasegate:status": `Usage: phasegate phasegate:status
|
|
274
|
+
|
|
275
|
+
Display harness status (enabled validators, schema version, hook deployment).`,
|
|
276
|
+
"phasegate:detect-drift": `Usage: phasegate phasegate:detect-drift [options]
|
|
277
|
+
|
|
278
|
+
Run L4-001 drift detection between design documents and source code. WARNING: scans the project filesystem.
|
|
279
|
+
|
|
280
|
+
Options:
|
|
281
|
+
--json Output machine-readable JSON (default for this command)
|
|
282
|
+
--help, -h Show this help`,
|
|
283
|
+
"phasegate:check-ready": `Usage: phasegate phasegate:check-ready
|
|
284
|
+
|
|
285
|
+
Check whether the harness is ready (config valid, hooks deployed).`,
|
|
286
|
+
"phasegate:complete-check": `Usage: phasegate phasegate:complete-check
|
|
287
|
+
|
|
288
|
+
Run completion check (used by Stop hook). Validates phase-gate, metadata, and test-quality.`,
|
|
289
|
+
"phasegate:check-phase": `Usage: phasegate phasegate:check-phase [options]
|
|
290
|
+
|
|
291
|
+
Check phase gate for a specific unit.
|
|
292
|
+
|
|
293
|
+
Options:
|
|
294
|
+
--unit <unitId> Target unit ID (e.g., harness-api). If omitted,
|
|
295
|
+
the first positional argument is used.
|
|
296
|
+
--json Output result as JSON.
|
|
297
|
+
--help, -h Show this help.
|
|
298
|
+
|
|
299
|
+
Examples:
|
|
300
|
+
phasegate phasegate:check-phase --unit harness-api
|
|
301
|
+
phasegate phasegate:check-phase harness-api --json`,
|
|
302
|
+
"check-change-category": `Usage: phasegate check-change-category --paths <csv> [options]
|
|
303
|
+
|
|
304
|
+
Classify changed file paths into quick-mode categories and report
|
|
305
|
+
whether Full Mode is required.
|
|
306
|
+
|
|
307
|
+
Options:
|
|
308
|
+
--paths <csv> Comma-separated file paths to classify.
|
|
309
|
+
--format <human|json> Output format. Default: human.
|
|
310
|
+
--fail-on-full-required Exit with code 1 when Full Mode is required.
|
|
311
|
+
--help, -h Show this help.
|
|
312
|
+
|
|
313
|
+
Examples:
|
|
314
|
+
phasegate check-change-category --paths src/foo.ts,src/bar.ts
|
|
315
|
+
phasegate check-change-category --paths src/foo.ts --format json`,
|
|
316
|
+
"ci:generate-template": `Usage: phasegate ci:generate-template [options]
|
|
317
|
+
|
|
318
|
+
Generates a CI template configuration.
|
|
319
|
+
|
|
320
|
+
Options:
|
|
321
|
+
--preset <id> Preset name (e.g. standard, strict). Required.
|
|
322
|
+
--type <type> Template purpose (NOT CI platform name). One of:
|
|
323
|
+
aidlc-gate — AIDLC phase gate checks
|
|
324
|
+
consistency-check — Doc/code consistency checks
|
|
325
|
+
pre-commit — Pre-commit hook template
|
|
326
|
+
--render Render the template to stdout
|
|
327
|
+
--json Output in JSON format
|
|
328
|
+
|
|
329
|
+
Examples:
|
|
330
|
+
phasegate ci:generate-template --preset standard --type aidlc-gate
|
|
331
|
+
phasegate ci:generate-template --preset strict --type pre-commit --render`,
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
function printSubcommandHelp(command: string): void {
|
|
335
|
+
const help = SUBCOMMAND_HELP[command];
|
|
336
|
+
if (help) {
|
|
337
|
+
console.log(help);
|
|
338
|
+
} else {
|
|
339
|
+
console.log(`Usage: phasegate ${command} [options]`);
|
|
340
|
+
console.log("(use 'phasegate --help' for the full command reference)");
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
169
344
|
/** フラグとその値を除いた位置引数のみを返す */
|
|
170
345
|
function parsePositionalArgs(args: readonly string[], flagsWithValues: readonly string[] = []): string[] {
|
|
171
346
|
const result: string[] = [];
|
|
@@ -425,6 +600,12 @@ async function main(): Promise<void> {
|
|
|
425
600
|
process.exit(0);
|
|
426
601
|
}
|
|
427
602
|
|
|
603
|
+
// Pre-dispatch: 全 subcommand で --help / -h を最優先で解釈し usage 出力 (副作用走行を防ぐ — WI-091 finding #3)
|
|
604
|
+
if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
|
|
605
|
+
printSubcommandHelp(command);
|
|
606
|
+
process.exit(0);
|
|
607
|
+
}
|
|
608
|
+
|
|
428
609
|
const json = hasFlag(args, "--json");
|
|
429
610
|
|
|
430
611
|
// Cross-unit wiring: 設定を先に解決し、各Unit に注入する
|
|
@@ -434,6 +615,12 @@ async function main(): Promise<void> {
|
|
|
434
615
|
switch (command) {
|
|
435
616
|
// ── harness setup ──
|
|
436
617
|
case "init": {
|
|
618
|
+
const KNOWN_INIT_FLAGS = ["--name", "--preset", "--skills", "--agent", "--with-husky", "--yes"];
|
|
619
|
+
const flagError = validateKnownFlags(args, KNOWN_INIT_FLAGS);
|
|
620
|
+
if (flagError) {
|
|
621
|
+
console.error(flagError);
|
|
622
|
+
process.exit(2);
|
|
623
|
+
}
|
|
437
624
|
const projectName = parseFlag(args, "--name") ?? "my-project";
|
|
438
625
|
const rawPhasePreset = parseFlag(args, "--preset");
|
|
439
626
|
if (
|
|
@@ -69,6 +69,10 @@ export class RunL4ValidatorsUseCase {
|
|
|
69
69
|
throw new ValidatorExecutionError(`Failed to get L4 LayerConfig: ${err instanceof Error ? err.message : String(err)}`, err);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
if (!layerConfig.enabled) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
|
|
72
76
|
const results = this.executionService.execute(definitions, [layerConfig]);
|
|
73
77
|
const overrideMap = new Map<string, ValidationResult>(results.map((result) => [result.validatorId.value, result]));
|
|
74
78
|
|
package/scripts/harness/validator-system/infrastructure/adapters/markdown-design-document-adapter.ts
CHANGED
|
@@ -52,7 +52,15 @@ function extractConceptNames(markdown: string): string[] {
|
|
|
52
52
|
// 既知のメタ見出しは暗黙的にスキップ
|
|
53
53
|
if (isMetaHeading(name)) continue;
|
|
54
54
|
// 名前から末尾のスキップマーカーを落とす (念のため)
|
|
55
|
-
|
|
55
|
+
// WI-091 finding #5: `(〜)` / `(〜)` qualifier (例: `(エンティティ・新規)`) を strip
|
|
56
|
+
// して code 側の class 名と exact match できるようにする
|
|
57
|
+
const stripped = name
|
|
58
|
+
.replace(SKIP_MARKER, '')
|
|
59
|
+
.replace(/[((][^))]*[))]/g, '')
|
|
60
|
+
.trim();
|
|
61
|
+
if (stripped.length > 0) {
|
|
62
|
+
headings.push(stripped);
|
|
63
|
+
}
|
|
56
64
|
}
|
|
57
65
|
return headings;
|
|
58
66
|
}
|