phasegate 0.191.0 → 0.222.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 +15 -0
- package/README.ja.md +52 -15
- package/README.md +39 -11
- package/docs/ADR/030-injection-threat-model-and-trust-root.md +145 -0
- package/docs/guide/hooks-integration.md +101 -1
- package/docs/guide/installation.md +1 -1
- package/docs/guide/quick-vs-full-mode.md +1 -1
- package/docs/guide/skills-overview.md +7 -8
- package/package.json +1 -1
- package/scripts/harness/agent-integration/presentation/phasegate-status-context.ts +131 -63
- package/scripts/harness/agent-integration/presentation/session-start-hook.ts +31 -4
- package/scripts/harness/agent-integration/presentation/spotlight.ts +65 -0
- package/scripts/harness/biome-ast-engine/application/mappers/build-harness-error-payload-output-mapper.ts +3 -3
- package/scripts/harness/biome-ast-engine/infrastructure/mappers/rule-violation-code-mapper.ts +3 -3
- package/scripts/harness/biome-ast-engine/presentation/cli/harness-lint-command-handler.ts +6 -4
- package/scripts/harness/ci-governance/application/dto/pin-integrity-input.ts +8 -0
- package/scripts/harness/ci-governance/application/dto/pin-integrity-output.ts +9 -0
- package/scripts/harness/ci-governance/application/dto/verify-integrity-input.ts +7 -0
- package/scripts/harness/ci-governance/application/dto/verify-integrity-output.ts +10 -0
- package/scripts/harness/ci-governance/application/usecases/pin-integrity-usecase.ts +60 -0
- package/scripts/harness/ci-governance/application/usecases/verify-integrity-usecase.ts +46 -0
- package/scripts/harness/ci-governance/composition-root.ts +67 -64
- package/scripts/harness/ci-governance/domain/ports/integrity-manifest-repository-port.ts +14 -0
- package/scripts/harness/ci-governance/domain/ports/sha256-hasher-port.ts +10 -0
- package/scripts/harness/ci-governance/domain/services/integrity-checker.ts +42 -0
- package/scripts/harness/ci-governance/domain/value-objects/integrity-drift.ts +16 -0
- package/scripts/harness/ci-governance/domain/value-objects/integrity-manifest.ts +49 -0
- package/scripts/harness/ci-governance/domain/value-objects/integrity-target.ts +40 -0
- package/scripts/harness/ci-governance/infrastructure/adapters/file-system-sha256-hasher-adapter.ts +17 -0
- package/scripts/harness/ci-governance/infrastructure/adapters/harness-api-command-existence-adapter.ts +7 -73
- package/scripts/harness/ci-governance/infrastructure/adapters/integrity-manifest-json-repository-adapter.ts +83 -0
- package/scripts/harness/ci-governance/presentation/handlers/integrity-handler.ts +76 -0
- package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +60 -28
- package/scripts/harness/harness-api/domain/value-objects/ci-check-result.ts +33 -8
- package/scripts/harness/harness-api/domain/value-objects/known-harness-commands.ts +90 -0
- package/scripts/harness/harness-error/infrastructure/registry/l1-error-definitions.ts +9 -9
- package/scripts/harness/installation/application/bundled-skill-selection.ts +2 -5
- package/scripts/harness/installation/application/usecases/run-reconcile.ts +69 -1
- package/scripts/harness/main.ts +257 -105
- package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.ts +65 -3
- package/scripts/harness/quick-mode/domain/services/quick-mode-judgment-engine.ts +44 -40
- package/scripts/harness/setup/skill-deployer.ts +2 -4
- package/scripts/harness/validator-system/application/use-cases/run-l2-validators-usecase.ts +82 -49
- package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +133 -53
- package/scripts/harness/validator-system/composition-root.ts +153 -99
- package/scripts/harness/validator-system/domain/ports/coverage-attestation-gating-policy-port.ts +14 -0
- package/scripts/harness/validator-system/domain/ports/coverage-attestation-verification-policy-port.ts +30 -0
- package/scripts/harness/validator-system/domain/ports/injection-scan-policy-port.ts +14 -0
- package/scripts/harness/validator-system/domain/services/coverage-attestation-gating-service.ts +56 -0
- package/scripts/harness/validator-system/domain/services/coverage-attestation-verification-service.ts +45 -0
- package/scripts/harness/validator-system/domain/services/injection-pattern-scan-service.ts +118 -0
- package/scripts/harness/validator-system/domain/value-objects/attestation-verification-report.ts +59 -0
- package/scripts/harness/validator-system/domain/value-objects/coverage-gating-report.ts +67 -0
- package/scripts/harness/validator-system/domain/value-objects/injection-scan-report.ts +55 -0
- package/scripts/harness/validator-system/domain/value-objects/validator-id.ts +37 -31
- package/scripts/harness/validator-system/infrastructure/adapters/adr-foundation-reference-adapter.ts +13 -7
- package/scripts/harness/validator-system/infrastructure/adapters/file-system-coverage-attestation-gating-adapter.ts +87 -0
- package/scripts/harness/validator-system/infrastructure/adapters/file-system-coverage-attestation-verification-adapter.ts +165 -0
- package/scripts/harness/validator-system/infrastructure/adapters/file-system-injection-scan-adapter.ts +82 -0
- package/skills/README.md +1 -1
- package/skills/codebase-mapper/SKILL.md +1 -1
- package/skills/consistency-checker/references//343/203/201/343/202/247/343/203/203/343/202/257/343/203/252/343/202/271/343/203/210.md +1 -1
- package/skills/doc-health-checker/SKILL.md +148 -0
- package/skills/release-publisher/SKILL.md +101 -0
- package/skills/skill-creator/SKILL.md +74 -332
- package/skills/story-implementor/SKILL.md +52 -0
- package/skills/story-mapper/SKILL.md +4 -0
- package/skills/story-writer/SKILL.md +9 -0
- package/skills/uiux-designer/references/uiux-design-template.md +4 -4
- package/skills/unit-designer/SKILL.md +3 -1
- package/templates/.claude/scripts/deny-check.sh +260 -0
- package/skills/doc-freshness-checker/SKILL.md +0 -140
- package/skills/implementation-planner/SKILL.md +0 -169
- package/skills/implementation-planner/references/document-structure.md +0 -116
- package/skills/implementation-planner/references/plan-template.md +0 -177
- package/skills/implementation-planner/references/workflow.md +0 -164
- package/skills/pointer-validator/SKILL.md +0 -105
package/scripts/harness/main.ts
CHANGED
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
|
|
31
31
|
import {
|
|
32
32
|
mkdir as fsMkdir,
|
|
33
|
-
readFile as fsReadFile,
|
|
34
33
|
readdir as fsReaddir,
|
|
34
|
+
readFile as fsReadFile,
|
|
35
35
|
readlink as fsReadlink,
|
|
36
36
|
rename as fsRename,
|
|
37
37
|
rm as fsRm,
|
|
@@ -53,8 +53,11 @@ import {
|
|
|
53
53
|
} from "./config-foundation/infrastructure/repositories/file-system-config-repository.js";
|
|
54
54
|
import { createHarnessApiModule } from "./harness-api/composition-root.js";
|
|
55
55
|
import { createHarnessErrorModule } from "./harness-error/composition-root.js";
|
|
56
|
+
import {
|
|
57
|
+
type DeployManifestRecord,
|
|
58
|
+
SkillDeployerManifestBuilder,
|
|
59
|
+
} from "./installation/application/wrappers/skill-deployer-manifest-builder.js";
|
|
56
60
|
import { createInstallationModule } from "./installation/composition-root.js";
|
|
57
|
-
import { SkillDeployerManifestBuilder, type DeployManifestRecord } from "./installation/application/wrappers/skill-deployer-manifest-builder.js";
|
|
58
61
|
import { NodeCryptoHashAdapter } from "./installation/infrastructure/adapters/node-crypto-hash-adapter.js";
|
|
59
62
|
import { CheckStoryReflectionUseCase } from "./phase-dependency-model/application/usecases/check-story-reflection-usecase.js";
|
|
60
63
|
import { createPhaseDependencyModelModule } from "./phase-dependency-model/composition-root.js";
|
|
@@ -72,8 +75,8 @@ import { buildRegressionSuite } from "./regression-suite/composition-root.js";
|
|
|
72
75
|
import type { SkillSet } from "./setup/skill-deployer.js";
|
|
73
76
|
import {
|
|
74
77
|
deployAgentSkillLinks,
|
|
75
|
-
deployCodexHooks,
|
|
76
78
|
deployCiWorkflows,
|
|
79
|
+
deployCodexHooks,
|
|
77
80
|
deployDesignDocs,
|
|
78
81
|
deployHookScripts,
|
|
79
82
|
deployHuskyCommitMsgHook,
|
|
@@ -103,9 +106,7 @@ function getProjectRoot(): string {
|
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
function toTraceabilityModelOptions(resolvedConfig: HarnessConfigV2 | undefined) {
|
|
106
|
-
return resolvedConfig
|
|
107
|
-
? { pathRoots: { designDocsRoot: resolvedConfig.paths.designDocs } }
|
|
108
|
-
: undefined;
|
|
109
|
+
return resolvedConfig ? { pathRoots: { designDocsRoot: resolvedConfig.paths.designDocs } } : undefined;
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
interface PackageJsonDocument {
|
|
@@ -306,7 +307,8 @@ function parseWorkflowMode(value: string | undefined): WorkflowMode {
|
|
|
306
307
|
}
|
|
307
308
|
|
|
308
309
|
function parseScaffoldWorkItemType(value: string | undefined): ScaffoldWorkItemType | null {
|
|
309
|
-
if (value === "story" || value === "issue" || value === "fix" || value === "refactor" || value === "chore")
|
|
310
|
+
if (value === "story" || value === "issue" || value === "fix" || value === "refactor" || value === "chore")
|
|
311
|
+
return value;
|
|
310
312
|
return null;
|
|
311
313
|
}
|
|
312
314
|
|
|
@@ -376,7 +378,11 @@ async function countLegacyPlansWithoutWorkItems(rootDir: string): Promise<number
|
|
|
376
378
|
}).length;
|
|
377
379
|
}
|
|
378
380
|
|
|
379
|
-
async function scaffoldInceptionRoots(
|
|
381
|
+
async function scaffoldInceptionRoots(
|
|
382
|
+
rootDir: string,
|
|
383
|
+
unit: string | null = null,
|
|
384
|
+
inceptionRoot = "docs/inception",
|
|
385
|
+
): Promise<void> {
|
|
380
386
|
await fsMkdir(join(rootDir, inceptionRoot, "_shared"), { recursive: true });
|
|
381
387
|
await fsMkdir(join(rootDir, inceptionRoot, "_cross"), { recursive: true });
|
|
382
388
|
if (unit && unit !== "_cross" && unit !== "_shared") {
|
|
@@ -392,7 +398,7 @@ async function scaffoldWorkItem(
|
|
|
392
398
|
options: { readonly id?: string; readonly inceptionRoot?: string } = {},
|
|
393
399
|
): Promise<string> {
|
|
394
400
|
const inceptionRoot = options.inceptionRoot ?? "docs/inception";
|
|
395
|
-
const id = options.id ?? await nextWorkItemId(rootDir, inceptionRoot);
|
|
401
|
+
const id = options.id ?? (await nextWorkItemId(rootDir, inceptionRoot));
|
|
396
402
|
await scaffoldInceptionRoots(rootDir, unit, inceptionRoot);
|
|
397
403
|
const targetBase = unit === "_cross" ? join(rootDir, inceptionRoot, "_cross") : join(rootDir, inceptionRoot, unit);
|
|
398
404
|
const targetDir = join(targetBase, id);
|
|
@@ -435,7 +441,10 @@ async function createFileManifestRecord(
|
|
|
435
441
|
}
|
|
436
442
|
}
|
|
437
443
|
|
|
438
|
-
async function createSymlinkManifestRecord(
|
|
444
|
+
async function createSymlinkManifestRecord(
|
|
445
|
+
rootDir: string,
|
|
446
|
+
relativePath: string,
|
|
447
|
+
): Promise<DeployManifestRecord | null> {
|
|
439
448
|
try {
|
|
440
449
|
const target = await fsReadlink(join(rootDir, relativePath));
|
|
441
450
|
return { path: relativePath, mode: "symlink", contentForHash: target };
|
|
@@ -958,7 +967,15 @@ function parseCoverageThreshold(raw: string | undefined): number {
|
|
|
958
967
|
type InitPhasePreset = "full" | "standard" | "minimal" | "custom";
|
|
959
968
|
type AgentTarget = "claude" | "codex" | "both";
|
|
960
969
|
type SetupIntent = "minimal" | "recommended" | "strict" | "ci-only" | "agent-hooks" | "retrofit";
|
|
961
|
-
type ConfigChangeIntent =
|
|
970
|
+
type ConfigChangeIntent =
|
|
971
|
+
| "l4-strict"
|
|
972
|
+
| "codex-hooks"
|
|
973
|
+
| "ci-fail-on-warning"
|
|
974
|
+
| "baseline-reset"
|
|
975
|
+
| "quick-mode-strict"
|
|
976
|
+
| "quick-mode-relax"
|
|
977
|
+
| "retrofit-bootstrap"
|
|
978
|
+
| "planning-mode-relax";
|
|
962
979
|
type SetupCompletenessStatus = "configured" | "planned" | "manual" | "not-applicable" | "unknown";
|
|
963
980
|
|
|
964
981
|
interface SetupCompletenessEntry {
|
|
@@ -1117,7 +1134,8 @@ function buildSetupCompleteness(input: {
|
|
|
1117
1134
|
const includeClaude = input.agent === "claude" || input.agent === "both";
|
|
1118
1135
|
const includeCodex = input.agent === "codex" || input.agent === "both";
|
|
1119
1136
|
const doctorCommand = doctorValidationCommand(input.agent);
|
|
1120
|
-
const agentHooksConfigured =
|
|
1137
|
+
const agentHooksConfigured =
|
|
1138
|
+
(!includeClaude || input.checks.claudeSettings) && (!includeCodex || input.checks.codexHooks);
|
|
1121
1139
|
const agentContextConfigured = (!includeClaude || input.checks.claudeMd) && (!includeCodex || input.checks.agentsMd);
|
|
1122
1140
|
const entries: SetupCompletenessEntry[] = [
|
|
1123
1141
|
setupCompletenessEntry({
|
|
@@ -1181,7 +1199,8 @@ function buildSetupCompleteness(input: {
|
|
|
1181
1199
|
];
|
|
1182
1200
|
|
|
1183
1201
|
const externalActions: string[] = [];
|
|
1184
|
-
if (includeCodex)
|
|
1202
|
+
if (includeCodex)
|
|
1203
|
+
externalActions.push("Run codex features enable hooks if Codex hooks are not enabled for the user.");
|
|
1185
1204
|
if (input.withCi) externalActions.push("Trigger or inspect the first GitHub Actions PhaseGate workflow run.");
|
|
1186
1205
|
if (input.intent === "strict") externalActions.push("Confirm team policy accepts strict local and CI enforcement.");
|
|
1187
1206
|
entries.push({
|
|
@@ -1189,7 +1208,10 @@ function buildSetupCompleteness(input: {
|
|
|
1189
1208
|
status: externalActions.length > 0 ? "manual" : "not-applicable",
|
|
1190
1209
|
evidence: externalActions.length > 0 ? externalActions : ["No external manual checks selected for this setup run."],
|
|
1191
1210
|
nextAction: externalActions.length > 0 ? externalActions.join(" ") : null,
|
|
1192
|
-
risk:
|
|
1211
|
+
risk:
|
|
1212
|
+
externalActions.length > 0
|
|
1213
|
+
? "PhaseGate cannot prove these user-level or hosted-service states from local files."
|
|
1214
|
+
: null,
|
|
1193
1215
|
});
|
|
1194
1216
|
return entries;
|
|
1195
1217
|
}
|
|
@@ -1271,7 +1293,8 @@ function buildAgentReadiness(input: {
|
|
|
1271
1293
|
"setup:agent will create or refresh the CLAUDE.md PhaseGate managed section.",
|
|
1272
1294
|
"setup:agent will deploy bundled skills for Claude Code.",
|
|
1273
1295
|
],
|
|
1274
|
-
nextAction:
|
|
1296
|
+
nextAction:
|
|
1297
|
+
"Run setup:agent --agent claude --apply, then ask Claude Code to read CLAUDE.md before planning work.",
|
|
1275
1298
|
}),
|
|
1276
1299
|
setupReadinessEntry({
|
|
1277
1300
|
agent: "codex",
|
|
@@ -1294,9 +1317,7 @@ function buildAgentReadiness(input: {
|
|
|
1294
1317
|
agent: "shared",
|
|
1295
1318
|
included: true,
|
|
1296
1319
|
configured: sharedConfigured,
|
|
1297
|
-
configuredEvidence: [
|
|
1298
|
-
"package.json, phasegate.config.json, skills, and selected Husky/CI targets are present.",
|
|
1299
|
-
],
|
|
1320
|
+
configuredEvidence: ["package.json, phasegate.config.json, skills, and selected Husky/CI targets are present."],
|
|
1300
1321
|
plannedEvidence: [
|
|
1301
1322
|
"setup:agent will create or refresh package scripts, phasegate.config.json, skills, and selected Husky/CI targets.",
|
|
1302
1323
|
],
|
|
@@ -1306,13 +1327,16 @@ function buildAgentReadiness(input: {
|
|
|
1306
1327
|
];
|
|
1307
1328
|
}
|
|
1308
1329
|
|
|
1309
|
-
async function buildAgentSetupPlan(
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1330
|
+
async function buildAgentSetupPlan(
|
|
1331
|
+
rootDir: string,
|
|
1332
|
+
input: {
|
|
1333
|
+
readonly intent: SetupIntent;
|
|
1334
|
+
readonly agent: AgentTarget;
|
|
1335
|
+
readonly withHusky: boolean;
|
|
1336
|
+
readonly withCi: boolean;
|
|
1337
|
+
readonly workflow: WorkflowMode;
|
|
1338
|
+
},
|
|
1339
|
+
) {
|
|
1316
1340
|
const checks = {
|
|
1317
1341
|
packageJson: await projectFileExists(rootDir, "package.json"),
|
|
1318
1342
|
phasegateConfig: await projectFileExists(rootDir, "phasegate.config.json"),
|
|
@@ -1327,18 +1351,32 @@ async function buildAgentSetupPlan(rootDir: string, input: {
|
|
|
1327
1351
|
const includeClaude = input.agent === "claude" || input.agent === "both";
|
|
1328
1352
|
const includeCodex = input.agent === "codex" || input.agent === "both";
|
|
1329
1353
|
const changes = [
|
|
1330
|
-
!checks.packageJson
|
|
1331
|
-
|
|
1354
|
+
!checks.packageJson
|
|
1355
|
+
? "Add phasegate devDependency and phasegate scripts to package.json."
|
|
1356
|
+
: "Keep existing package.json and merge missing phasegate scripts only.",
|
|
1357
|
+
!checks.phasegateConfig
|
|
1358
|
+
? `Create phasegate.config.json for ${input.workflow} workflow.`
|
|
1359
|
+
: "Keep existing phasegate.config.json; review config:plan before changing policy.",
|
|
1332
1360
|
includeClaude ? "Create or refresh .claude/settings.json and CLAUDE.md managed section." : null,
|
|
1333
1361
|
includeCodex ? "Create or refresh .codex/hooks.json and AGENTS.md managed section." : null,
|
|
1334
|
-
input.withHusky
|
|
1335
|
-
|
|
1362
|
+
input.withHusky
|
|
1363
|
+
? "Create or refresh Husky pre-commit, commit-msg, and pre-push backstops."
|
|
1364
|
+
: "Leave Husky hooks unmanaged in this setup run.",
|
|
1365
|
+
input.withCi
|
|
1366
|
+
? "Create or refresh GitHub Actions PhaseGate workflow."
|
|
1367
|
+
: "Leave CI workflow unmanaged in this setup run.",
|
|
1336
1368
|
].filter((item): item is string => item !== null);
|
|
1337
1369
|
const questions = [
|
|
1338
|
-
input.intent === "recommended"
|
|
1339
|
-
|
|
1370
|
+
input.intent === "recommended"
|
|
1371
|
+
? "Do you want both Claude and Codex context files, or only the agent you actively use?"
|
|
1372
|
+
: null,
|
|
1373
|
+
input.intent === "retrofit"
|
|
1374
|
+
? "Should existing hooks/workflows be preserved as user-owned content or migrated into PhaseGate managed blocks?"
|
|
1375
|
+
: null,
|
|
1340
1376
|
input.intent === "ci-only" ? "Should local hooks remain disabled while CI enforces the same checks?" : null,
|
|
1341
|
-
input.workflow !== "strict"
|
|
1377
|
+
input.workflow !== "strict"
|
|
1378
|
+
? "Do you want strict WI/product reflection enforcement now, or after the first green run?"
|
|
1379
|
+
: null,
|
|
1342
1380
|
].filter((item): item is string => item !== null);
|
|
1343
1381
|
return {
|
|
1344
1382
|
intent: input.intent,
|
|
@@ -1399,7 +1437,10 @@ function getNestedValue(source: unknown, path: readonly string[]): unknown {
|
|
|
1399
1437
|
}
|
|
1400
1438
|
|
|
1401
1439
|
function buildConfigPatchPreview(intent: ConfigChangeIntent, before: unknown | null): ConfigPatchPreview {
|
|
1402
|
-
const configIntents: Record<
|
|
1440
|
+
const configIntents: Record<
|
|
1441
|
+
ConfigChangeIntent,
|
|
1442
|
+
readonly { readonly pointer: string; readonly path: readonly string[]; readonly value: unknown }[]
|
|
1443
|
+
> = {
|
|
1403
1444
|
"l4-strict": [
|
|
1404
1445
|
{ pointer: "/layers/L4/enabled", path: ["layers", "L4", "enabled"], value: true },
|
|
1405
1446
|
{ pointer: "/validate/failOnWarning", path: ["validate", "failOnWarning"], value: true },
|
|
@@ -1413,7 +1454,11 @@ function buildConfigPatchPreview(intent: ConfigChangeIntent, before: unknown | n
|
|
|
1413
1454
|
{ pointer: "/quickMode/relaxedGates", path: ["quickMode", "relaxedGates"], value: [] },
|
|
1414
1455
|
],
|
|
1415
1456
|
"quick-mode-relax": [
|
|
1416
|
-
{
|
|
1457
|
+
{
|
|
1458
|
+
pointer: "/quickMode/allowedCategories",
|
|
1459
|
+
path: ["quickMode", "allowedCategories"],
|
|
1460
|
+
value: ["bugfix", "docs", "test", "config"],
|
|
1461
|
+
},
|
|
1417
1462
|
],
|
|
1418
1463
|
"codex-hooks": [],
|
|
1419
1464
|
"baseline-reset": [],
|
|
@@ -1422,9 +1467,7 @@ function buildConfigPatchPreview(intent: ConfigChangeIntent, before: unknown | n
|
|
|
1422
1467
|
{ pointer: "/phaseDependencies/override", path: ["phaseDependencies", "override"], value: true },
|
|
1423
1468
|
{ pointer: "/quickMode/relaxedGates", path: ["quickMode", "relaxedGates"], value: ["phase-gate"] },
|
|
1424
1469
|
],
|
|
1425
|
-
"planning-mode-relax": [
|
|
1426
|
-
{ pointer: "/planningMode/default", path: ["planningMode", "default"], value: "manual" },
|
|
1427
|
-
],
|
|
1470
|
+
"planning-mode-relax": [{ pointer: "/planningMode/default", path: ["planningMode", "default"], value: "manual" }],
|
|
1428
1471
|
};
|
|
1429
1472
|
const changes = configIntents[intent];
|
|
1430
1473
|
if (changes.length === 0) {
|
|
@@ -1464,7 +1507,10 @@ function configPlanBackupPath(rootDir: string, now: Date): string {
|
|
|
1464
1507
|
return join(rootDir, ".phasegate", "backups", `phasegate.config.${stamp}.json`);
|
|
1465
1508
|
}
|
|
1466
1509
|
|
|
1467
|
-
async function applyConfigPlan(
|
|
1510
|
+
async function applyConfigPlan(
|
|
1511
|
+
rootDir: string,
|
|
1512
|
+
plan: Awaited<ReturnType<typeof buildConfigChangePlan>>,
|
|
1513
|
+
): Promise<ConfigApplyResult> {
|
|
1468
1514
|
const patch = plan.configPatch;
|
|
1469
1515
|
if (patch.applicability !== "applicable") {
|
|
1470
1516
|
throw new Error(`config plan is not applicable: ${patch.blockedReason ?? patch.applicability}`);
|
|
@@ -1498,26 +1544,44 @@ function hasStructuredInstallError(value: unknown): boolean {
|
|
|
1498
1544
|
}
|
|
1499
1545
|
|
|
1500
1546
|
async function buildConfigChangePlan(rootDir: string, intent: ConfigChangeIntent) {
|
|
1501
|
-
const catalog: Record<
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1547
|
+
const catalog: Record<
|
|
1548
|
+
ConfigChangeIntent,
|
|
1549
|
+
{
|
|
1550
|
+
readonly targets: readonly string[];
|
|
1551
|
+
readonly managedTargets: readonly string[];
|
|
1552
|
+
readonly externalActions: readonly {
|
|
1553
|
+
readonly id: string;
|
|
1554
|
+
readonly label: string;
|
|
1555
|
+
readonly command: string | null;
|
|
1556
|
+
readonly blocking: boolean;
|
|
1557
|
+
}[];
|
|
1558
|
+
readonly commands: readonly string[];
|
|
1559
|
+
readonly validations: readonly string[];
|
|
1560
|
+
readonly risks: readonly string[];
|
|
1561
|
+
}
|
|
1562
|
+
> = {
|
|
1509
1563
|
"l4-strict": {
|
|
1510
1564
|
targets: ["phasegate.config.json: layers.L4.enabled", "phasegate.config.json: validate.failOnWarning"],
|
|
1511
1565
|
managedTargets: ["phasegate.config.json"],
|
|
1512
1566
|
externalActions: [],
|
|
1513
|
-
commands: [
|
|
1567
|
+
commands: [
|
|
1568
|
+
"phasegate config:plan --intent l4-strict --apply --json",
|
|
1569
|
+
"phasegate validate --layer L4 --fail-on-warning --format human",
|
|
1570
|
+
],
|
|
1514
1571
|
validations: ["phasegate phasegate:detect-drift --json", "phasegate phasegate:check-ready"],
|
|
1515
1572
|
risks: ["L4 findings may be advisory today but become blocking when fail-on-warning is enabled."],
|
|
1516
1573
|
},
|
|
1517
1574
|
"codex-hooks": {
|
|
1518
1575
|
targets: [".codex/hooks.json", "AGENTS.md", ".codex/skills"],
|
|
1519
1576
|
managedTargets: [".codex/hooks.json", "AGENTS.md", ".codex/skills"],
|
|
1520
|
-
externalActions: [
|
|
1577
|
+
externalActions: [
|
|
1578
|
+
{
|
|
1579
|
+
id: "codex-hooks-feature",
|
|
1580
|
+
label: "Enable Codex user-level hooks feature.",
|
|
1581
|
+
command: "codex features enable hooks",
|
|
1582
|
+
blocking: true,
|
|
1583
|
+
},
|
|
1584
|
+
],
|
|
1521
1585
|
commands: ["phasegate install --agent codex --apply", "codex features enable hooks"],
|
|
1522
1586
|
validations: ["phasegate doctor --json", "phasegate phasegate:status --json"],
|
|
1523
1587
|
risks: ["Codex apply_patch writes still require the pre-commit backstop for full coverage."],
|
|
@@ -1525,8 +1589,19 @@ async function buildConfigChangePlan(rootDir: string, intent: ConfigChangeIntent
|
|
|
1525
1589
|
"ci-fail-on-warning": {
|
|
1526
1590
|
targets: [".github/workflows/phasegate-aidlc-gate.yml", "phasegate.config.json"],
|
|
1527
1591
|
managedTargets: [".github/workflows/phasegate-aidlc-gate.yml", "phasegate.config.json"],
|
|
1528
|
-
externalActions: [
|
|
1529
|
-
|
|
1592
|
+
externalActions: [
|
|
1593
|
+
{
|
|
1594
|
+
id: "github-actions-first-run",
|
|
1595
|
+
label: "Trigger or inspect the first GitHub Actions PhaseGate run.",
|
|
1596
|
+
command: null,
|
|
1597
|
+
blocking: false,
|
|
1598
|
+
},
|
|
1599
|
+
],
|
|
1600
|
+
commands: [
|
|
1601
|
+
"phasegate install --with-ci --apply",
|
|
1602
|
+
"phasegate config:plan --intent ci-fail-on-warning --apply --json",
|
|
1603
|
+
"phasegate validate --layer L4 --fail-on-warning",
|
|
1604
|
+
],
|
|
1530
1605
|
validations: ["phasegate doctor", "phasegate ci:generate-template --type aidlc-gate --render"],
|
|
1531
1606
|
risks: ["Existing warning-only projects may start failing CI after rollout."],
|
|
1532
1607
|
},
|
|
@@ -1542,7 +1617,10 @@ async function buildConfigChangePlan(rootDir: string, intent: ConfigChangeIntent
|
|
|
1542
1617
|
targets: ["phasegate.config.json: quickMode"],
|
|
1543
1618
|
managedTargets: ["phasegate.config.json"],
|
|
1544
1619
|
externalActions: [],
|
|
1545
|
-
commands: [
|
|
1620
|
+
commands: [
|
|
1621
|
+
"phasegate config:plan --intent quick-mode-strict --apply --json",
|
|
1622
|
+
"phasegate check-change-category --paths <changed-files> --format json",
|
|
1623
|
+
],
|
|
1546
1624
|
validations: ["phasegate ci-check --quick --dry-run", "phasegate phasegate:check-ready"],
|
|
1547
1625
|
risks: ["More changes will require Full Mode validation before commit."],
|
|
1548
1626
|
},
|
|
@@ -1550,25 +1628,46 @@ async function buildConfigChangePlan(rootDir: string, intent: ConfigChangeIntent
|
|
|
1550
1628
|
targets: ["phasegate.config.json: quickMode.allowedCategories"],
|
|
1551
1629
|
managedTargets: ["phasegate.config.json"],
|
|
1552
1630
|
externalActions: [],
|
|
1553
|
-
commands: [
|
|
1631
|
+
commands: [
|
|
1632
|
+
"phasegate config:plan --intent quick-mode-relax --json",
|
|
1633
|
+
"phasegate config:plan --intent quick-mode-relax --apply --json",
|
|
1634
|
+
"phasegate check-change-category --paths <changed-files> --format json",
|
|
1635
|
+
],
|
|
1554
1636
|
validations: ["phasegate ci-check --quick --dry-run", "phasegate phasegate:check-ready"],
|
|
1555
|
-
risks: [
|
|
1637
|
+
risks: [
|
|
1638
|
+
"Small bugfix/docs/test/config changes can proceed through Quick Mode again; protected files remain governed by hook and managed command policies.",
|
|
1639
|
+
],
|
|
1556
1640
|
},
|
|
1557
1641
|
"retrofit-bootstrap": {
|
|
1558
|
-
targets: [
|
|
1642
|
+
targets: [
|
|
1643
|
+
"phasegate.config.json: planningMode.default",
|
|
1644
|
+
"phasegate.config.json: phaseDependencies.override",
|
|
1645
|
+
"phasegate.config.json: quickMode.relaxedGates",
|
|
1646
|
+
],
|
|
1559
1647
|
managedTargets: ["phasegate.config.json"],
|
|
1560
1648
|
externalActions: [],
|
|
1561
|
-
commands: [
|
|
1649
|
+
commands: [
|
|
1650
|
+
"phasegate baseline --dry-run",
|
|
1651
|
+
"phasegate config:plan --intent retrofit-bootstrap --json",
|
|
1652
|
+
"phasegate config:plan --intent retrofit-bootstrap --apply --json",
|
|
1653
|
+
],
|
|
1562
1654
|
validations: ["phasegate validate-metadata docs/inception/_shared/*.md", "phasegate check-phase-gate --level 2"],
|
|
1563
|
-
risks: [
|
|
1655
|
+
risks: [
|
|
1656
|
+
"Manual planning mode accepts existing retrofit planning evidence; review the patch before applying it to avoid weakening greenfield projects.",
|
|
1657
|
+
],
|
|
1564
1658
|
},
|
|
1565
1659
|
"planning-mode-relax": {
|
|
1566
1660
|
targets: ["phasegate.config.json: planningMode.default"],
|
|
1567
1661
|
managedTargets: ["phasegate.config.json"],
|
|
1568
1662
|
externalActions: [],
|
|
1569
|
-
commands: [
|
|
1663
|
+
commands: [
|
|
1664
|
+
"phasegate config:plan --intent planning-mode-relax --json",
|
|
1665
|
+
"phasegate config:plan --intent planning-mode-relax --apply --json",
|
|
1666
|
+
],
|
|
1570
1667
|
validations: ["phasegate check-phase-gate --level 2", "phasegate phasegate:check-ready"],
|
|
1571
|
-
risks: [
|
|
1668
|
+
risks: [
|
|
1669
|
+
"Manual planning mode reduces PhaseGate's QA enforcement for plan documents until strict planning is restored.",
|
|
1670
|
+
],
|
|
1572
1671
|
},
|
|
1573
1672
|
};
|
|
1574
1673
|
const before = await readProjectJson(rootDir, "phasegate.config.json");
|
|
@@ -1576,7 +1675,8 @@ async function buildConfigChangePlan(rootDir: string, intent: ConfigChangeIntent
|
|
|
1576
1675
|
intent,
|
|
1577
1676
|
...catalog[intent],
|
|
1578
1677
|
configPatch: buildConfigPatchPreview(intent, before),
|
|
1579
|
-
diffExplanation:
|
|
1678
|
+
diffExplanation:
|
|
1679
|
+
"Review the listed targets first, apply through PhaseGate managed commands where possible, then run the validations in order.",
|
|
1580
1680
|
rollback: "Use git diff for config changes; use phasegate uninstall/reconcile dry-runs for managed setup targets.",
|
|
1581
1681
|
};
|
|
1582
1682
|
}
|
|
@@ -1866,13 +1966,19 @@ async function main(): Promise<void> {
|
|
|
1866
1966
|
contentForHash: `${result.version}:${skill}`,
|
|
1867
1967
|
})),
|
|
1868
1968
|
await createFileManifestRecord(rootDir, join("skills", ".harness-version")),
|
|
1869
|
-
packageResult.created || packageResult.updated
|
|
1969
|
+
packageResult.created || packageResult.updated
|
|
1970
|
+
? await createFileManifestRecord(rootDir, "package.json")
|
|
1971
|
+
: null,
|
|
1870
1972
|
configResult.created ? await createFileManifestRecord(rootDir, "phasegate.config.json") : null,
|
|
1871
|
-
hooksResult.settingsCreated
|
|
1973
|
+
hooksResult.settingsCreated
|
|
1974
|
+
? await createFileManifestRecord(rootDir, join(".claude", "settings.json"))
|
|
1975
|
+
: null,
|
|
1872
1976
|
hooksResult.hookConfigGenerated
|
|
1873
1977
|
? await createFileManifestRecord(rootDir, join(".claude", "scripts", "hook-config.json"))
|
|
1874
1978
|
: null,
|
|
1875
|
-
skillLinkResult.claude !== null
|
|
1979
|
+
skillLinkResult.claude !== null
|
|
1980
|
+
? await createSymlinkManifestRecord(rootDir, join(".claude", "skills"))
|
|
1981
|
+
: null,
|
|
1876
1982
|
codexResult?.created ? await createFileManifestRecord(rootDir, join(".codex", "hooks.json")) : null,
|
|
1877
1983
|
skillLinkResult.codex !== null ? await createSymlinkManifestRecord(rootDir, join(".codex", "skills")) : null,
|
|
1878
1984
|
...designDocsResult.copiedFiles.map((path) => createFileManifestRecord(rootDir, path)),
|
|
@@ -1920,7 +2026,9 @@ async function main(): Promise<void> {
|
|
|
1920
2026
|
if (configResult.created) {
|
|
1921
2027
|
console.log(`✓ phasegate.config.json created`);
|
|
1922
2028
|
if (workflow === "strict") {
|
|
1923
|
-
console.log(
|
|
2029
|
+
console.log(
|
|
2030
|
+
`✓ strict workflow configured (quickMode.relaxedGates: [], allowedCategories: ["bugfix","docs","test","config"])`,
|
|
2031
|
+
);
|
|
1924
2032
|
}
|
|
1925
2033
|
} else {
|
|
1926
2034
|
console.log(` phasegate.config.json already exists, skipped`);
|
|
@@ -1931,9 +2039,7 @@ async function main(): Promise<void> {
|
|
|
1931
2039
|
if (hooksResult.hookConfigGenerated) {
|
|
1932
2040
|
const dirsLabel = hooksResult.detectedTargetDirs.join(", ");
|
|
1933
2041
|
const formatterLabel = hooksResult.detectedFormatter ?? "none";
|
|
1934
|
-
console.log(
|
|
1935
|
-
`✓ hook-config.json generated (targetDirs: ${dirsLabel}; formatter: ${formatterLabel})`,
|
|
1936
|
-
);
|
|
2042
|
+
console.log(`✓ hook-config.json generated (targetDirs: ${dirsLabel}; formatter: ${formatterLabel})`);
|
|
1937
2043
|
}
|
|
1938
2044
|
if (hooksResult.settingsCreated) {
|
|
1939
2045
|
console.log(`✓ .claude/settings.json created`);
|
|
@@ -2101,7 +2207,9 @@ async function main(): Promise<void> {
|
|
|
2101
2207
|
const unit = args[1];
|
|
2102
2208
|
const type = parseScaffoldWorkItemType(args[2]);
|
|
2103
2209
|
if (!unit || !type) {
|
|
2104
|
-
console.error(
|
|
2210
|
+
console.error(
|
|
2211
|
+
"Usage: phasegate scaffold-wi <unit|_cross> <story|issue|fix|refactor|chore> [--id <work-item-id>] [--root <path>]",
|
|
2212
|
+
);
|
|
2105
2213
|
process.exit(2);
|
|
2106
2214
|
}
|
|
2107
2215
|
const configuredPersonalRoot = resolvedConfig?.paths.inceptionDocs?.startsWith(".phasegate-local/")
|
|
@@ -2117,7 +2225,18 @@ async function main(): Promise<void> {
|
|
|
2117
2225
|
}
|
|
2118
2226
|
|
|
2119
2227
|
case "install": {
|
|
2120
|
-
const KNOWN_INSTALL_FLAGS = [
|
|
2228
|
+
const KNOWN_INSTALL_FLAGS = [
|
|
2229
|
+
"--dry-run",
|
|
2230
|
+
"--apply",
|
|
2231
|
+
"--force",
|
|
2232
|
+
"--json",
|
|
2233
|
+
"--agent",
|
|
2234
|
+
"--skills",
|
|
2235
|
+
"--workflow",
|
|
2236
|
+
"--with-husky",
|
|
2237
|
+
"--with-ci",
|
|
2238
|
+
"--personal",
|
|
2239
|
+
];
|
|
2121
2240
|
const flagError = validateKnownFlags(args, KNOWN_INSTALL_FLAGS);
|
|
2122
2241
|
if (flagError) {
|
|
2123
2242
|
console.error(flagError);
|
|
@@ -2164,7 +2283,17 @@ async function main(): Promise<void> {
|
|
|
2164
2283
|
}
|
|
2165
2284
|
|
|
2166
2285
|
case "setup:agent": {
|
|
2167
|
-
const KNOWN_SETUP_AGENT_FLAGS = [
|
|
2286
|
+
const KNOWN_SETUP_AGENT_FLAGS = [
|
|
2287
|
+
"--intent",
|
|
2288
|
+
"--agent",
|
|
2289
|
+
"--workflow",
|
|
2290
|
+
"--with-husky",
|
|
2291
|
+
"--with-ci",
|
|
2292
|
+
"--dry-run",
|
|
2293
|
+
"--apply",
|
|
2294
|
+
"--force",
|
|
2295
|
+
"--json",
|
|
2296
|
+
];
|
|
2168
2297
|
const flagError = validateKnownFlags(args, KNOWN_SETUP_AGENT_FLAGS);
|
|
2169
2298
|
if (flagError) {
|
|
2170
2299
|
console.error(flagError);
|
|
@@ -2172,7 +2301,9 @@ async function main(): Promise<void> {
|
|
|
2172
2301
|
}
|
|
2173
2302
|
const intent = parseSetupIntent(parseFlag(args, "--intent"));
|
|
2174
2303
|
const agent = parseAgentTarget(parseFlag(args, "--agent"), "both");
|
|
2175
|
-
const workflow = parseWorkflowMode(
|
|
2304
|
+
const workflow = parseWorkflowMode(
|
|
2305
|
+
parseFlag(args, "--workflow") ?? (intent === "strict" ? "strict" : "standard"),
|
|
2306
|
+
);
|
|
2176
2307
|
const withCi = hasFlag(args, "--with-ci") || intent === "ci-only" || intent === "strict";
|
|
2177
2308
|
const withHusky = hasFlag(args, "--with-husky") || intent === "agent-hooks" || intent === "strict";
|
|
2178
2309
|
let plan = await buildAgentSetupPlan(rootDir, { intent, agent, withHusky, withCi, workflow });
|
|
@@ -2182,10 +2313,15 @@ async function main(): Promise<void> {
|
|
|
2182
2313
|
if (apply) {
|
|
2183
2314
|
const skillResult = await deploySkills(harnessRoot, rootDir, "all");
|
|
2184
2315
|
const packageResult = await ensurePhasegatePackageDependency(rootDir, skillResult.version);
|
|
2185
|
-
const configResult = await initHarnessConfig(
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2316
|
+
const configResult = await initHarnessConfig(
|
|
2317
|
+
rootDir,
|
|
2318
|
+
"my-project",
|
|
2319
|
+
workflow === "strict" ? "full" : "standard",
|
|
2320
|
+
{
|
|
2321
|
+
ciEnabled: withCi,
|
|
2322
|
+
workflow,
|
|
2323
|
+
},
|
|
2324
|
+
);
|
|
2189
2325
|
bootstrapResult = {
|
|
2190
2326
|
skillsDeployed: skillResult.deployedSkills.length,
|
|
2191
2327
|
packageDependency: packageResult,
|
|
@@ -2253,13 +2389,20 @@ async function main(): Promise<void> {
|
|
|
2253
2389
|
console.log(`changed: ${applyResult.changed}`);
|
|
2254
2390
|
console.log(`backup: ${applyResult.backupPath}`);
|
|
2255
2391
|
console.log("Applied operations:");
|
|
2256
|
-
for (const operation of applyResult.appliedOperations)
|
|
2392
|
+
for (const operation of applyResult.appliedOperations)
|
|
2393
|
+
console.log(`- ${operation.op} ${operation.pointer}`);
|
|
2257
2394
|
}
|
|
2258
2395
|
process.exit(0);
|
|
2259
2396
|
} catch (error) {
|
|
2260
2397
|
const message = error instanceof Error ? error.message : String(error);
|
|
2261
2398
|
if (json) {
|
|
2262
|
-
console.log(
|
|
2399
|
+
console.log(
|
|
2400
|
+
JSON.stringify(
|
|
2401
|
+
{ intent: plan.intent, refused: true, error: message, configPatch: plan.configPatch },
|
|
2402
|
+
null,
|
|
2403
|
+
2,
|
|
2404
|
+
),
|
|
2405
|
+
);
|
|
2263
2406
|
} else {
|
|
2264
2407
|
console.error(`Error: ${message}`);
|
|
2265
2408
|
}
|
|
@@ -2427,10 +2570,7 @@ async function main(): Promise<void> {
|
|
|
2427
2570
|
|
|
2428
2571
|
case "migrate": {
|
|
2429
2572
|
if (args[1] === "work-items") {
|
|
2430
|
-
const mod = createTraceabilityModelModule(
|
|
2431
|
-
rootDir,
|
|
2432
|
-
toTraceabilityModelOptions(resolvedConfig),
|
|
2433
|
-
);
|
|
2573
|
+
const mod = createTraceabilityModelModule(rootDir, toTraceabilityModelOptions(resolvedConfig));
|
|
2434
2574
|
const result = await mod.migrateWorkItemsCommandHandler.execute({
|
|
2435
2575
|
dryRun: hasFlag(args, "--dry-run"),
|
|
2436
2576
|
apply: hasFlag(args, "--apply"),
|
|
@@ -2497,10 +2637,7 @@ async function main(): Promise<void> {
|
|
|
2497
2637
|
|
|
2498
2638
|
// ── traceability-model ──
|
|
2499
2639
|
case "validate-metadata": {
|
|
2500
|
-
const mod = createTraceabilityModelModule(
|
|
2501
|
-
rootDir,
|
|
2502
|
-
toTraceabilityModelOptions(resolvedConfig),
|
|
2503
|
-
);
|
|
2640
|
+
const mod = createTraceabilityModelModule(rootDir, toTraceabilityModelOptions(resolvedConfig));
|
|
2504
2641
|
const filePaths = parsePositionalArgs(args.slice(1));
|
|
2505
2642
|
const result = await mod.validateMetadataCommandHandler.execute({
|
|
2506
2643
|
filePaths,
|
|
@@ -2512,10 +2649,7 @@ async function main(): Promise<void> {
|
|
|
2512
2649
|
}
|
|
2513
2650
|
|
|
2514
2651
|
case "work-items:status": {
|
|
2515
|
-
const mod = createTraceabilityModelModule(
|
|
2516
|
-
rootDir,
|
|
2517
|
-
toTraceabilityModelOptions(resolvedConfig),
|
|
2518
|
-
);
|
|
2652
|
+
const mod = createTraceabilityModelModule(rootDir, toTraceabilityModelOptions(resolvedConfig));
|
|
2519
2653
|
const result = await mod.workItemStatusCommandHandler.execute({
|
|
2520
2654
|
dryRun: hasFlag(args, "--dry-run"),
|
|
2521
2655
|
apply: hasFlag(args, "--apply"),
|
|
@@ -2818,22 +2952,19 @@ async function main(): Promise<void> {
|
|
|
2818
2952
|
});
|
|
2819
2953
|
const flags: Record<string, boolean | string> = {};
|
|
2820
2954
|
if (json) flags.json = true;
|
|
2821
|
-
await mod.handlers.generateMatrixHandler.handle(
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2955
|
+
await mod.handlers.generateMatrixHandler.handle(
|
|
2956
|
+
{
|
|
2957
|
+
requirementsPath: parseFlag(args, "--requirements") ?? "docs/product/user_stories.md",
|
|
2958
|
+
testRoot: parseFlag(args, "--tests") ?? "scripts/harness/__tests__",
|
|
2959
|
+
matrixFilePath: parseFlag(args, "--out") ?? ".harness/requirement-test-matrix.json",
|
|
2960
|
+
},
|
|
2961
|
+
flags,
|
|
2962
|
+
);
|
|
2826
2963
|
break;
|
|
2827
2964
|
}
|
|
2828
2965
|
|
|
2829
2966
|
case "phasegate:attest": {
|
|
2830
|
-
const flagError = validateKnownFlags(args.slice(1), [
|
|
2831
|
-
"--out",
|
|
2832
|
-
"--require-pass",
|
|
2833
|
-
"--mode",
|
|
2834
|
-
"--json",
|
|
2835
|
-
"--help",
|
|
2836
|
-
]);
|
|
2967
|
+
const flagError = validateKnownFlags(args.slice(1), ["--out", "--require-pass", "--mode", "--json", "--help"]);
|
|
2837
2968
|
if (flagError !== null) {
|
|
2838
2969
|
console.error(flagError);
|
|
2839
2970
|
process.exit(2);
|
|
@@ -3007,6 +3138,25 @@ Examples:
|
|
|
3007
3138
|
break;
|
|
3008
3139
|
}
|
|
3009
3140
|
|
|
3141
|
+
case "integrity:pin": {
|
|
3142
|
+
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3143
|
+
const dryRun = hasFlag(args, "--dry-run");
|
|
3144
|
+
const format = json ? "json" : "human";
|
|
3145
|
+
const result = await mod.integrityHandler.pin({ dryRun, format });
|
|
3146
|
+
console.log(result.output);
|
|
3147
|
+
process.exit(result.exitCode);
|
|
3148
|
+
break;
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
case "integrity:verify": {
|
|
3152
|
+
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3153
|
+
const format = json ? "json" : "human";
|
|
3154
|
+
const result = await mod.integrityHandler.verify({ format });
|
|
3155
|
+
console.log(result.output);
|
|
3156
|
+
process.exit(result.exitCode);
|
|
3157
|
+
break;
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3010
3160
|
// ── skill-quality ──
|
|
3011
3161
|
case "skill:execute-tdd-cycle": {
|
|
3012
3162
|
const mod = createSkillQualityHandlers();
|
|
@@ -3246,11 +3396,13 @@ Examples:
|
|
|
3246
3396
|
printSubcommandHelp("delegate-sonnet");
|
|
3247
3397
|
process.exit(0);
|
|
3248
3398
|
}
|
|
3249
|
-
if (!hasFlag(args, "--dry-run") && await isModelDelegationDisabled(rootDir)) {
|
|
3250
|
-
console.error(
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3399
|
+
if (!hasFlag(args, "--dry-run") && (await isModelDelegationDisabled(rootDir))) {
|
|
3400
|
+
console.error(
|
|
3401
|
+
JSON.stringify({
|
|
3402
|
+
code: "MODEL_DELEGATION_DISABLED",
|
|
3403
|
+
message: "phasegate delegate-sonnet is disabled by modelRouting.delegation=none",
|
|
3404
|
+
}),
|
|
3405
|
+
);
|
|
3254
3406
|
process.exit(1);
|
|
3255
3407
|
}
|
|
3256
3408
|
const { spawn } = await import("node:child_process");
|