phasegate 0.144.1 → 0.145.1
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 +20 -0
- package/package.json +1 -1
- package/scripts/harness/installation/application/checks/check-utils.ts +65 -0
- package/scripts/harness/installation/application/checks/ci-workflow-missing-check.ts +29 -0
- package/scripts/harness/installation/application/checks/claude-hook-missing-check.ts +45 -0
- package/scripts/harness/installation/application/checks/claude-skills-symlink-check.ts +25 -0
- package/scripts/harness/installation/application/checks/codex-hook-missing-check.ts +44 -0
- package/scripts/harness/installation/application/checks/codex-skills-symlink-check.ts +25 -0
- package/scripts/harness/installation/application/checks/husky-commit-msg-missing-check.ts +25 -0
- package/scripts/harness/installation/application/checks/husky-pre-commit-missing-check.ts +37 -0
- package/scripts/harness/installation/application/checks/husky-pre-push-missing-check.ts +25 -0
- package/scripts/harness/installation/application/checks/package-json-devdep-missing-check.ts +37 -0
- package/scripts/harness/installation/application/ports/file-inspector-port.ts +11 -0
- package/scripts/harness/installation/application/ports/hash-calculator-port.ts +9 -0
- package/scripts/harness/installation/application/ports/manifest-repository-port.ts +12 -0
- package/scripts/harness/installation/application/usecases/run-doctor-diagnostics.ts +44 -0
- package/scripts/harness/installation/application/wrappers/skill-deployer-manifest-builder.ts +38 -0
- package/scripts/harness/installation/composition-root.ts +39 -0
- package/scripts/harness/installation/domain/check-id.ts +21 -0
- package/scripts/harness/installation/domain/deployment-entry.ts +86 -0
- package/scripts/harness/installation/domain/deployment-manifest.ts +90 -0
- package/scripts/harness/installation/domain/diagnostic-finding.ts +71 -0
- package/scripts/harness/installation/domain/diagnostic-report.ts +52 -0
- package/scripts/harness/installation/domain/hash.ts +30 -0
- package/scripts/harness/installation/domain/managed-block.ts +37 -0
- package/scripts/harness/installation/domain/ports/heuristic-check.ts +12 -0
- package/scripts/harness/installation/domain/ports/merge-strategy.ts +13 -0
- package/scripts/harness/installation/domain/ports/reconcile-strategy.ts +15 -0
- package/scripts/harness/installation/domain/ports/uninstall-reverse-strategy.ts +10 -0
- package/scripts/harness/installation/domain/repair-mode.ts +11 -0
- package/scripts/harness/installation/domain/repair-table.ts +41 -0
- package/scripts/harness/installation/domain/suggested-skill.ts +37 -0
- package/scripts/harness/installation/infrastructure/adapters/file-system-manifest-repository-adapter.ts +62 -0
- package/scripts/harness/installation/infrastructure/adapters/node-crypto-hash-adapter.ts +13 -0
- package/scripts/harness/installation/infrastructure/adapters/node-fs-file-inspector-adapter.ts +53 -0
- package/scripts/harness/installation/presentation/cli/doctor-handler.ts +51 -0
- package/scripts/harness/installation/presentation/formatters/diagnostic-report-formatter.ts +53 -0
- package/scripts/harness/main.ts +171 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// @unit installation
|
|
2
|
+
// @layer presentation
|
|
3
|
+
// @work-item-id WI-145
|
|
4
|
+
|
|
5
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
6
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
7
|
+
import type { RunDoctorDiagnosticsUseCase } from "../../application/usecases/run-doctor-diagnostics.js";
|
|
8
|
+
import { DiagnosticReportFormatter } from "../formatters/diagnostic-report-formatter.js";
|
|
9
|
+
|
|
10
|
+
export interface DoctorHandlerInput {
|
|
11
|
+
readonly projectRoot: string;
|
|
12
|
+
readonly strict: boolean;
|
|
13
|
+
readonly json: boolean;
|
|
14
|
+
readonly reportOut: string | null;
|
|
15
|
+
readonly phasegateVersion: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface DoctorHandlerOutput {
|
|
19
|
+
readonly stdout: string;
|
|
20
|
+
readonly exitCode: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class DoctorHandler {
|
|
24
|
+
constructor(
|
|
25
|
+
private readonly useCase: RunDoctorDiagnosticsUseCase,
|
|
26
|
+
private readonly formatter = new DiagnosticReportFormatter(),
|
|
27
|
+
) {}
|
|
28
|
+
|
|
29
|
+
async execute(input: DoctorHandlerInput): Promise<DoctorHandlerOutput> {
|
|
30
|
+
const result = await this.useCase.execute({
|
|
31
|
+
projectRoot: input.projectRoot,
|
|
32
|
+
strict: input.strict,
|
|
33
|
+
});
|
|
34
|
+
const formatInput = {
|
|
35
|
+
report: result.report,
|
|
36
|
+
phasegateVersion: input.phasegateVersion,
|
|
37
|
+
projectRoot: input.projectRoot,
|
|
38
|
+
exitCode: result.exitCode,
|
|
39
|
+
};
|
|
40
|
+
const jsonOutput = this.formatter.formatJson(formatInput);
|
|
41
|
+
if (input.reportOut !== null) {
|
|
42
|
+
const reportPath = isAbsolute(input.reportOut) ? input.reportOut : join(input.projectRoot, input.reportOut);
|
|
43
|
+
await mkdir(dirname(reportPath), { recursive: true });
|
|
44
|
+
await writeFile(reportPath, `${jsonOutput}\n`, "utf8");
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
stdout: input.json ? jsonOutput : this.formatter.formatHuman(formatInput),
|
|
48
|
+
exitCode: result.exitCode,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// @unit installation
|
|
2
|
+
// @layer presentation
|
|
3
|
+
// @work-item-id WI-145
|
|
4
|
+
|
|
5
|
+
import type { DiagnosticReport } from "../../domain/diagnostic-report.js";
|
|
6
|
+
|
|
7
|
+
export interface DiagnosticReportFormatterInput {
|
|
8
|
+
readonly report: DiagnosticReport;
|
|
9
|
+
readonly phasegateVersion: string;
|
|
10
|
+
readonly projectRoot: string;
|
|
11
|
+
readonly exitCode: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class DiagnosticReportFormatter {
|
|
15
|
+
formatJson(input: DiagnosticReportFormatterInput): string {
|
|
16
|
+
return JSON.stringify(
|
|
17
|
+
{
|
|
18
|
+
schemaVersion: "1.0",
|
|
19
|
+
phasegateVersion: input.phasegateVersion,
|
|
20
|
+
projectRoot: input.projectRoot,
|
|
21
|
+
overallStatus: input.report.overallStatus,
|
|
22
|
+
findings: input.report.findings.map((finding) => finding.toJSON()),
|
|
23
|
+
exitCode: input.exitCode,
|
|
24
|
+
},
|
|
25
|
+
null,
|
|
26
|
+
2,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
formatHuman(input: DiagnosticReportFormatterInput): string {
|
|
31
|
+
const lines = [
|
|
32
|
+
`phasegate doctor v${input.phasegateVersion}`,
|
|
33
|
+
`Project: ${input.projectRoot}`,
|
|
34
|
+
"",
|
|
35
|
+
];
|
|
36
|
+
for (const finding of input.report.findings) {
|
|
37
|
+
lines.push(`[${finding.severity}] ${finding.checkId}: ${finding.message}`);
|
|
38
|
+
lines.push(` target: ${finding.target}`);
|
|
39
|
+
lines.push(` repairMode: ${finding.repairMode}`);
|
|
40
|
+
if (finding.repairHint !== null) lines.push(` fix: ${finding.repairHint}`);
|
|
41
|
+
if (finding.suggestedSkill !== null) {
|
|
42
|
+
lines.push(` suggested: ${finding.suggestedSkill.skillName} (${finding.suggestedSkill.invokeCommand})`);
|
|
43
|
+
lines.push(` rationale: ${finding.suggestedSkill.rationale}`);
|
|
44
|
+
}
|
|
45
|
+
lines.push("");
|
|
46
|
+
}
|
|
47
|
+
const redCount = input.report.findings.filter((finding) => finding.severity === "red").length;
|
|
48
|
+
const warnCount = input.report.findings.filter((finding) => finding.severity === "warn").length;
|
|
49
|
+
lines.push(`Status: ${input.report.overallStatus.toUpperCase()} (${input.report.findings.length} findings: ${redCount} red, ${warnCount} warn)`);
|
|
50
|
+
lines.push(`Exit: ${input.exitCode}`);
|
|
51
|
+
return lines.join("\n");
|
|
52
|
+
}
|
|
53
|
+
}
|
package/scripts/harness/main.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* 起動時に config-foundation で設定を解決し、他Unit に注入する(Cross-unit wiring)。
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { access, readFile as fsReadFile } from "node:fs/promises";
|
|
11
|
+
import { access, readFile as fsReadFile, readlink as fsReadlink, writeFile as fsWriteFile } from "node:fs/promises";
|
|
12
12
|
import { dirname, join, resolve } from "node:path";
|
|
13
13
|
import { createAdrFoundationModule } from "./adr-foundation/composition-root.js";
|
|
14
14
|
import { createBiomeAstEngineModule } from "./biome-ast-engine/composition-root.js";
|
|
@@ -24,6 +24,9 @@ import {
|
|
|
24
24
|
} from "./config-foundation/infrastructure/repositories/file-system-config-repository.js";
|
|
25
25
|
import { createHarnessApiModule } from "./harness-api/composition-root.js";
|
|
26
26
|
import { createHarnessErrorModule } from "./harness-error/composition-root.js";
|
|
27
|
+
import { createInstallationModule } from "./installation/composition-root.js";
|
|
28
|
+
import { SkillDeployerManifestBuilder, type DeployManifestRecord } from "./installation/application/wrappers/skill-deployer-manifest-builder.js";
|
|
29
|
+
import { NodeCryptoHashAdapter } from "./installation/infrastructure/adapters/node-crypto-hash-adapter.js";
|
|
27
30
|
import { CheckStoryReflectionUseCase } from "./phase-dependency-model/application/usecases/check-story-reflection-usecase.js";
|
|
28
31
|
import { createPhaseDependencyModelModule } from "./phase-dependency-model/composition-root.js";
|
|
29
32
|
import { StoryReflectionChecker } from "./phase-dependency-model/domain/services/story-reflection-checker.js";
|
|
@@ -84,6 +87,60 @@ async function pathExists(path: string): Promise<boolean> {
|
|
|
84
87
|
}
|
|
85
88
|
}
|
|
86
89
|
|
|
90
|
+
interface PackageJsonDocument {
|
|
91
|
+
readonly [key: string]: unknown;
|
|
92
|
+
readonly dependencies?: unknown;
|
|
93
|
+
readonly devDependencies?: unknown;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface PackageDependencyResult {
|
|
97
|
+
readonly created: boolean;
|
|
98
|
+
readonly updated: boolean;
|
|
99
|
+
readonly alreadyPresent: boolean;
|
|
100
|
+
readonly skipped: boolean;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isJsonRecord(value: unknown): value is Record<string, unknown> {
|
|
104
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function ensurePhasegatePackageDependency(rootDir: string, version: string): Promise<PackageDependencyResult> {
|
|
108
|
+
const packageJsonPath = join(rootDir, "package.json");
|
|
109
|
+
let pkg: PackageJsonDocument = {};
|
|
110
|
+
let created = false;
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const raw = await fsReadFile(packageJsonPath, "utf-8");
|
|
114
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
115
|
+
if (!isJsonRecord(parsed)) {
|
|
116
|
+
return { created: false, updated: false, alreadyPresent: false, skipped: true };
|
|
117
|
+
}
|
|
118
|
+
pkg = parsed;
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
121
|
+
return { created: false, updated: false, alreadyPresent: false, skipped: true };
|
|
122
|
+
}
|
|
123
|
+
created = true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (
|
|
127
|
+
(isJsonRecord(pkg.devDependencies) && pkg.devDependencies.phasegate !== undefined) ||
|
|
128
|
+
(isJsonRecord(pkg.dependencies) && pkg.dependencies.phasegate !== undefined)
|
|
129
|
+
) {
|
|
130
|
+
return { created: false, updated: false, alreadyPresent: true, skipped: false };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
pkg = {
|
|
134
|
+
...pkg,
|
|
135
|
+
devDependencies: {
|
|
136
|
+
...(isJsonRecord(pkg.devDependencies) ? pkg.devDependencies : {}),
|
|
137
|
+
phasegate: `^${version}`,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
await fsWriteFile(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`, "utf-8");
|
|
141
|
+
return { created, updated: !created, alreadyPresent: false, skipped: false };
|
|
142
|
+
}
|
|
143
|
+
|
|
87
144
|
function printUsage(): void {
|
|
88
145
|
const usage = `
|
|
89
146
|
Usage: phasegate <command> [options]
|
|
@@ -93,6 +150,10 @@ Setup:
|
|
|
93
150
|
(--name <project-name>, --preset <full|standard|minimal|custom>,
|
|
94
151
|
--skills <core|all>, --agent <claude|codex|both>, --with-husky, --with-ci, --yes)
|
|
95
152
|
update-skills Re-deploy skills from current harness version
|
|
153
|
+
doctor Diagnose silent installation failures (--json, --strict, --report-out <path>)
|
|
154
|
+
install Install phasegate managed files (stub until WI-146)
|
|
155
|
+
uninstall Uninstall phasegate managed files (stub until WI-147)
|
|
156
|
+
reconcile Reconcile phasegate managed files (stub until WI-148)
|
|
96
157
|
|
|
97
158
|
Commands:
|
|
98
159
|
enable-feature <name> Enable a harness feature
|
|
@@ -181,6 +242,40 @@ function hasFlag(args: readonly string[], flag: string): boolean {
|
|
|
181
242
|
return args.includes(flag);
|
|
182
243
|
}
|
|
183
244
|
|
|
245
|
+
async function createFileManifestRecord(
|
|
246
|
+
rootDir: string,
|
|
247
|
+
relativePath: string,
|
|
248
|
+
mode: "created" | "merged" = "created",
|
|
249
|
+
): Promise<DeployManifestRecord | null> {
|
|
250
|
+
try {
|
|
251
|
+
const contentForHash = await fsReadFile(join(rootDir, relativePath), "utf8");
|
|
252
|
+
return { path: relativePath, mode, contentForHash };
|
|
253
|
+
} catch {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function createSymlinkManifestRecord(rootDir: string, relativePath: string): Promise<DeployManifestRecord | null> {
|
|
259
|
+
try {
|
|
260
|
+
const target = await fsReadlink(join(rootDir, relativePath));
|
|
261
|
+
return { path: relativePath, mode: "symlink", contentForHash: target };
|
|
262
|
+
} catch {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function saveInstallationManifest(
|
|
268
|
+
rootDir: string,
|
|
269
|
+
version: string,
|
|
270
|
+
records: readonly (DeployManifestRecord | null | Promise<DeployManifestRecord | null>)[],
|
|
271
|
+
): Promise<void> {
|
|
272
|
+
const filteredRecords = (await Promise.all(records)).filter((record) => record !== null);
|
|
273
|
+
if (filteredRecords.length === 0) return;
|
|
274
|
+
const mod = createInstallationModule();
|
|
275
|
+
const builder = new SkillDeployerManifestBuilder(new NodeCryptoHashAdapter());
|
|
276
|
+
await mod.manifestRepository.save(rootDir, builder.build(version, filteredRecords));
|
|
277
|
+
}
|
|
278
|
+
|
|
184
279
|
/**
|
|
185
280
|
* WI-094 / ADR-017: CLI で boolean フラグを tri-state に解釈する。
|
|
186
281
|
* - `--<flag>` 指定 → true
|
|
@@ -700,6 +795,7 @@ async function main(): Promise<void> {
|
|
|
700
795
|
const deployClaude = agent === "claude" || agent === "both";
|
|
701
796
|
const deployCodex = agent === "codex" || agent === "both";
|
|
702
797
|
const result = await deploySkills(harnessRoot, rootDir, skillSet);
|
|
798
|
+
const packageResult = await ensurePhasegatePackageDependency(rootDir, result.version);
|
|
703
799
|
const skillLinkResult = await deployAgentSkillLinks(rootDir, {
|
|
704
800
|
claude: deployClaude,
|
|
705
801
|
codex: deployCodex,
|
|
@@ -722,9 +818,40 @@ async function main(): Promise<void> {
|
|
|
722
818
|
const huskyCommitMsgResult = withHusky ? await deployHuskyCommitMsgHook(harnessRoot, rootDir) : null;
|
|
723
819
|
const huskyPrePushResult = withHusky ? await deployHuskyPrePushHook(harnessRoot, rootDir) : null;
|
|
724
820
|
const ciWorkflowResult = withCi ? await deployCiWorkflows(harnessRoot, rootDir) : null;
|
|
821
|
+
await saveInstallationManifest(rootDir, result.version, [
|
|
822
|
+
...result.deployedSkills.map((skill) => ({
|
|
823
|
+
path: join("skills", skill),
|
|
824
|
+
mode: "created" as const,
|
|
825
|
+
contentForHash: `${result.version}:${skill}`,
|
|
826
|
+
})),
|
|
827
|
+
await createFileManifestRecord(rootDir, join("skills", ".harness-version")),
|
|
828
|
+
packageResult.created || packageResult.updated ? await createFileManifestRecord(rootDir, "package.json") : null,
|
|
829
|
+
configResult.created ? await createFileManifestRecord(rootDir, "phasegate.config.json") : null,
|
|
830
|
+
hooksResult.settingsCreated ? await createFileManifestRecord(rootDir, join(".claude", "settings.json")) : null,
|
|
831
|
+
hooksResult.hookConfigGenerated
|
|
832
|
+
? await createFileManifestRecord(rootDir, join(".claude", "scripts", "hook-config.json"))
|
|
833
|
+
: null,
|
|
834
|
+
skillLinkResult.claude !== null ? await createSymlinkManifestRecord(rootDir, join(".claude", "skills")) : null,
|
|
835
|
+
codexResult?.created ? await createFileManifestRecord(rootDir, join(".codex", "hooks.json")) : null,
|
|
836
|
+
skillLinkResult.codex !== null ? await createSymlinkManifestRecord(rootDir, join(".codex", "skills")) : null,
|
|
837
|
+
...designDocsResult.copiedFiles.map((path) => createFileManifestRecord(rootDir, path)),
|
|
838
|
+
huskyResult?.created ? await createFileManifestRecord(rootDir, join(".husky", "pre-commit")) : null,
|
|
839
|
+
huskyCommitMsgResult?.created ? await createFileManifestRecord(rootDir, join(".husky", "commit-msg")) : null,
|
|
840
|
+
huskyPrePushResult?.created ? await createFileManifestRecord(rootDir, join(".husky", "pre-push")) : null,
|
|
841
|
+
...(ciWorkflowResult?.copiedFiles.map((path) => createFileManifestRecord(rootDir, path)) ?? []),
|
|
842
|
+
]);
|
|
725
843
|
console.log(
|
|
726
844
|
`✓ Skills deployed to ${result.targetDir} (${result.deployedSkills.length} skills, set: ${skillSet})`,
|
|
727
845
|
);
|
|
846
|
+
if (packageResult.created) {
|
|
847
|
+
console.log(`✓ package.json created with phasegate devDependency`);
|
|
848
|
+
} else if (packageResult.updated) {
|
|
849
|
+
console.log(`✓ package.json updated with phasegate devDependency`);
|
|
850
|
+
} else if (packageResult.alreadyPresent) {
|
|
851
|
+
console.log(` package.json already declares phasegate, skipped`);
|
|
852
|
+
} else if (packageResult.skipped) {
|
|
853
|
+
console.log(` package.json could not be updated, skipped`);
|
|
854
|
+
}
|
|
728
855
|
if (configResult.created) {
|
|
729
856
|
console.log(`✓ phasegate.config.json created`);
|
|
730
857
|
} else {
|
|
@@ -854,11 +981,54 @@ async function main(): Promise<void> {
|
|
|
854
981
|
claude: shouldLinkClaude,
|
|
855
982
|
codex: shouldLinkCodex,
|
|
856
983
|
});
|
|
984
|
+
await saveInstallationManifest(rootDir, current, [
|
|
985
|
+
...result.deployedSkills.map((skill) => ({
|
|
986
|
+
path: join("skills", skill),
|
|
987
|
+
mode: "created" as const,
|
|
988
|
+
contentForHash: `${current}:${skill}`,
|
|
989
|
+
})),
|
|
990
|
+
await createFileManifestRecord(rootDir, join("skills", ".harness-version")),
|
|
991
|
+
shouldLinkClaude ? await createSymlinkManifestRecord(rootDir, join(".claude", "skills")) : null,
|
|
992
|
+
shouldLinkCodex ? await createSymlinkManifestRecord(rootDir, join(".codex", "skills")) : null,
|
|
993
|
+
]);
|
|
857
994
|
console.log(`✓ Skills updated (${result.deployedSkills.length} skills redeployed, set: ${updateSkillSet})`);
|
|
858
995
|
process.exit(0);
|
|
859
996
|
break;
|
|
860
997
|
}
|
|
861
998
|
|
|
999
|
+
case "doctor": {
|
|
1000
|
+
const mod = createInstallationModule();
|
|
1001
|
+
const phasegateVersion = await getHarnessVersion(harnessRoot);
|
|
1002
|
+
const result = await mod.doctorHandler.execute({
|
|
1003
|
+
projectRoot: rootDir,
|
|
1004
|
+
strict: hasFlag(args, "--strict"),
|
|
1005
|
+
json,
|
|
1006
|
+
reportOut: parseFlag(args, "--report-out") ?? null,
|
|
1007
|
+
phasegateVersion,
|
|
1008
|
+
});
|
|
1009
|
+
console.log(result.stdout);
|
|
1010
|
+
process.exit(result.exitCode);
|
|
1011
|
+
break;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
case "install": {
|
|
1015
|
+
console.error("Not yet implemented: phasegate install is owned by WI-146");
|
|
1016
|
+
process.exit(2);
|
|
1017
|
+
break;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
case "uninstall": {
|
|
1021
|
+
console.error("Not yet implemented: phasegate uninstall is owned by WI-147");
|
|
1022
|
+
process.exit(2);
|
|
1023
|
+
break;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
case "reconcile": {
|
|
1027
|
+
console.error("Not yet implemented: phasegate reconcile is owned by WI-148");
|
|
1028
|
+
process.exit(2);
|
|
1029
|
+
break;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
862
1032
|
// ── config-foundation ──
|
|
863
1033
|
case "enable-feature": {
|
|
864
1034
|
const mod = createConfigFoundationModule();
|