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.
Files changed (77) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.ja.md +52 -15
  3. package/README.md +39 -11
  4. package/docs/ADR/030-injection-threat-model-and-trust-root.md +145 -0
  5. package/docs/guide/hooks-integration.md +101 -1
  6. package/docs/guide/installation.md +1 -1
  7. package/docs/guide/quick-vs-full-mode.md +1 -1
  8. package/docs/guide/skills-overview.md +7 -8
  9. package/package.json +1 -1
  10. package/scripts/harness/agent-integration/presentation/phasegate-status-context.ts +131 -63
  11. package/scripts/harness/agent-integration/presentation/session-start-hook.ts +31 -4
  12. package/scripts/harness/agent-integration/presentation/spotlight.ts +65 -0
  13. package/scripts/harness/biome-ast-engine/application/mappers/build-harness-error-payload-output-mapper.ts +3 -3
  14. package/scripts/harness/biome-ast-engine/infrastructure/mappers/rule-violation-code-mapper.ts +3 -3
  15. package/scripts/harness/biome-ast-engine/presentation/cli/harness-lint-command-handler.ts +6 -4
  16. package/scripts/harness/ci-governance/application/dto/pin-integrity-input.ts +8 -0
  17. package/scripts/harness/ci-governance/application/dto/pin-integrity-output.ts +9 -0
  18. package/scripts/harness/ci-governance/application/dto/verify-integrity-input.ts +7 -0
  19. package/scripts/harness/ci-governance/application/dto/verify-integrity-output.ts +10 -0
  20. package/scripts/harness/ci-governance/application/usecases/pin-integrity-usecase.ts +60 -0
  21. package/scripts/harness/ci-governance/application/usecases/verify-integrity-usecase.ts +46 -0
  22. package/scripts/harness/ci-governance/composition-root.ts +67 -64
  23. package/scripts/harness/ci-governance/domain/ports/integrity-manifest-repository-port.ts +14 -0
  24. package/scripts/harness/ci-governance/domain/ports/sha256-hasher-port.ts +10 -0
  25. package/scripts/harness/ci-governance/domain/services/integrity-checker.ts +42 -0
  26. package/scripts/harness/ci-governance/domain/value-objects/integrity-drift.ts +16 -0
  27. package/scripts/harness/ci-governance/domain/value-objects/integrity-manifest.ts +49 -0
  28. package/scripts/harness/ci-governance/domain/value-objects/integrity-target.ts +40 -0
  29. package/scripts/harness/ci-governance/infrastructure/adapters/file-system-sha256-hasher-adapter.ts +17 -0
  30. package/scripts/harness/ci-governance/infrastructure/adapters/harness-api-command-existence-adapter.ts +7 -73
  31. package/scripts/harness/ci-governance/infrastructure/adapters/integrity-manifest-json-repository-adapter.ts +83 -0
  32. package/scripts/harness/ci-governance/presentation/handlers/integrity-handler.ts +76 -0
  33. package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +60 -28
  34. package/scripts/harness/harness-api/domain/value-objects/ci-check-result.ts +33 -8
  35. package/scripts/harness/harness-api/domain/value-objects/known-harness-commands.ts +90 -0
  36. package/scripts/harness/harness-error/infrastructure/registry/l1-error-definitions.ts +9 -9
  37. package/scripts/harness/installation/application/bundled-skill-selection.ts +2 -5
  38. package/scripts/harness/installation/application/usecases/run-reconcile.ts +69 -1
  39. package/scripts/harness/main.ts +257 -105
  40. package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.ts +65 -3
  41. package/scripts/harness/quick-mode/domain/services/quick-mode-judgment-engine.ts +44 -40
  42. package/scripts/harness/setup/skill-deployer.ts +2 -4
  43. package/scripts/harness/validator-system/application/use-cases/run-l2-validators-usecase.ts +82 -49
  44. package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +133 -53
  45. package/scripts/harness/validator-system/composition-root.ts +153 -99
  46. package/scripts/harness/validator-system/domain/ports/coverage-attestation-gating-policy-port.ts +14 -0
  47. package/scripts/harness/validator-system/domain/ports/coverage-attestation-verification-policy-port.ts +30 -0
  48. package/scripts/harness/validator-system/domain/ports/injection-scan-policy-port.ts +14 -0
  49. package/scripts/harness/validator-system/domain/services/coverage-attestation-gating-service.ts +56 -0
  50. package/scripts/harness/validator-system/domain/services/coverage-attestation-verification-service.ts +45 -0
  51. package/scripts/harness/validator-system/domain/services/injection-pattern-scan-service.ts +118 -0
  52. package/scripts/harness/validator-system/domain/value-objects/attestation-verification-report.ts +59 -0
  53. package/scripts/harness/validator-system/domain/value-objects/coverage-gating-report.ts +67 -0
  54. package/scripts/harness/validator-system/domain/value-objects/injection-scan-report.ts +55 -0
  55. package/scripts/harness/validator-system/domain/value-objects/validator-id.ts +37 -31
  56. package/scripts/harness/validator-system/infrastructure/adapters/adr-foundation-reference-adapter.ts +13 -7
  57. package/scripts/harness/validator-system/infrastructure/adapters/file-system-coverage-attestation-gating-adapter.ts +87 -0
  58. package/scripts/harness/validator-system/infrastructure/adapters/file-system-coverage-attestation-verification-adapter.ts +165 -0
  59. package/scripts/harness/validator-system/infrastructure/adapters/file-system-injection-scan-adapter.ts +82 -0
  60. package/skills/README.md +1 -1
  61. package/skills/codebase-mapper/SKILL.md +1 -1
  62. 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
  63. package/skills/doc-health-checker/SKILL.md +148 -0
  64. package/skills/release-publisher/SKILL.md +101 -0
  65. package/skills/skill-creator/SKILL.md +74 -332
  66. package/skills/story-implementor/SKILL.md +52 -0
  67. package/skills/story-mapper/SKILL.md +4 -0
  68. package/skills/story-writer/SKILL.md +9 -0
  69. package/skills/uiux-designer/references/uiux-design-template.md +4 -4
  70. package/skills/unit-designer/SKILL.md +3 -1
  71. package/templates/.claude/scripts/deny-check.sh +260 -0
  72. package/skills/doc-freshness-checker/SKILL.md +0 -140
  73. package/skills/implementation-planner/SKILL.md +0 -169
  74. package/skills/implementation-planner/references/document-structure.md +0 -116
  75. package/skills/implementation-planner/references/plan-template.md +0 -177
  76. package/skills/implementation-planner/references/workflow.md +0 -164
  77. package/skills/pointer-validator/SKILL.md +0 -105
@@ -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") return value;
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(rootDir: string, unit: string | null = null, inceptionRoot = "docs/inception"): Promise<void> {
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(rootDir: string, relativePath: string): Promise<DeployManifestRecord | null> {
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 = "l4-strict" | "codex-hooks" | "ci-fail-on-warning" | "baseline-reset" | "quick-mode-strict" | "quick-mode-relax" | "retrofit-bootstrap" | "planning-mode-relax";
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 = (!includeClaude || input.checks.claudeSettings) && (!includeCodex || input.checks.codexHooks);
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) externalActions.push("Run codex features enable hooks if Codex hooks are not enabled for the user.");
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: externalActions.length > 0 ? "PhaseGate cannot prove these user-level or hosted-service states from local files." : null,
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: "Run setup:agent --agent claude --apply, then ask Claude Code to read CLAUDE.md before planning work.",
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(rootDir: string, input: {
1310
- readonly intent: SetupIntent;
1311
- readonly agent: AgentTarget;
1312
- readonly withHusky: boolean;
1313
- readonly withCi: boolean;
1314
- readonly workflow: WorkflowMode;
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 ? "Add phasegate devDependency and phasegate scripts to package.json." : "Keep existing package.json and merge missing phasegate scripts only.",
1331
- !checks.phasegateConfig ? `Create phasegate.config.json for ${input.workflow} workflow.` : "Keep existing phasegate.config.json; review config:plan before changing policy.",
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 ? "Create or refresh Husky pre-commit, commit-msg, and pre-push backstops." : "Leave Husky hooks unmanaged in this setup run.",
1335
- input.withCi ? "Create or refresh GitHub Actions PhaseGate workflow." : "Leave CI workflow unmanaged in this setup run.",
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" ? "Do you want both Claude and Codex context files, or only the agent you actively use?" : null,
1339
- input.intent === "retrofit" ? "Should existing hooks/workflows be preserved as user-owned content or migrated into PhaseGate managed blocks?" : null,
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" ? "Do you want strict WI/product reflection enforcement now, or after the first green run?" : null,
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<ConfigChangeIntent, readonly { readonly pointer: string; readonly path: readonly string[]; readonly value: unknown }[]> = {
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
- { pointer: "/quickMode/allowedCategories", path: ["quickMode", "allowedCategories"], value: ["bugfix", "docs", "test", "config"] },
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(rootDir: string, plan: Awaited<ReturnType<typeof buildConfigChangePlan>>): Promise<ConfigApplyResult> {
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<ConfigChangeIntent, {
1502
- readonly targets: readonly string[];
1503
- readonly managedTargets: readonly string[];
1504
- readonly externalActions: readonly { readonly id: string; readonly label: string; readonly command: string | null; readonly blocking: boolean }[];
1505
- readonly commands: readonly string[];
1506
- readonly validations: readonly string[];
1507
- readonly risks: readonly string[];
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: ["phasegate config:plan --intent l4-strict --apply --json", "phasegate validate --layer L4 --fail-on-warning --format human"],
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: [{ id: "codex-hooks-feature", label: "Enable Codex user-level hooks feature.", command: "codex features enable hooks", blocking: true }],
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: [{ id: "github-actions-first-run", label: "Trigger or inspect the first GitHub Actions PhaseGate run.", command: null, blocking: false }],
1529
- commands: ["phasegate install --with-ci --apply", "phasegate config:plan --intent ci-fail-on-warning --apply --json", "phasegate validate --layer L4 --fail-on-warning"],
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: ["phasegate config:plan --intent quick-mode-strict --apply --json", "phasegate check-change-category --paths <changed-files> --format json"],
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: ["phasegate config:plan --intent quick-mode-relax --json", "phasegate config:plan --intent quick-mode-relax --apply --json", "phasegate check-change-category --paths <changed-files> --format json"],
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: ["Small bugfix/docs/test/config changes can proceed through Quick Mode again; protected files remain governed by hook and managed command policies."],
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: ["phasegate.config.json: planningMode.default", "phasegate.config.json: phaseDependencies.override", "phasegate.config.json: quickMode.relaxedGates"],
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: ["phasegate baseline --dry-run", "phasegate config:plan --intent retrofit-bootstrap --json", "phasegate config:plan --intent retrofit-bootstrap --apply --json"],
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: ["Manual planning mode accepts existing retrofit planning evidence; review the patch before applying it to avoid weakening greenfield projects."],
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: ["phasegate config:plan --intent planning-mode-relax --json", "phasegate config:plan --intent planning-mode-relax --apply --json"],
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: ["Manual planning mode reduces PhaseGate's QA enforcement for plan documents until strict planning is restored."],
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: "Review the listed targets first, apply through PhaseGate managed commands where possible, then run the validations in order.",
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 ? await createFileManifestRecord(rootDir, "package.json") : null,
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 ? await createFileManifestRecord(rootDir, join(".claude", "settings.json")) : null,
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 ? await createSymlinkManifestRecord(rootDir, join(".claude", "skills")) : 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(`✓ strict workflow configured (quickMode.relaxedGates: [], allowedCategories: ["bugfix","docs","test","config"])`);
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("Usage: phasegate scaffold-wi <unit|_cross> <story|issue|fix|refactor|chore> [--id <work-item-id>] [--root <path>]");
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 = ["--dry-run", "--apply", "--force", "--json", "--agent", "--skills", "--workflow", "--with-husky", "--with-ci", "--personal"];
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 = ["--intent", "--agent", "--workflow", "--with-husky", "--with-ci", "--dry-run", "--apply", "--force", "--json"];
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(parseFlag(args, "--workflow") ?? (intent === "strict" ? "strict" : "standard"));
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(rootDir, "my-project", workflow === "strict" ? "full" : "standard", {
2186
- ciEnabled: withCi,
2187
- workflow,
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) console.log(`- ${operation.op} ${operation.pointer}`);
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(JSON.stringify({ intent: plan.intent, refused: true, error: message, configPatch: plan.configPatch }, null, 2));
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
- requirementsPath: parseFlag(args, "--requirements") ?? "docs/product/user_stories.md",
2823
- testRoot: parseFlag(args, "--tests") ?? "scripts/harness/__tests__",
2824
- matrixFilePath: parseFlag(args, "--out") ?? ".harness/requirement-test-matrix.json",
2825
- }, flags);
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(JSON.stringify({
3251
- code: "MODEL_DELEGATION_DISABLED",
3252
- message: "phasegate delegate-sonnet is disabled by modelRouting.delegation=none",
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");