phasegate 0.160.6 → 0.160.7
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/package.json +1 -1
- package/scripts/harness/ci-governance/presentation/formatters/ci-template-formatter.ts +3 -1
- package/scripts/harness/installation/application/usecases/run-install.ts +2 -1
- package/scripts/harness/installation/application/usecases/run-reconcile.ts +9 -8
- package/scripts/harness/installation/application/usecases/run-uninstall.ts +13 -2
- package/scripts/harness/installation/presentation/cli/uninstall-handler.ts +4 -2
- package/scripts/harness/main.ts +26 -0
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @layer presentation
|
|
3
3
|
* @unit ci-governance
|
|
4
|
+
* @work-item-id WI-200
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import type { GenerateCiTemplateOutput } from '../../application/dto/generate-ci-template-output.js';
|
|
@@ -15,12 +16,13 @@ export class CiTemplateFormatter {
|
|
|
15
16
|
lines.push(` [${err.code}] ${err.message}`);
|
|
16
17
|
}
|
|
17
18
|
} else {
|
|
18
|
-
lines.push('✓ CI Template
|
|
19
|
+
lines.push('✓ CI Template Plan Ready');
|
|
19
20
|
lines.push(` Template Type: ${output.templateType}`);
|
|
20
21
|
lines.push(` Preset: ${output.presetRef}`);
|
|
21
22
|
lines.push(` Trigger: ${output.triggerCondition}`);
|
|
22
23
|
lines.push(` Validators: ${output.targetValidatorIds.join(', ')}`);
|
|
23
24
|
lines.push(` Fail on Warning: ${output.failOnWarning}`);
|
|
25
|
+
lines.push(' Output: no file written; use --render to print template YAML');
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
return lines.join('\n');
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// @layer application
|
|
3
3
|
// @work-item-id WI-146
|
|
4
4
|
// @work-item-id WI-174
|
|
5
|
+
// @work-item-id WI-198
|
|
5
6
|
// @work-item-id WI-175
|
|
6
7
|
// @work-item-id WI-177
|
|
7
8
|
// @work-item-id WI-182
|
|
@@ -190,7 +191,7 @@ function renderAgentContextTemplate(
|
|
|
190
191
|
.replaceAll("{{PHASEGATE_HUSKY_STATE}}", options.includeHusky ? "managed" : "not managed by this setup run")
|
|
191
192
|
.replaceAll("{{PHASEGATE_CI_STATE}}", options.includeCi ? "managed" : "not managed by this setup run")
|
|
192
193
|
.replaceAll("{{PHASEGATE_COMMANDS}}", commands)
|
|
193
|
-
.replaceAll("{{PHASEGATE_SKILLS}}", options.skillSet === "core" ? "- core skills" : "- all bundled skills")
|
|
194
|
+
.replaceAll("{{PHASEGATE_SKILLS}}", options.skillSet === "core" ? "- `core skills`" : "- `all bundled skills`")
|
|
194
195
|
.replaceAll("{{PHASEGATE_PRESETS}}", "- `minimal`\n- `standard`\n- `full`\n- `custom`")
|
|
195
196
|
.replaceAll("{{PHASEGATE_USER_SECTION}}", "Project-specific agent instructions go here.");
|
|
196
197
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// @layer application
|
|
3
3
|
// @work-item-id WI-148
|
|
4
4
|
// @work-item-id WI-174
|
|
5
|
+
// @work-item-id WI-198
|
|
5
6
|
|
|
6
7
|
import { access, chmod, copyFile, lstat, mkdir, readFile, readlink, symlink, writeFile } from "node:fs/promises";
|
|
7
8
|
import { dirname, join, relative, resolve } from "node:path";
|
|
@@ -156,12 +157,12 @@ function reconcileManagedMarkdown(existing: string | null, incoming: string): st
|
|
|
156
157
|
|
|
157
158
|
function renderAgentContextTemplate(template: string): string {
|
|
158
159
|
const commands = [
|
|
159
|
-
"
|
|
160
|
-
"
|
|
161
|
-
"
|
|
162
|
-
"
|
|
163
|
-
"
|
|
164
|
-
].join("\n");
|
|
160
|
+
"phasegate doctor",
|
|
161
|
+
"phasegate phasegate:check-ready",
|
|
162
|
+
"phasegate validate --layer L2 --format human",
|
|
163
|
+
"phasegate setup:agent --dry-run",
|
|
164
|
+
"phasegate config:plan --intent l4-strict --dry-run",
|
|
165
|
+
].map((command) => `- \`${command}\``).join("\n");
|
|
165
166
|
return template
|
|
166
167
|
.replaceAll("{{PHASEGATE_AGENT}}", "both")
|
|
167
168
|
.replaceAll("{{PHASEGATE_SKILLS_MODE}}", "all")
|
|
@@ -169,7 +170,7 @@ function renderAgentContextTemplate(template: string): string {
|
|
|
169
170
|
.replaceAll("{{PHASEGATE_HUSKY_STATE}}", "managed")
|
|
170
171
|
.replaceAll("{{PHASEGATE_CI_STATE}}", "managed")
|
|
171
172
|
.replaceAll("{{PHASEGATE_COMMANDS}}", commands)
|
|
172
|
-
.replaceAll("{{PHASEGATE_SKILLS}}", "- all bundled skills")
|
|
173
|
+
.replaceAll("{{PHASEGATE_SKILLS}}", "- `all bundled skills`")
|
|
173
174
|
.replaceAll("{{PHASEGATE_PRESETS}}", "- `minimal`\n- `standard`\n- `full`\n- `custom`")
|
|
174
175
|
.replaceAll("{{PHASEGATE_USER_SECTION}}", "Project-specific agent instructions go here.");
|
|
175
176
|
}
|
|
@@ -299,7 +300,7 @@ export class RunReconcileUseCase {
|
|
|
299
300
|
const matchesManifest = currentHash.equals(entry.hash);
|
|
300
301
|
const rawTemplate = target.templatePath ? await readFile(join(input.harnessRoot, target.templatePath), "utf8") : "";
|
|
301
302
|
const template = target.strategy === "markdown-managed" ? renderAgentContextTemplate(rawTemplate) : rawTemplate;
|
|
302
|
-
const next = entry.mode === "created" && target.strategy !== "package-json"
|
|
303
|
+
const next = entry.mode === "created" && target.strategy !== "package-json" && target.strategy !== "markdown-managed"
|
|
303
304
|
? template
|
|
304
305
|
: this.reconcileContent(target, before, template, input.phasegateVersion);
|
|
305
306
|
const changed = before !== next;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// @layer application
|
|
3
3
|
// @work-item-id WI-147
|
|
4
4
|
// @work-item-id WI-174
|
|
5
|
+
// @work-item-id WI-199
|
|
5
6
|
|
|
6
7
|
import { access, copyFile, lstat, mkdir, readFile, readlink, rm, rmdir, writeFile } from "node:fs/promises";
|
|
7
8
|
import { dirname, join, relative, resolve } from "node:path";
|
|
@@ -19,6 +20,7 @@ export interface UninstallPlanItem {
|
|
|
19
20
|
readonly repairMode: RepairMode;
|
|
20
21
|
readonly strategy: StrategyType;
|
|
21
22
|
readonly changed: boolean;
|
|
23
|
+
readonly protected: boolean;
|
|
22
24
|
readonly summary: string;
|
|
23
25
|
readonly diff: string;
|
|
24
26
|
readonly skillHint: string | null;
|
|
@@ -46,6 +48,7 @@ const SHELL_END = "# === phasegate managed (END) ===";
|
|
|
46
48
|
const MARKDOWN_BEGIN = "<!-- phasegate:managed-section:start -->";
|
|
47
49
|
const MARKDOWN_END = "<!-- phasegate:managed-section:end -->";
|
|
48
50
|
const PHASEGATE_SCRIPT_PREFIX = "phasegate:";
|
|
51
|
+
const PROTECTED_UNINSTALL_PATHS = new Set(["package.json", "package-lock.json"]);
|
|
49
52
|
|
|
50
53
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
51
54
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -175,7 +178,7 @@ export class RunUninstallUseCase {
|
|
|
175
178
|
const outcome = await this.planEntry(input, entry);
|
|
176
179
|
outcomes.push({ entry, ...outcome });
|
|
177
180
|
plan.push(outcome.item);
|
|
178
|
-
if (input.apply && outcome.item.changed && (outcome.item
|
|
181
|
+
if (input.apply && outcome.item.changed && this.requiresForce(outcome.item) && !input.force) {
|
|
179
182
|
refused.push({ ...outcome.item, action: "refuse" });
|
|
180
183
|
}
|
|
181
184
|
}
|
|
@@ -372,6 +375,14 @@ export class RunUninstallUseCase {
|
|
|
372
375
|
diff: string,
|
|
373
376
|
skillHint: string | null,
|
|
374
377
|
): UninstallPlanItem {
|
|
375
|
-
return { path, action, repairMode, strategy, changed, summary, diff, skillHint };
|
|
378
|
+
return { path, action, repairMode, strategy, changed, protected: this.isProtectedPath(path), summary, diff, skillHint };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
private requiresForce(item: UninstallPlanItem): boolean {
|
|
382
|
+
return item.protected || item.repairMode === "ai-assisted" || item.repairMode === "manual";
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
private isProtectedPath(path: string): boolean {
|
|
386
|
+
return PROTECTED_UNINSTALL_PATHS.has(path);
|
|
376
387
|
}
|
|
377
388
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @unit installation
|
|
2
2
|
// @layer presentation
|
|
3
3
|
// @work-item-id WI-147
|
|
4
|
+
// @work-item-id WI-199
|
|
4
5
|
|
|
5
6
|
import type { RunUninstallUseCase } from "../../application/usecases/run-uninstall.js";
|
|
6
7
|
|
|
@@ -33,14 +34,15 @@ export class UninstallHandler {
|
|
|
33
34
|
input.apply ? "phasegate uninstall apply" : "phasegate uninstall dry-run",
|
|
34
35
|
...result.plan.map((item) => {
|
|
35
36
|
const hint = item.skillHint ? `; hint: ${item.skillHint}` : "";
|
|
36
|
-
|
|
37
|
+
const protectedMarker = item.protected ? "; protected: true" : "";
|
|
38
|
+
return `- ${item.path}: ${item.action} (${item.repairMode}, ${item.strategy}${protectedMarker}); diff: ${item.diff}${hint}`;
|
|
37
39
|
}),
|
|
38
40
|
];
|
|
39
41
|
if (result.backupDir !== null) lines.push(`backups: ${result.backupDir}`);
|
|
40
42
|
if (result.archivedManifestPath !== null) lines.push(`archived manifest: ${result.archivedManifestPath}`);
|
|
41
43
|
if (result.refused.length > 0) {
|
|
42
44
|
lines.push("");
|
|
43
|
-
lines.push("Refused ai-assisted
|
|
45
|
+
lines.push("Refused protected, ai-assisted, or manual targets. Re-run with --force after reviewing the plan.");
|
|
44
46
|
}
|
|
45
47
|
return {
|
|
46
48
|
stdout: lines.join("\n"),
|
package/scripts/harness/main.ts
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* @work-item-id WI-191
|
|
12
12
|
* @work-item-id WI-195
|
|
13
13
|
* @work-item-id WI-196
|
|
14
|
+
* @work-item-id WI-197
|
|
15
|
+
* @work-item-id WI-200
|
|
14
16
|
*
|
|
15
17
|
* Phasegate CLI エントリポイント。
|
|
16
18
|
* 各Unitの Composition Root からハンドラーを取得し、コマンドに応じてディスパッチする。
|
|
@@ -2402,6 +2404,16 @@ async function main(): Promise<void> {
|
|
|
2402
2404
|
break;
|
|
2403
2405
|
}
|
|
2404
2406
|
|
|
2407
|
+
case "status": {
|
|
2408
|
+
console.error("Warning: 'phasegate status' is deprecated; use 'phasegate phasegate:status'.");
|
|
2409
|
+
const mod = createHarnessApiModule();
|
|
2410
|
+
const flags: Record<string, boolean | string> = {};
|
|
2411
|
+
if (json) flags.json = true;
|
|
2412
|
+
await mod.handlers.status.handle({}, flags);
|
|
2413
|
+
if (!json) await printStoryReflectionStatusLine(rootDir);
|
|
2414
|
+
break;
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2405
2417
|
case "phasegate:lint": {
|
|
2406
2418
|
const mod = createHarnessApiModule();
|
|
2407
2419
|
const flags: Record<string, boolean | string> = {};
|
|
@@ -2420,6 +2432,15 @@ async function main(): Promise<void> {
|
|
|
2420
2432
|
break;
|
|
2421
2433
|
}
|
|
2422
2434
|
|
|
2435
|
+
case "complete-check": {
|
|
2436
|
+
console.error("Warning: 'phasegate complete-check' is deprecated; use 'phasegate phasegate:complete-check'.");
|
|
2437
|
+
const mod = createHarnessApiModule();
|
|
2438
|
+
const flags: Record<string, boolean | string> = {};
|
|
2439
|
+
if (json) flags.json = true;
|
|
2440
|
+
await mod.handlers.completeCheck.handle({}, flags);
|
|
2441
|
+
break;
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2423
2444
|
case "phasegate:impact-analysis": {
|
|
2424
2445
|
const mod = createHarnessApiModule();
|
|
2425
2446
|
const storyId = args[1] && !args[1].startsWith("--") ? args[1] : (parseFlag(args, "--story-id") ?? "");
|
|
@@ -2466,6 +2487,11 @@ Examples:
|
|
|
2466
2487
|
phasegate ci:generate-template --preset strict --type pre-commit --render`);
|
|
2467
2488
|
process.exit(0);
|
|
2468
2489
|
}
|
|
2490
|
+
const flagError = validateKnownFlags(args.slice(1), ["--preset", "--type", "--render", "--json", "--help"]);
|
|
2491
|
+
if (flagError !== null) {
|
|
2492
|
+
console.error(flagError);
|
|
2493
|
+
process.exit(2);
|
|
2494
|
+
}
|
|
2469
2495
|
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
2470
2496
|
const presetId = parseFlag(args, "--preset") ?? "standard";
|
|
2471
2497
|
const templateType = parseFlag(args, "--type") ?? "aidlc-gate";
|