phasegate 0.125.0 → 0.126.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 +17 -0
- package/package.json +1 -1
- package/scripts/harness/main.ts +60 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.126.0] - 2026-05-08
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **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 化。
|
|
15
|
+
- **新規ヘルパー**: `scripts/harness/main.ts` に `validateKnownFlags` / `findClosestFlag` / `levenshtein` の 3 関数を inline 追加 (presentation layer の zero-dep CLI parser に組み込み、commander/yargs などの外部依存は追加しない方針を維持)。
|
|
16
|
+
- **挙動**: `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。
|
|
17
|
+
- **互換性**: 既存の正しい flag 利用 (`--skills core` / `--name foo` 等) には一切影響しない。`--yes` は既存 user の script 互換のため known flag として受理 (no-op)。
|
|
18
|
+
- **help line 修正**: `main.ts` の `printUsage()` で表示される `init` 説明行に `--skills <core|all>` と `--yes` を追記 (従来 `--skills` が help から欠落していた)。
|
|
19
|
+
- **テスト追加**: 4 integration ケース (`scripts/harness/__tests__/integration/harness-api/init-flag-validation.integration.test.ts`):
|
|
20
|
+
- `--skill-set core` typo は `Did you mean '--skills'?` を出して exit 2
|
|
21
|
+
- `--skill-set=core` (=value 形式の typo) も同様に検出
|
|
22
|
+
- `--xyz-totally-unknown` は known flags の列挙を出して exit 2
|
|
23
|
+
- 正しい組み合わせ `--name foo --skills all --agent claude --yes` は flag validation で reject されない
|
|
24
|
+
- 全 3503 テスト (前回 3499 + 新規 4) グリーン、L1 lint 違反なし。
|
|
25
|
+
- **スコープ外**: 他 subcommand (update-skills / migrate / lint / validate / etc.) への validateKnownFlags 展開は段階適用のため別 WI。`--help` per subcommand 実装も別 WI。
|
|
26
|
+
|
|
10
27
|
## [0.125.0] - 2026-05-08
|
|
11
28
|
|
|
12
29
|
### 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,58 @@ 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
|
+
|
|
169
222
|
/** フラグとその値を除いた位置引数のみを返す */
|
|
170
223
|
function parsePositionalArgs(args: readonly string[], flagsWithValues: readonly string[] = []): string[] {
|
|
171
224
|
const result: string[] = [];
|
|
@@ -434,6 +487,12 @@ async function main(): Promise<void> {
|
|
|
434
487
|
switch (command) {
|
|
435
488
|
// ── harness setup ──
|
|
436
489
|
case "init": {
|
|
490
|
+
const KNOWN_INIT_FLAGS = ["--name", "--preset", "--skills", "--agent", "--with-husky", "--yes"];
|
|
491
|
+
const flagError = validateKnownFlags(args, KNOWN_INIT_FLAGS);
|
|
492
|
+
if (flagError) {
|
|
493
|
+
console.error(flagError);
|
|
494
|
+
process.exit(2);
|
|
495
|
+
}
|
|
437
496
|
const projectName = parseFlag(args, "--name") ?? "my-project";
|
|
438
497
|
const rawPhasePreset = parseFlag(args, "--preset");
|
|
439
498
|
if (
|