phasegate 0.160.7 → 0.160.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.160.9] - 2026-05-15
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **WI-201 — config plan apply discoverability** — documents `config:plan --apply` in subcommand help, top-level help, and CLI reference so agents can discover the managed config apply path from `--help`.
|
|
15
|
+
|
|
16
|
+
## [0.160.8] - 2026-05-15
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **WI-201 — managed config plan apply path** — adds `config:plan --apply` for applicable config patch plans, records rollback backups before mutating `phasegate.config.json`, advertises managed apply commands in config-plan guidance, and points config edit blocks back to the reviewed dry-run/apply workflow.
|
|
21
|
+
|
|
10
22
|
## [0.160.6] - 2026-05-14
|
|
11
23
|
|
|
12
24
|
### Fixed
|
|
@@ -28,7 +28,7 @@ Command names in this document are split into three surfaces:
|
|
|
28
28
|
| `uninstall --dry-run` / `--apply` | Remove PhaseGate-managed files and managed blocks using `.phasegate/manifest.json`; `--force` handles managed conflict cases. |
|
|
29
29
|
| `reconcile --dry-run` / `--apply` | Update PhaseGate-managed files to current package templates and refresh manifest hashes; `--force` allows managed-file replacement with backup. |
|
|
30
30
|
| `setup:agent` | Agent-readable setup planner and optional apply path. Options: `--intent <minimal\|recommended\|strict\|ci-only\|agent-hooks\|retrofit>`, `--agent <claude\|codex\|both>`, `--workflow <standard\|strict>`, `--with-husky`, `--with-ci`, `--dry-run`, `--apply`, `--json`. <!-- @work-item-id WI-172 --> |
|
|
31
|
-
| `config:plan` | Agent-readable configuration change planner. Options: `--intent <l4-strict\|codex-hooks\|ci-fail-on-warning\|baseline-reset\|quick-mode-strict>`, `--dry-run`, `--json`. <!-- @work-item-id WI-173 --> |
|
|
31
|
+
| `config:plan` | Agent-readable configuration change planner. Options: `--intent <l4-strict\|codex-hooks\|ci-fail-on-warning\|baseline-reset\|quick-mode-strict\|retrofit-bootstrap\|planning-mode-relax>`, `--dry-run`, `--apply`, `--json`. <!-- @work-item-id WI-173 --> <!-- @work-item-id WI-201 --> |
|
|
32
32
|
| `update-skills` | Compatibility alias for `reconcile`; use `reconcile` for new automation. |
|
|
33
33
|
| `scaffold-wi <unit> <type>` | Create `docs/inception/{unit}/WI-XXX/description.md` using the next free WI number. |
|
|
34
34
|
| `emit-agent-rules` | Print the AGENTS.md / CLAUDE.md WI workflow rules block. |
|
package/package.json
CHANGED
package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* @layer application
|
|
3
3
|
* @unit agent-integration
|
|
4
4
|
* @story H11-02
|
|
5
|
+
* @work-item-id WI-201
|
|
5
6
|
*
|
|
6
7
|
* HandlePreToolUseUseCase
|
|
7
8
|
* PreToolUse Hook処理のオーケストレーション
|
|
@@ -237,6 +238,31 @@ export class HandlePreToolUseUseCase {
|
|
|
237
238
|
unitId: string | undefined,
|
|
238
239
|
): HandlePreToolUseOutput {
|
|
239
240
|
const fp = blockedFilePath ?? "不明なファイル";
|
|
241
|
+
if (result.dominantCategory === "config" && /(?:^|\/)phasegate\.config\.json$/.test(fp)) {
|
|
242
|
+
const dryRunCommand = "phasegate config:plan --intent retrofit-bootstrap --dry-run --json";
|
|
243
|
+
const applyCommand = "phasegate config:plan --intent retrofit-bootstrap --apply --json";
|
|
244
|
+
const lines = [
|
|
245
|
+
`Full mode 必須変更が検出されました: ${fp}`,
|
|
246
|
+
"カテゴリ: config",
|
|
247
|
+
];
|
|
248
|
+
if (result.rejectionRule) {
|
|
249
|
+
lines.push(`判定ルール: ${result.rejectionRule}`);
|
|
250
|
+
}
|
|
251
|
+
if (result.rejectionReason) {
|
|
252
|
+
lines.push(`理由: ${result.rejectionReason}`);
|
|
253
|
+
}
|
|
254
|
+
lines.push(`次のアクション: ${dryRunCommand} で差分を確認し、承認後に ${applyCommand} を実行してください。`);
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
shouldBlock: true,
|
|
258
|
+
blockedFilePath,
|
|
259
|
+
blockReason: "FULL_MODE_REQUIRED",
|
|
260
|
+
error: { message: lines.join("\n") },
|
|
261
|
+
fullModeRejectionRule: result.rejectionRule,
|
|
262
|
+
fullModeDominantCategory: result.dominantCategory,
|
|
263
|
+
nextAction: `${dryRunCommand} && ${applyCommand}`,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
240
266
|
const lines: string[] = [`Full mode 必須変更が検出されました: ${fp}`];
|
|
241
267
|
if (result.dominantCategory) {
|
|
242
268
|
lines.push(`カテゴリ: ${result.dominantCategory}`);
|
|
@@ -373,6 +399,11 @@ export class HandlePreToolUseUseCase {
|
|
|
373
399
|
message: (fp) =>
|
|
374
400
|
`保護ファイルへの書き込みがブロックされました: ${fp}\nバージョン変更を含む package.json の更新は /quick-implementor スキルを使用してください。`,
|
|
375
401
|
},
|
|
402
|
+
{
|
|
403
|
+
pattern: /(?:^|\/)phasegate\.config\.json$/,
|
|
404
|
+
message: (fp) =>
|
|
405
|
+
`保護ファイルへの書き込みがブロックされました: ${fp}\n設定変更は CLI 経由で計画・適用してください: phasegate config:plan --intent retrofit-bootstrap --dry-run --json / phasegate config:plan --intent retrofit-bootstrap --apply --json`,
|
|
406
|
+
},
|
|
376
407
|
{
|
|
377
408
|
pattern: /(?:^|\/)harness\.config\.json$/,
|
|
378
409
|
message: (fp) =>
|
package/scripts/harness/main.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* @work-item-id WI-196
|
|
14
14
|
* @work-item-id WI-197
|
|
15
15
|
* @work-item-id WI-200
|
|
16
|
+
* @work-item-id WI-201
|
|
16
17
|
*
|
|
17
18
|
* Phasegate CLI エントリポイント。
|
|
18
19
|
* 各Unitの Composition Root からハンドラーを取得し、コマンドに応じてディスパッチする。
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
readFile as fsReadFile,
|
|
26
27
|
readdir as fsReaddir,
|
|
27
28
|
readlink as fsReadlink,
|
|
29
|
+
rename as fsRename,
|
|
28
30
|
writeFile as fsWriteFile,
|
|
29
31
|
} from "node:fs/promises";
|
|
30
32
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -217,7 +219,7 @@ Gate semantics:
|
|
|
217
219
|
refresh-claude-md Refresh CLAUDE.md standard sections (--dry-run, --apply, --json)
|
|
218
220
|
p2:check-agent-context Check AGENTS.md / CLAUDE.md freshness (--threshold-days <n>, --json)
|
|
219
221
|
setup:agent Plan agent-driven setup (--intent <minimal|recommended|strict|ci-only|agent-hooks|retrofit>, --agent <claude|codex|both>, --dry-run|--apply, --json)
|
|
220
|
-
config:plan Plan safe config changes (--intent <l4-strict|codex-hooks|ci-fail-on-warning|baseline-reset|quick-mode-strict|retrofit-bootstrap|planning-mode-relax>, --dry-run, --json)
|
|
222
|
+
config:plan Plan safe config changes (--intent <l4-strict|codex-hooks|ci-fail-on-warning|baseline-reset|quick-mode-strict|retrofit-bootstrap|planning-mode-relax>, --dry-run|--apply, --json)
|
|
221
223
|
ci:check-repetition Check error repetition (--code <errorCode>, --reset, --json)
|
|
222
224
|
baseline Create retrofit baseline snapshot (--dry-run, --force, --paths <glob,glob,...>, --json)
|
|
223
225
|
scaffold-design Scaffold a design doc (--unit <id>, --phase <logical|domain|uiux|unit-test|it-test>, --dry-run|--apply, --force, --json)
|
|
@@ -538,6 +540,7 @@ Intents:
|
|
|
538
540
|
|
|
539
541
|
Options:
|
|
540
542
|
--dry-run
|
|
543
|
+
--apply
|
|
541
544
|
--json
|
|
542
545
|
--help, -h Show this help`,
|
|
543
546
|
reconcile: `Usage: phasegate reconcile [options]
|
|
@@ -811,6 +814,12 @@ interface ConfigPatchPreview {
|
|
|
811
814
|
readonly operations: readonly ConfigPatchOperation[];
|
|
812
815
|
}
|
|
813
816
|
|
|
817
|
+
interface ConfigApplyResult {
|
|
818
|
+
readonly changed: boolean;
|
|
819
|
+
readonly backupPath: string;
|
|
820
|
+
readonly appliedOperations: readonly ConfigPatchOperation[];
|
|
821
|
+
}
|
|
822
|
+
|
|
814
823
|
function parseInitPhasePreset(value: string | undefined): InitPhasePreset | undefined {
|
|
815
824
|
if (value === undefined) return undefined;
|
|
816
825
|
if (value === "full" || value === "standard" || value === "minimal" || value === "custom") {
|
|
@@ -1267,6 +1276,40 @@ function buildConfigPatchPreview(intent: ConfigChangeIntent, before: unknown | n
|
|
|
1267
1276
|
};
|
|
1268
1277
|
}
|
|
1269
1278
|
|
|
1279
|
+
function configPlanBackupPath(rootDir: string, now: Date): string {
|
|
1280
|
+
const stamp = now.toISOString().replaceAll(":", "-");
|
|
1281
|
+
return join(rootDir, ".phasegate", "backups", `phasegate.config.${stamp}.json`);
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
async function applyConfigPlan(rootDir: string, plan: Awaited<ReturnType<typeof buildConfigChangePlan>>): Promise<ConfigApplyResult> {
|
|
1285
|
+
const patch = plan.configPatch;
|
|
1286
|
+
if (patch.applicability !== "applicable") {
|
|
1287
|
+
throw new Error(`config plan is not applicable: ${patch.blockedReason ?? patch.applicability}`);
|
|
1288
|
+
}
|
|
1289
|
+
if (patch.operations.length === 0) {
|
|
1290
|
+
throw new Error("config plan has no operations to apply.");
|
|
1291
|
+
}
|
|
1292
|
+
if (!isPlainRecord(patch.after)) {
|
|
1293
|
+
throw new Error("config plan after-state must be a JSON object.");
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
const configPath = join(rootDir, patch.path);
|
|
1297
|
+
const backupPath = configPlanBackupPath(rootDir, new Date());
|
|
1298
|
+
await fsMkdir(dirname(backupPath), { recursive: true });
|
|
1299
|
+
const beforeText = patch.before === null ? "" : `${JSON.stringify(patch.before, null, 2)}\n`;
|
|
1300
|
+
await fsWriteFile(backupPath, beforeText, "utf8");
|
|
1301
|
+
|
|
1302
|
+
const tempPath = `${configPath}.tmp-${process.pid}-${Date.now()}`;
|
|
1303
|
+
await fsWriteFile(tempPath, `${JSON.stringify(patch.after, null, 2)}\n`, "utf8");
|
|
1304
|
+
await fsRename(tempPath, configPath);
|
|
1305
|
+
|
|
1306
|
+
return {
|
|
1307
|
+
changed: true,
|
|
1308
|
+
backupPath: backupPath.slice(rootDir.length + 1),
|
|
1309
|
+
appliedOperations: patch.operations,
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1270
1313
|
function hasStructuredInstallError(value: unknown): boolean {
|
|
1271
1314
|
return isPlainRecord(value) && isPlainRecord(value.error);
|
|
1272
1315
|
}
|
|
@@ -1284,7 +1327,7 @@ async function buildConfigChangePlan(rootDir: string, intent: ConfigChangeIntent
|
|
|
1284
1327
|
targets: ["phasegate.config.json: layers.L4.enabled", "phasegate.config.json: layers.L4.failOnWarning"],
|
|
1285
1328
|
managedTargets: ["phasegate.config.json"],
|
|
1286
1329
|
externalActions: [],
|
|
1287
|
-
commands: ["phasegate validate --layer L4 --fail-on-warning --format human"],
|
|
1330
|
+
commands: ["phasegate config:plan --intent l4-strict --apply --json", "phasegate validate --layer L4 --fail-on-warning --format human"],
|
|
1288
1331
|
validations: ["phasegate phasegate:detect-drift --json", "phasegate phasegate:check-ready"],
|
|
1289
1332
|
risks: ["L4 findings may be advisory today but become blocking when fail-on-warning is enabled."],
|
|
1290
1333
|
},
|
|
@@ -1300,7 +1343,7 @@ async function buildConfigChangePlan(rootDir: string, intent: ConfigChangeIntent
|
|
|
1300
1343
|
targets: [".github/workflows/phasegate-aidlc-gate.yml", "phasegate.config.json"],
|
|
1301
1344
|
managedTargets: [".github/workflows/phasegate-aidlc-gate.yml", "phasegate.config.json"],
|
|
1302
1345
|
externalActions: [{ id: "github-actions-first-run", label: "Trigger or inspect the first GitHub Actions PhaseGate run.", command: null, blocking: false }],
|
|
1303
|
-
commands: ["phasegate install --with-ci --apply", "phasegate validate --layer L4 --fail-on-warning"],
|
|
1346
|
+
commands: ["phasegate install --with-ci --apply", "phasegate config:plan --intent ci-fail-on-warning --apply --json", "phasegate validate --layer L4 --fail-on-warning"],
|
|
1304
1347
|
validations: ["phasegate doctor", "phasegate ci:generate-template --type aidlc-gate --render"],
|
|
1305
1348
|
risks: ["Existing warning-only projects may start failing CI after rollout."],
|
|
1306
1349
|
},
|
|
@@ -1316,7 +1359,7 @@ async function buildConfigChangePlan(rootDir: string, intent: ConfigChangeIntent
|
|
|
1316
1359
|
targets: ["phasegate.config.json: quickMode"],
|
|
1317
1360
|
managedTargets: ["phasegate.config.json"],
|
|
1318
1361
|
externalActions: [],
|
|
1319
|
-
commands: ["phasegate check-change-category --paths <changed-files> --format json"],
|
|
1362
|
+
commands: ["phasegate config:plan --intent quick-mode-strict --apply --json", "phasegate check-change-category --paths <changed-files> --format json"],
|
|
1320
1363
|
validations: ["phasegate ci-check --quick --dry-run", "phasegate phasegate:check-ready"],
|
|
1321
1364
|
risks: ["More changes will require Full Mode validation before commit."],
|
|
1322
1365
|
},
|
|
@@ -1324,7 +1367,7 @@ async function buildConfigChangePlan(rootDir: string, intent: ConfigChangeIntent
|
|
|
1324
1367
|
targets: ["phasegate.config.json: planningMode.default", "phasegate.config.json: phaseDependencies.override", "phasegate.config.json: quickMode.relaxedGates"],
|
|
1325
1368
|
managedTargets: ["phasegate.config.json"],
|
|
1326
1369
|
externalActions: [],
|
|
1327
|
-
commands: ["phasegate baseline --dry-run", "phasegate config:plan --intent retrofit-bootstrap --json"],
|
|
1370
|
+
commands: ["phasegate baseline --dry-run", "phasegate config:plan --intent retrofit-bootstrap --json", "phasegate config:plan --intent retrofit-bootstrap --apply --json"],
|
|
1328
1371
|
validations: ["phasegate validate-metadata docs/inception/_shared/*.md", "phasegate check-phase-gate --level 2"],
|
|
1329
1372
|
risks: ["Manual planning mode accepts existing retrofit planning evidence; review the patch before applying it to avoid weakening greenfield projects."],
|
|
1330
1373
|
},
|
|
@@ -1332,7 +1375,7 @@ async function buildConfigChangePlan(rootDir: string, intent: ConfigChangeIntent
|
|
|
1332
1375
|
targets: ["phasegate.config.json: planningMode.default"],
|
|
1333
1376
|
managedTargets: ["phasegate.config.json"],
|
|
1334
1377
|
externalActions: [],
|
|
1335
|
-
commands: ["phasegate config:plan --intent planning-mode-relax --json"],
|
|
1378
|
+
commands: ["phasegate config:plan --intent planning-mode-relax --json", "phasegate config:plan --intent planning-mode-relax --apply --json"],
|
|
1336
1379
|
validations: ["phasegate check-phase-gate --level 2", "phasegate phasegate:check-ready"],
|
|
1337
1380
|
risks: ["Manual planning mode reduces PhaseGate's QA enforcement for plan documents until strict planning is restored."],
|
|
1338
1381
|
},
|
|
@@ -1975,13 +2018,42 @@ async function main(): Promise<void> {
|
|
|
1975
2018
|
}
|
|
1976
2019
|
|
|
1977
2020
|
case "config:plan": {
|
|
1978
|
-
const KNOWN_CONFIG_PLAN_FLAGS = ["--intent", "--dry-run", "--json"];
|
|
2021
|
+
const KNOWN_CONFIG_PLAN_FLAGS = ["--intent", "--dry-run", "--apply", "--json"];
|
|
1979
2022
|
const flagError = validateKnownFlags(args, KNOWN_CONFIG_PLAN_FLAGS);
|
|
1980
2023
|
if (flagError) {
|
|
1981
2024
|
console.error(flagError);
|
|
1982
2025
|
process.exit(2);
|
|
1983
2026
|
}
|
|
2027
|
+
const apply = hasFlag(args, "--apply");
|
|
2028
|
+
if (apply && hasFlag(args, "--dry-run")) {
|
|
2029
|
+
console.error("Error: --apply and --dry-run cannot be used together.");
|
|
2030
|
+
process.exit(2);
|
|
2031
|
+
}
|
|
1984
2032
|
const plan = await buildConfigChangePlan(rootDir, parseConfigChangeIntent(parseFlag(args, "--intent")));
|
|
2033
|
+
if (apply) {
|
|
2034
|
+
try {
|
|
2035
|
+
const applyResult = await applyConfigPlan(rootDir, plan);
|
|
2036
|
+
const output = { ...plan, applyResult };
|
|
2037
|
+
if (json) {
|
|
2038
|
+
console.log(JSON.stringify(output, null, 2));
|
|
2039
|
+
} else {
|
|
2040
|
+
console.log(`phasegate config:plan apply (${plan.intent})`);
|
|
2041
|
+
console.log(`changed: ${applyResult.changed}`);
|
|
2042
|
+
console.log(`backup: ${applyResult.backupPath}`);
|
|
2043
|
+
console.log("Applied operations:");
|
|
2044
|
+
for (const operation of applyResult.appliedOperations) console.log(`- ${operation.op} ${operation.pointer}`);
|
|
2045
|
+
}
|
|
2046
|
+
process.exit(0);
|
|
2047
|
+
} catch (error) {
|
|
2048
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2049
|
+
if (json) {
|
|
2050
|
+
console.log(JSON.stringify({ intent: plan.intent, refused: true, error: message, configPatch: plan.configPatch }, null, 2));
|
|
2051
|
+
} else {
|
|
2052
|
+
console.error(`Error: ${message}`);
|
|
2053
|
+
}
|
|
2054
|
+
process.exit(1);
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
1985
2057
|
if (json) {
|
|
1986
2058
|
console.log(JSON.stringify(plan, null, 2));
|
|
1987
2059
|
} else {
|