phasegate 0.335.0 → 0.341.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 (111) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.ja.md +22 -9
  3. package/README.md +28 -17
  4. package/docs/ADR/038-config-state-operation-permission-policy.md +5 -2
  5. package/docs/ADR/041-non-excludable-agent-trust-roots.md +68 -0
  6. package/docs/folder_management_rules.md +10 -1
  7. package/docs/guide/antigravity-integration.md +26 -0
  8. package/docs/guide/cli-reference.md +27 -7
  9. package/docs/guide/codex-integration.md +44 -107
  10. package/docs/guide/configuration.md +28 -2
  11. package/docs/guide/grok-integration.md +26 -0
  12. package/docs/guide/hooks-integration.md +34 -5
  13. package/docs/guide/installation.md +4 -2
  14. package/docs/guide/setup-artifacts.md +4 -4
  15. package/docs/guide/skills-overview.md +4 -1
  16. package/package.json +2 -1
  17. package/scripts/harness/agent-integration/application/dto/handle-pre-tool-use-dto.ts +1 -1
  18. package/scripts/harness/agent-integration/application/dto/normalized-pre-tool-use-request.ts +32 -0
  19. package/scripts/harness/agent-integration/application/usecases/handle-post-tool-use-usecase.ts +4 -0
  20. package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +38 -3
  21. package/scripts/harness/agent-integration/application/usecases/handle-stop-usecase.ts +3 -9
  22. package/scripts/harness/agent-integration/domain/ports/full-mode-requirement-query-port.ts +3 -1
  23. package/scripts/harness/agent-integration/domain/ports/story-reflection-query-port.ts +2 -0
  24. package/scripts/harness/agent-integration/domain/services/apply-patch-write-target-extractor.ts +98 -0
  25. package/scripts/harness/agent-integration/domain/services/bash-write-target-extractor.ts +6 -52
  26. package/scripts/harness/agent-integration/domain/services/hook-to-cli-translator.ts +8 -5
  27. package/scripts/harness/agent-integration/domain/value-objects/hook-translation-result.ts +2 -2
  28. package/scripts/harness/agent-integration/domain/value-objects/protected-file-list.ts +35 -18
  29. package/scripts/harness/agent-integration/domain/value-objects/story-reflection-query-result.ts +10 -2
  30. package/scripts/harness/agent-integration/domain/value-objects/write-target-scope.ts +12 -0
  31. package/scripts/harness/agent-integration/infrastructure/adapters/child-process-cli-executor-adapter.ts +64 -13
  32. package/scripts/harness/agent-integration/infrastructure/adapters/file-system-story-reflection-query-adapter.ts +108 -2
  33. package/scripts/harness/agent-integration/infrastructure/adapters/harness-config-config-query-adapter.ts +8 -6
  34. package/scripts/harness/agent-integration/presentation/hook-skip-event-recorder.ts +3 -0
  35. package/scripts/harness/agent-integration/presentation/phasegate-status-context.ts +1 -1
  36. package/scripts/harness/agent-integration/presentation/post-tool-use-feedback.ts +39 -0
  37. package/scripts/harness/agent-integration/presentation/post-tool-use-hook.ts +21 -27
  38. package/scripts/harness/agent-integration/presentation/pre-tool-use-hook.ts +126 -70
  39. package/scripts/harness/agent-integration/presentation/pre-tool-use-payload-normalizer.ts +228 -0
  40. package/scripts/harness/agent-integration/presentation/pre-tool-use-response-renderer.ts +38 -0
  41. package/scripts/harness/agent-integration/presentation/stop-hook.ts +1 -3
  42. package/scripts/harness/biome-ast-engine/infrastructure/adapters/biome-cli-executor-adapter.ts +21 -1
  43. package/scripts/harness/biome-ast-engine/infrastructure/adapters/typescript-source-module-analyzer-adapter.ts +5 -0
  44. package/scripts/harness/ci-governance/domain/value-objects/integrity-target.ts +2 -0
  45. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +15 -0
  46. package/scripts/harness/config-foundation/infrastructure/validators/ajv-config-schema-validator.ts +6 -3
  47. package/scripts/harness/harness-api/domain/ports/biome-lint-port.ts +2 -1
  48. package/scripts/harness/harness-api/domain/services/command-dispatch-service.ts +12 -1
  49. package/scripts/harness/harness-api/infrastructure/adapters/biome-ast-engine-lint-adapter.ts +10 -2
  50. package/scripts/harness/installation/application/bundled-skill-selection.ts +28 -2
  51. package/scripts/harness/installation/application/checks/antigravity-hook-missing-check.ts +79 -0
  52. package/scripts/harness/installation/application/checks/check-utils.ts +2 -2
  53. package/scripts/harness/installation/application/checks/codex-hook-missing-check.ts +70 -6
  54. package/scripts/harness/installation/application/checks/grok-hook-missing-check.ts +79 -0
  55. package/scripts/harness/installation/application/checks/husky-runtime-inactive-check.ts +33 -0
  56. package/scripts/harness/installation/application/named-hook-json.ts +17 -0
  57. package/scripts/harness/installation/application/operator-notice.ts +42 -0
  58. package/scripts/harness/installation/application/usecases/run-doctor-diagnostics.ts +18 -7
  59. package/scripts/harness/installation/application/usecases/run-install.ts +157 -57
  60. package/scripts/harness/installation/application/usecases/run-reconcile.ts +148 -31
  61. package/scripts/harness/installation/application/usecases/run-uninstall.ts +149 -25
  62. package/scripts/harness/installation/composition-root.ts +10 -0
  63. package/scripts/harness/installation/domain/agent-target.ts +39 -0
  64. package/scripts/harness/installation/domain/check-id.ts +5 -0
  65. package/scripts/harness/installation/domain/husky-runtime-state.ts +34 -0
  66. package/scripts/harness/installation/domain/ports/git-hooks-runtime-probe.ts +9 -0
  67. package/scripts/harness/installation/domain/repair-table.ts +5 -0
  68. package/scripts/harness/installation/infrastructure/adapters/git-hooks-runtime-probe-adapter.ts +62 -0
  69. package/scripts/harness/installation/presentation/cli/install-handler.ts +25 -5
  70. package/scripts/harness/installation/presentation/cli/reconcile-handler.ts +7 -1
  71. package/scripts/harness/installation/presentation/formatters/diagnostic-report-formatter.ts +27 -0
  72. package/scripts/harness/main.ts +227 -128
  73. package/scripts/harness/phase-dependency-model/domain/services/story-reflection-checker.ts +48 -5
  74. package/scripts/harness/phase-dependency-model/domain/services/work-item-reflection-scope-resolver.ts +64 -0
  75. package/scripts/harness/phase-dependency-model/domain/values/story-reflection-mapping.ts +8 -2
  76. package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.ts +6 -1
  77. package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-work-item-dependency-catalog.ts +73 -0
  78. package/scripts/harness/quick-mode/application/dto/change-category-classification-contract.ts +5 -1
  79. package/scripts/harness/quick-mode/application/dto/quick-mode-eligibility-contract.ts +1 -1
  80. package/scripts/harness/quick-mode/application/ports/change-risk-advisory-port.ts +16 -0
  81. package/scripts/harness/quick-mode/application/usecases/classify-change-category-usecase.ts +3 -1
  82. package/scripts/harness/quick-mode/composition-root.ts +5 -1
  83. package/scripts/harness/quick-mode/domain/services/quick-mode-judgment-engine.ts +13 -2
  84. package/scripts/harness/quick-mode/domain/types/rejection-rule.ts +1 -1
  85. package/scripts/harness/quick-mode/infrastructure/adapters/snapshot-risk-advisory-adapter.ts +118 -0
  86. package/scripts/harness/quick-mode/presentation/formatters/change-category-formatter.ts +7 -0
  87. package/scripts/harness/quick-mode/presentation/handlers/check-change-category-handler.ts +12 -1
  88. package/scripts/harness/regression-suite/infrastructure/adapters/vitest-test-runner-adapter.ts +13 -3
  89. package/scripts/harness/setup/skill-deployer.ts +3 -45
  90. package/scripts/harness/skill-quality/application/dto/run-plan-checker-loop-output.ts +1 -0
  91. package/scripts/harness/skill-quality/application/usecases/apply-cascade-update-usecase.ts +9 -4
  92. package/scripts/harness/skill-quality/application/usecases/execute-tdd-cycle-usecase.ts +2 -1
  93. package/scripts/harness/skill-quality/application/usecases/run-plan-checker-loop-usecase.ts +8 -0
  94. package/scripts/harness/skill-quality/composition-root.ts +14 -7
  95. package/scripts/harness/skill-quality/domain/ports/plan-check-executor-port.ts +2 -0
  96. package/scripts/harness/skill-quality/domain/value-objects/cascade-update-target.ts +15 -1
  97. package/scripts/harness/skill-quality/infrastructure/adapters/l1-biome-validator-adapter.ts +9 -1
  98. package/scripts/harness/skill-quality/infrastructure/adapters/l2-validator-system-adapter.ts +18 -2
  99. package/scripts/harness/skill-quality/presentation/handlers/apply-cascade-update-handler.ts +8 -2
  100. package/scripts/harness/skill-quality/presentation/handlers/execute-tdd-cycle-handler.ts +4 -3
  101. package/scripts/harness/skill-quality/presentation/handlers/run-plan-checker-loop-handler.ts +11 -4
  102. package/scripts/harness/traceability-model/infrastructure/parsers/work-item-frontmatter-parser.ts +41 -0
  103. package/skills/README.md +7 -0
  104. package/skills/cascade-updater/SKILL.md +37 -109
  105. package/skills/cascade-updater/references//345/261/244/345/210/245/345/210/244/345/256/232/343/202/254/343/202/244/343/203/211.md +5 -3
  106. package/skills/release-publisher/SKILL.md +3 -1
  107. package/skills/skill-creator/SKILL.md +8 -9
  108. package/templates/.agents/hooks.json +16 -0
  109. package/templates/.claude/scripts/analyze-errors-hook.sh +2 -2
  110. package/templates/.claude/settings.json +5 -3
  111. package/templates/.codex/hooks.json +2 -2
@@ -6,7 +6,10 @@
6
6
  // @work-item-id WI-215
7
7
  // @work-item-id WI-330
8
8
  // @work-item-id WI-343
9
+ // @work-item-id WI-385
10
+ // @work-item-id WI-390
9
11
 
12
+ import type { AgentTarget } from "../../domain/agent-target.js";
10
13
  import type { CheckId } from "../../domain/check-id.js";
11
14
  import type { ConfigStatus } from "../../domain/config-status.js";
12
15
  import type { DiagnosticFinding } from "../../domain/diagnostic-finding.js";
@@ -16,7 +19,7 @@ import type { ConfigStatusProbePort } from "../ports/config-status-probe-port.js
16
19
  import type { FileInspectorPort } from "../ports/file-inspector-port.js";
17
20
  import type { ManifestRepositoryPort } from "../ports/manifest-repository-port.js";
18
21
 
19
- export type DoctorAgentScope = "claude" | "codex" | "both";
22
+ export type DoctorAgentScope = AgentTarget;
20
23
 
21
24
  export interface ScopedOutDiagnosticFinding {
22
25
  readonly finding: DiagnosticFinding;
@@ -40,10 +43,13 @@ export interface RunDoctorDiagnosticsOutput {
40
43
 
41
44
  const CLAUDE_ONLY_CHECKS = new Set<CheckId>(["claude-hook-missing", "claude-context-missing", "claude-skills-symlink"]);
42
45
  const CODEX_ONLY_CHECKS = new Set<CheckId>(["codex-hook-missing", "codex-context-missing", "codex-skills-symlink"]);
46
+ const CLAUDE_COMPATIBLE_CHECKS = new Set<CheckId>(["grok-hook-missing"]);
47
+ const ANTIGRAVITY_ONLY_CHECKS = new Set<CheckId>(["antigravity-hook-missing"]);
43
48
  const PERSONAL_SCOPED_OUT_CHECKS = new Set<CheckId>([
44
49
  "husky-pre-commit-missing",
45
50
  "husky-commit-msg-missing",
46
51
  "husky-pre-push-missing",
52
+ "husky-runtime-inactive",
47
53
  "ci-workflow-missing",
48
54
  "package-json-devdep-missing",
49
55
  ]);
@@ -65,9 +71,7 @@ export class RunDoctorDiagnosticsUseCase {
65
71
  : "project";
66
72
  const configStatus = (await this.configStatusProbe.probe(input.projectRoot)).status;
67
73
  const rawFindings = (
68
- await Promise.all(
69
- this.checks.map((check) => check.run(input.projectRoot, this.inspector, { installationMode })),
70
- )
74
+ await Promise.all(this.checks.map((check) => check.run(input.projectRoot, this.inspector, { installationMode })))
71
75
  ).filter((finding) => finding !== null);
72
76
  const { findings, scopedOutFindings } = this.applyPersonalScope(
73
77
  this.applyAgentScope(rawFindings, agent),
@@ -91,12 +95,10 @@ export class RunDoctorDiagnosticsUseCase {
91
95
  readonly findings: readonly DiagnosticFinding[];
92
96
  readonly scopedOutFindings: readonly ScopedOutDiagnosticFinding[];
93
97
  } {
94
- if (agent === "both") return { findings, scopedOutFindings: [] };
95
- const scopedOutChecks = agent === "claude" ? CODEX_ONLY_CHECKS : CLAUDE_ONLY_CHECKS;
96
98
  const applicable: DiagnosticFinding[] = [];
97
99
  const scopedOut: ScopedOutDiagnosticFinding[] = [];
98
100
  for (const finding of findings) {
99
- if (scopedOutChecks.has(finding.checkId)) {
101
+ if (!this.isApplicableAgentCheck(finding.checkId, agent)) {
100
102
  scopedOut.push({
101
103
  finding,
102
104
  scopeReason: `${finding.checkId} belongs to an unselected agent for doctor --agent ${agent}.`,
@@ -108,6 +110,15 @@ export class RunDoctorDiagnosticsUseCase {
108
110
  return { findings: applicable, scopedOutFindings: scopedOut };
109
111
  }
110
112
 
113
+ private isApplicableAgentCheck(checkId: CheckId, agent: DoctorAgentScope): boolean {
114
+ if (CLAUDE_ONLY_CHECKS.has(checkId)) return agent === "claude" || agent === "both" || agent === "all";
115
+ if (CODEX_ONLY_CHECKS.has(checkId)) return agent === "codex" || agent === "both" || agent === "all";
116
+ if (CLAUDE_COMPATIBLE_CHECKS.has(checkId))
117
+ return agent === "claude" || agent === "both" || agent === "grok" || agent === "all";
118
+ if (ANTIGRAVITY_ONLY_CHECKS.has(checkId)) return agent === "antigravity" || agent === "all";
119
+ return true;
120
+ }
121
+
111
122
  private applyPersonalScope(
112
123
  scoped: {
113
124
  readonly findings: readonly DiagnosticFinding[];
@@ -19,6 +19,8 @@
19
19
  // @work-item-id WI-315
20
20
  // @work-item-id WI-326
21
21
  // @work-item-id WI-331
22
+ // @work-item-id WI-385
23
+ // @work-item-id WI-387
22
24
 
23
25
  import {
24
26
  access,
@@ -34,11 +36,13 @@ import {
34
36
  writeFile,
35
37
  } from "node:fs/promises";
36
38
  import { dirname, join } from "node:path";
39
+ import { type AgentTarget, resolveAgentTarget } from "../../domain/agent-target.js";
37
40
  import { DeploymentEntry } from "../../domain/deployment-entry.js";
38
41
  import { DeploymentManifest, type InstallationFlags } from "../../domain/deployment-manifest.js";
39
42
  import type { ManagedBlockInput } from "../../domain/managed-block.js";
40
43
  import type { RepairMode } from "../../domain/repair-mode.js";
41
44
  import { getBundledSkillsForSet, type SkillSet } from "../bundled-skill-selection.js";
45
+ import { mergeNamedHookJson } from "../named-hook-json.js";
42
46
  import type { HashCalculatorPort } from "../ports/hash-calculator-port.js";
43
47
  import type { ManifestRepositoryPort } from "../ports/manifest-repository-port.js";
44
48
  import type { ModelDelegationPort } from "../ports/model-delegation-port.js";
@@ -46,6 +50,7 @@ import type { ModelDelegationPort } from "../ports/model-delegation-port.js";
46
50
  type InstallAction = "missing" | "will-merge" | "will-skip" | "will-overwrite";
47
51
  type StrategyType =
48
52
  | "json"
53
+ | "json-named"
49
54
  | "shell"
50
55
  | "yaml-add"
51
56
  | "package-json"
@@ -81,9 +86,9 @@ export interface RunInstallInput {
81
86
  readonly includeCodex?: boolean;
82
87
  readonly includeHusky?: boolean;
83
88
  readonly includeCi?: boolean;
84
- readonly skillSet?: "core" | "all";
89
+ readonly skillSet?: SkillSet;
85
90
  readonly workflow?: "standard" | "strict";
86
- readonly agent?: "claude" | "codex" | "both";
91
+ readonly agent?: AgentTarget;
87
92
  readonly personal?: boolean;
88
93
  }
89
94
 
@@ -130,6 +135,7 @@ const PERSONAL_AGENT_RUNTIME_FILES = new Set([
130
135
  ".claude/settings.json",
131
136
  "AGENTS.md",
132
137
  ".codex/hooks.json",
138
+ ".agents/hooks.json",
133
139
  ]);
134
140
  const SHARED_SKILLS_VERSION_PATH = "skills/.harness-version";
135
141
  const PERSONAL_PRINCIPLES_DOCS = ".phasegate-local/docs/principles";
@@ -361,8 +367,8 @@ function mergeManagedMarkdown(existing: string | null, incoming: string): string
361
367
  function renderAgentContextTemplate(
362
368
  template: string,
363
369
  options: {
364
- readonly agent: "claude" | "codex" | "both";
365
- readonly skillSet: "core" | "all";
370
+ readonly agent: AgentTarget;
371
+ readonly skillSet: SkillSet;
366
372
  readonly workflow: "standard" | "strict";
367
373
  readonly includeHusky: boolean;
368
374
  readonly includeCi: boolean;
@@ -382,7 +388,7 @@ function renderAgentContextTemplate(
382
388
  .replaceAll("{{PHASEGATE_HUSKY_STATE}}", options.includeHusky ? "managed" : "not managed by this setup run")
383
389
  .replaceAll("{{PHASEGATE_CI_STATE}}", options.includeCi ? "managed" : "not managed by this setup run")
384
390
  .replaceAll("{{PHASEGATE_COMMANDS}}", commands)
385
- .replaceAll("{{PHASEGATE_SKILLS}}", options.skillSet === "core" ? "- `core skills`" : "- `all bundled skills`")
391
+ .replaceAll("{{PHASEGATE_SKILLS}}", options.skillSet === "all" ? "- `all bundled skills`" : `- \`${options.skillSet} skills\``)
386
392
  .replaceAll("{{PHASEGATE_PRESETS}}", "- `minimal`\n- `standard`\n- `full`\n- `custom`")
387
393
  .replaceAll("{{PHASEGATE_USER_SECTION}}", USER_SECTION_PLACEHOLDER);
388
394
  }
@@ -409,20 +415,6 @@ function mergePackageJson(existing: Record<string, unknown>, version: string): R
409
415
  };
410
416
  }
411
417
 
412
- function hasCustomJson(content: string | null): boolean {
413
- if (content === null) return false;
414
- try {
415
- const parsed = JSON.parse(content) as unknown;
416
- if (!isRecord(parsed)) return true;
417
- const withoutEmptyHooks = { ...parsed };
418
- if (isRecord(withoutEmptyHooks.hooks) && Object.keys(withoutEmptyHooks.hooks).length === 0)
419
- delete withoutEmptyHooks.hooks;
420
- return Object.keys(withoutEmptyHooks).length > 0;
421
- } catch {
422
- return true;
423
- }
424
- }
425
-
426
418
  function errorCode(error: unknown): string {
427
419
  if (isRecord(error) && typeof error.code === "string") return error.code;
428
420
  return "UNKNOWN";
@@ -471,17 +463,19 @@ export class RunInstallUseCase {
471
463
  ) {}
472
464
 
473
465
  async execute(input: RunInstallInput): Promise<RunInstallResult> {
474
- const includeClaude = input.includeClaude ?? true;
475
- const includeCodex = input.includeCodex ?? true;
466
+ const legacyIncludeClaude = input.includeClaude ?? true;
467
+ const legacyIncludeCodex = input.includeCodex ?? true;
476
468
  const includeHusky = input.includeHusky ?? true;
477
469
  const includeCi = input.includeCi ?? true;
478
470
  const skillSet = input.skillSet ?? "all";
479
471
  const workflow = input.workflow ?? "standard";
480
- const agent = input.agent ?? (includeClaude && includeCodex ? "both" : includeCodex ? "codex" : "claude");
472
+ const agent =
473
+ input.agent ?? (legacyIncludeClaude && legacyIncludeCodex ? "both" : legacyIncludeCodex ? "codex" : "claude");
474
+ const selection = resolveAgentTarget(agent);
481
475
  const personal = input.personal ?? false;
482
476
  const targets = personal
483
- ? this.createPersonalTargets({ includeClaude, includeCodex })
484
- : this.createTargets({ includeClaude, includeCodex, includeHusky, includeCi });
477
+ ? this.createPersonalTargets(selection)
478
+ : this.createTargets({ ...selection, includeHusky, includeCi });
485
479
  // Persist the effective opt-in state so a later reconcile can honor the
486
480
  // original install options. Personal installs never deploy Husky/CI
487
481
  // targets, so their effective state is recorded as false regardless of
@@ -584,7 +578,7 @@ export class RunInstallUseCase {
584
578
  const mode = before === null ? "created" : "merged";
585
579
  const hash = this.hashCalculator.compute(next);
586
580
  const manifestEntry = baseManifest.findEntry(target.path);
587
- if (manifestEntry !== null && manifestEntry.hash.equals(hash)) {
581
+ if (manifestEntry?.hash.equals(hash)) {
588
582
  manifest = manifest.addEntry(manifestEntry);
589
583
  } else {
590
584
  manifest = manifest.addEntry(
@@ -600,7 +594,11 @@ export class RunInstallUseCase {
600
594
  }
601
595
 
602
596
  const personalSkillTargets = input.personal
603
- ? [...(includeClaude ? [".claude/skills"] : []), ...(includeCodex ? [".codex/skills"] : [])]
597
+ ? [
598
+ ...(selection.claudeSkills ? [".claude/skills"] : []),
599
+ ...(selection.codexSkills ? [".codex/skills"] : []),
600
+ ...(selection.antigravitySkills ? [".agents/skills"] : []),
601
+ ]
604
602
  : [];
605
603
  const selectedPersonalSkills = input.personal ? await listSelectedBundledSkills(input.harnessRoot, skillSet) : [];
606
604
  for (const skillPath of personalSkillTargets) {
@@ -644,7 +642,7 @@ export class RunInstallUseCase {
644
642
  }
645
643
  }
646
644
 
647
- if (!input.personal && (includeClaude || includeCodex)) {
645
+ if (!input.personal && (selection.claudeSkills || selection.codexSkills || selection.antigravitySkills)) {
648
646
  const sharedSkills = await listSelectedBundledSkills(input.harnessRoot, skillSet);
649
647
  const item = await this.planSharedSkillDirectory(input, sharedSkills, baseManifest);
650
648
  plan.push(item);
@@ -673,8 +671,9 @@ export class RunInstallUseCase {
673
671
  const linkSpecs = input.personal
674
672
  ? []
675
673
  : [
676
- ...(includeClaude ? [{ path: ".claude/skills", target: "../skills" }] : []),
677
- ...(includeCodex ? [{ path: ".codex/skills", target: "../skills" }] : []),
674
+ ...(selection.claudeSkills ? [{ path: ".claude/skills", target: "../skills" }] : []),
675
+ ...(selection.codexSkills ? [{ path: ".codex/skills", target: "../skills" }] : []),
676
+ ...(selection.antigravitySkills ? [{ path: ".agents/skills", target: "../skills" }] : []),
678
677
  ];
679
678
  for (const linkSpec of linkSpecs) {
680
679
  const item = await this.planSkillLink(input.projectRoot, linkSpec.path, linkSpec.target);
@@ -703,7 +702,7 @@ export class RunInstallUseCase {
703
702
  });
704
703
  }
705
704
 
706
- if (input.personal && includeCodex) {
705
+ if (input.personal && selection.codexHook) {
707
706
  plan.push({
708
707
  path: "~/.codex/config.toml",
709
708
  action: "will-skip",
@@ -770,8 +769,11 @@ export class RunInstallUseCase {
770
769
  }
771
770
 
772
771
  private createTargets(options: {
773
- readonly includeClaude: boolean;
774
- readonly includeCodex: boolean;
772
+ readonly claudeHook: boolean;
773
+ readonly claudeContext: boolean;
774
+ readonly codexHook: boolean;
775
+ readonly agentsContext: boolean;
776
+ readonly antigravityHook: boolean;
775
777
  readonly includeHusky: boolean;
776
778
  readonly includeCi: boolean;
777
779
  }): readonly InstallTarget[] {
@@ -781,7 +783,7 @@ export class RunInstallUseCase {
781
783
  strategy: "copy" as const,
782
784
  templatePath: "docs/templates/project/phasegate.config.json",
783
785
  },
784
- ...(options.includeClaude
786
+ ...(options.claudeHook
785
787
  ? [
786
788
  {
787
789
  path: ".claude/settings.json",
@@ -789,16 +791,60 @@ export class RunInstallUseCase {
789
791
  templatePath: "templates/.claude/settings.json",
790
792
  },
791
793
  {
792
- path: "CLAUDE.md",
793
- strategy: "markdown-managed" as const,
794
- templatePath: "docs/templates/agent-context/CLAUDE.md.template.md",
795
- block: { start: MARKDOWN_BEGIN, end: MARKDOWN_END, content: "phasegate CLAUDE.md managed section" },
794
+ path: ".claude/scripts/deny-check.sh",
795
+ strategy: "shell" as const,
796
+ templatePath: "templates/.claude/scripts/deny-check.sh",
797
+ executable: true,
798
+ },
799
+ {
800
+ path: ".claude/scripts/format-settings-hook.sh",
801
+ strategy: "shell" as const,
802
+ templatePath: "templates/.claude/scripts/format-settings-hook.sh",
803
+ executable: true,
804
+ },
805
+ {
806
+ path: ".claude/scripts/format-typescript-hook.sh",
807
+ strategy: "shell" as const,
808
+ templatePath: "templates/.claude/scripts/format-typescript-hook.sh",
809
+ executable: true,
796
810
  },
811
+ {
812
+ path: ".claude/scripts/analyze-errors-hook.sh",
813
+ strategy: "shell" as const,
814
+ templatePath: "templates/.claude/scripts/analyze-errors-hook.sh",
815
+ executable: true,
816
+ },
817
+ {
818
+ path: ".claude/scripts/hook-config.json",
819
+ strategy: "copy" as const,
820
+ templatePath: "templates/.claude/scripts/hook-config.json",
821
+ },
822
+ ...(options.claudeContext
823
+ ? [
824
+ {
825
+ path: "CLAUDE.md",
826
+ strategy: "markdown-managed" as const,
827
+ templatePath: "docs/templates/agent-context/CLAUDE.md.template.md",
828
+ block: { start: MARKDOWN_BEGIN, end: MARKDOWN_END, content: "phasegate CLAUDE.md managed section" },
829
+ },
830
+ ]
831
+ : []),
797
832
  ]
798
833
  : []),
799
- ...(options.includeCodex
834
+ ...(options.codexHook
835
+ ? [{ path: ".codex/hooks.json", strategy: "json" as const, templatePath: "templates/.codex/hooks.json" }]
836
+ : []),
837
+ ...(options.antigravityHook
838
+ ? [
839
+ {
840
+ path: ".agents/hooks.json",
841
+ strategy: "json-named" as const,
842
+ templatePath: "templates/.agents/hooks.json",
843
+ },
844
+ ]
845
+ : []),
846
+ ...(options.agentsContext
800
847
  ? [
801
- { path: ".codex/hooks.json", strategy: "json" as const, templatePath: "templates/.codex/hooks.json" },
802
848
  {
803
849
  path: "AGENTS.md",
804
850
  strategy: "markdown-managed" as const,
@@ -846,8 +892,11 @@ export class RunInstallUseCase {
846
892
  }
847
893
 
848
894
  private createPersonalTargets(options: {
849
- readonly includeClaude: boolean;
850
- readonly includeCodex: boolean;
895
+ readonly claudeHook: boolean;
896
+ readonly claudeContext: boolean;
897
+ readonly codexHook: boolean;
898
+ readonly agentsContext: boolean;
899
+ readonly antigravityHook: boolean;
851
900
  }): readonly InstallTarget[] {
852
901
  return [
853
902
  {
@@ -855,27 +904,60 @@ export class RunInstallUseCase {
855
904
  strategy: "copy" as const,
856
905
  templatePath: "docs/templates/personal/phasegate-local-config.json",
857
906
  },
858
- ...(options.includeClaude
907
+ ...(options.claudeHook
859
908
  ? [
860
- {
861
- path: ".claude/CLAUDE.md",
862
- strategy: "markdown-managed" as const,
863
- templatePath: "docs/templates/agent-context/CLAUDE.md.template.md",
864
- block: {
865
- start: MARKDOWN_BEGIN,
866
- end: MARKDOWN_END,
867
- content: "phasegate personal .claude/CLAUDE.md managed section",
868
- },
869
- personalManualIfUnmanaged: true,
870
- },
909
+ ...(options.claudeContext
910
+ ? [
911
+ {
912
+ path: ".claude/CLAUDE.md",
913
+ strategy: "markdown-managed" as const,
914
+ templatePath: "docs/templates/agent-context/CLAUDE.md.template.md",
915
+ block: {
916
+ start: MARKDOWN_BEGIN,
917
+ end: MARKDOWN_END,
918
+ content: "phasegate personal .claude/CLAUDE.md managed section",
919
+ },
920
+ personalManualIfUnmanaged: true,
921
+ },
922
+ ]
923
+ : []),
871
924
  {
872
925
  path: ".claude/settings.json",
873
926
  strategy: "copy" as const,
874
927
  templatePath: "templates/.claude/settings.json",
875
928
  },
929
+ {
930
+ path: ".claude/scripts/deny-check.sh",
931
+ strategy: "shell" as const,
932
+ templatePath: "templates/.claude/scripts/deny-check.sh",
933
+ executable: true,
934
+ },
935
+ {
936
+ path: ".claude/scripts/format-settings-hook.sh",
937
+ strategy: "shell" as const,
938
+ templatePath: "templates/.claude/scripts/format-settings-hook.sh",
939
+ executable: true,
940
+ },
941
+ {
942
+ path: ".claude/scripts/format-typescript-hook.sh",
943
+ strategy: "shell" as const,
944
+ templatePath: "templates/.claude/scripts/format-typescript-hook.sh",
945
+ executable: true,
946
+ },
947
+ {
948
+ path: ".claude/scripts/analyze-errors-hook.sh",
949
+ strategy: "shell" as const,
950
+ templatePath: "templates/.claude/scripts/analyze-errors-hook.sh",
951
+ executable: true,
952
+ },
953
+ {
954
+ path: ".claude/scripts/hook-config.json",
955
+ strategy: "copy" as const,
956
+ templatePath: "templates/.claude/scripts/hook-config.json",
957
+ },
876
958
  ]
877
959
  : []),
878
- ...(options.includeCodex
960
+ ...(options.agentsContext
879
961
  ? [
880
962
  {
881
963
  path: "AGENTS.md",
@@ -888,6 +970,10 @@ export class RunInstallUseCase {
888
970
  },
889
971
  personalManualIfUnmanaged: true,
890
972
  },
973
+ ]
974
+ : []),
975
+ ...(options.codexHook
976
+ ? [
891
977
  {
892
978
  path: ".codex/hooks.json",
893
979
  strategy: "copy" as const,
@@ -895,6 +981,15 @@ export class RunInstallUseCase {
895
981
  },
896
982
  ]
897
983
  : []),
984
+ ...(options.antigravityHook
985
+ ? [
986
+ {
987
+ path: ".agents/hooks.json",
988
+ strategy: "json-named" as const,
989
+ templatePath: "templates/.agents/hooks.json",
990
+ },
991
+ ]
992
+ : []),
898
993
  {
899
994
  path: ".git/hooks/pre-commit",
900
995
  strategy: "copy" as const,
@@ -943,6 +1038,7 @@ export class RunInstallUseCase {
943
1038
  if (target.strategy === "copy-dir") return "mechanical";
944
1039
  if (target.strategy === "symlink") return "mechanical";
945
1040
  if (target.strategy === "json") return jsonRepairMode(before);
1041
+ if (target.strategy === "json-named") return jsonRepairMode(before);
946
1042
  if (target.strategy === "markdown-managed") return "mechanical";
947
1043
  return "mechanical";
948
1044
  }
@@ -966,6 +1062,10 @@ export class RunInstallUseCase {
966
1062
  }
967
1063
  const existing = before === null ? {} : (JSON.parse(before) as unknown);
968
1064
  const incoming = JSON.parse(template) as unknown;
1065
+ if (target.strategy === "json-named") {
1066
+ const merged = mergeNamedHookJson(isRecord(existing) ? existing : {}, isRecord(incoming) ? incoming : {});
1067
+ return `${JSON.stringify(merged, null, 2)}\n`;
1068
+ }
969
1069
  const merged = mergeJsonObject(isRecord(existing) ? existing : {}, isRecord(incoming) ? incoming : {});
970
1070
  return `${JSON.stringify(merged, null, 2)}\n`;
971
1071
  }
@@ -1046,13 +1146,13 @@ export class RunInstallUseCase {
1046
1146
  private personalSkillsVersionHashInput(
1047
1147
  path: string,
1048
1148
  version: string,
1049
- skillSet: "core" | "all",
1149
+ skillSet: SkillSet,
1050
1150
  skills: readonly string[],
1051
1151
  ): string {
1052
1152
  return `personal-skills-version:${path}:${version}:${skillSet}:${skills.join(",")}`;
1053
1153
  }
1054
1154
 
1055
- private personalSkillHashInput(path: string, skill: string, version: string, skillSet: "core" | "all"): string {
1155
+ private personalSkillHashInput(path: string, skill: string, version: string, skillSet: SkillSet): string {
1056
1156
  return `personal-skill:${path}:${skill}:${version}:${skillSet}`;
1057
1157
  }
1058
1158
 
@@ -1124,7 +1224,7 @@ export class RunInstallUseCase {
1124
1224
  ): DeploymentManifest {
1125
1225
  const hash = this.hashCalculator.compute(input.contentForHash);
1126
1226
  const existingEntry = baseManifest.findEntry(input.path);
1127
- if (existingEntry !== null && existingEntry.hash.equals(hash)) {
1227
+ if (existingEntry?.hash.equals(hash)) {
1128
1228
  return manifest.addEntry(existingEntry);
1129
1229
  }
1130
1230
  return manifest.addEntry(