phasegate 0.160.14 → 0.160.15

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/README.md CHANGED
@@ -109,7 +109,7 @@ npx phasegate install --personal --agent claude --dry-run
109
109
  npx phasegate install --personal --agent claude --apply
110
110
  ```
111
111
 
112
- `--personal` does not plan or write `package.json`, `AGENTS.md`, `CLAUDE.md`, `.husky/*`, `.github/workflows/*`, `.gitignore`, GitHub CLI config, repo secrets, or CI settings. For Claude Code it automatically creates `.phasegate-local/phasegate.config.json`, `.phasegate-local/claude/settings.json`, `.phasegate-local/skills/`, ignored root `.claude/settings.json` and `.claude/skills` shims, a managed local-only block in `.git/info/exclude`, and `.phasegate/manifest.json`. Codex user-level hook setup remains a manual action instead of a project-local `.codex/hooks.json` write. <!-- @work-item-id WI-207 --> <!-- @work-item-id WI-208 -->
112
+ `--personal` does not plan or write `package.json`, `AGENTS.md`, `CLAUDE.md`, `.husky/*`, `.github/workflows/*`, `.gitignore`, GitHub CLI config, repo secrets, or CI settings. It creates `.phasegate-local/phasegate.config.json`, real local-only agent runtime artifacts for the selected agent (`.claude/settings.json` + `.claude/skills/` and/or `.codex/hooks.json` + `.codex/skills/`), a managed local-only block in `.git/info/exclude`, and `.phasegate/manifest.json`. Codex user-level hook feature enablement remains a manual action. <!-- @work-item-id WI-207 --> <!-- @work-item-id WI-208 --> <!-- @work-item-id WI-209 -->
113
113
 
114
114
  For agent-driven setup planning, use:
115
115
 
@@ -12,7 +12,7 @@ Start with one question: what kind of repository are you setting up?
12
12
  |---|---|---|
13
13
  | New project with agent hooks | `npx phasegate init --name <project> --agent both --with-husky --with-ci` | `phasegate.config.json`, skills, hooks, agent context files, Husky, and CI targets exist |
14
14
  | Existing repository | `npx phasegate install --dry-run` then `npx phasegate install --apply` | Existing hooks/scripts are preserved and `.phasegate/manifest.json` records managed targets |
15
- | Personal evaluation in a team repository | `npx phasegate install --personal --agent claude --dry-run` then `npx phasegate install --personal --agent claude --apply` | Team-owned files stay untouched; `.phasegate-local/` stores config/settings/skills and ignored `.claude/*` shims expose them to Claude Code |
15
+ | Personal evaluation in a team repository | `npx phasegate install --personal --agent claude --dry-run` then `npx phasegate install --personal --agent claude --apply` | Team-owned files stay untouched; `.phasegate-local/` stores config and ignored real `.claude/*` / `.codex/*` runtime artifacts expose hooks and skills to selected agents |
16
16
  | CI-only rollout | `npx phasegate setup:agent --intent ci-only --with-ci --dry-run --json` | The plan explains CI changes without requiring local hooks |
17
17
  | Codex-only project | `npx phasegate init --agent codex --with-husky` | `.codex/hooks.json`, `AGENTS.md`, `.codex/skills`, and pre-commit backstop are present |
18
18
  | Strict validation rollout | `npx phasegate setup:agent --intent strict --dry-run --json` | The plan lists strict checks, L4 risk, rollback, and validation commands |
@@ -65,7 +65,7 @@ npx phasegate install --personal --agent claude --dry-run
65
65
  npx phasegate install --personal --agent claude --apply
66
66
  ```
67
67
 
68
- Personal install keeps team-owned files out of both the plan and apply path: `package.json`, `AGENTS.md`, `CLAUDE.md`, `.husky/*`, `.github/workflows/*`, `.gitignore`, GitHub CLI config, repo secrets, and CI settings are not touched. For Claude Code, PhaseGate writes local-only artifacts under `.phasegate-local/`, creates ignored root `.claude/settings.json` and `.claude/skills` shims for Claude discovery, records `.phasegate/manifest.json`, and manages a local exclude block in `.git/info/exclude`. Codex user-level hook setup is reported as a manual action instead of writing project-local `.codex/hooks.json`. <!-- @work-item-id WI-207 --> <!-- @work-item-id WI-208 -->
68
+ Personal install keeps team-owned files out of both the plan and apply path: `package.json`, `AGENTS.md`, `CLAUDE.md`, `.husky/*`, `.github/workflows/*`, `.gitignore`, GitHub CLI config, repo secrets, and CI settings are not touched. PhaseGate writes `.phasegate-local/phasegate.config.json`, creates real local-only agent runtime artifacts for the selected agent (`.claude/settings.json` + `.claude/skills/` and/or `.codex/hooks.json` + `.codex/skills/`), records `.phasegate/manifest.json`, and manages a local exclude block in `.git/info/exclude`. Codex user-level hook feature enablement is still reported as a manual action. <!-- @work-item-id WI-207 --> <!-- @work-item-id WI-208 --> <!-- @work-item-id WI-209 -->
69
69
 
70
70
  For agent-readable planning before writing files:
71
71
 
@@ -6,16 +6,17 @@ PhaseGate setup is more than `phasegate.config.json`. A healthy installation is
6
6
  <!-- @work-item-id WI-157 -->
7
7
  <!-- @work-item-id WI-169 -->
8
8
  <!-- @work-item-id WI-208 -->
9
+ <!-- @work-item-id WI-209 -->
9
10
 
10
11
  ## Artifact Classes
11
12
 
12
13
  | Class | Examples | Owner | Lifecycle |
13
14
  |---|---|---|---|
14
- | Managed target | `.claude/settings.json`, `.codex/hooks.json`, `CLAUDE.md`, `AGENTS.md`, `.husky/pre-commit`, `.husky/commit-msg`, `.husky/pre-push`, `.github/workflows/phasegate-aidlc-gate.yml`, `.claude/skills`, `.codex/skills`, `package.json` PhaseGate scripts/devDependency | PhaseGate managed block or symlink plus user content | Created or merged by `install`, refreshed by `reconcile`, removed or reversed by `uninstall` |
15
- | Configuration | `phasegate.config.json`, `package.json` | User owned, PhaseGate assisted | Created by `init`; `install` may merge scripts/devDependency into `package.json` |
15
+ | Managed target | `phasegate.config.json`, `.claude/settings.json`, `.codex/hooks.json`, `CLAUDE.md`, `AGENTS.md`, `.husky/pre-commit`, `.husky/commit-msg`, `.husky/pre-push`, `.github/workflows/phasegate-aidlc-gate.yml`, `.claude/skills`, `.codex/skills`, `package.json` PhaseGate scripts/devDependency | PhaseGate managed block, real runtime artifact, or symlink plus user content | Created or merged by `install`, refreshed by `reconcile`, removed or reversed by `uninstall` |
16
+ | Configuration | `phasegate.config.json`, `package.json` | User owned, PhaseGate assisted | Created by `init` or project install when absent; `install` may merge scripts/devDependency into `package.json` |
16
17
  | Generated artifact | `.phasegate/manifest.json`, `.phasegate/backups/*`, `.phasegate/uninstalled-*.json`, `.phasegate/baseline.json` | PhaseGate | Written by lifecycle commands and validators; safe to regenerate only through the owning command |
17
18
  | Runtime state/report | `.phasegate/hook-skip-events.jsonl`, explicit `doctor --report-out <path>` output, `reports/regression/*`, resolved `reporting.outputDir` reports | PhaseGate command output | Produced while hooks, doctor, and validation commands run |
18
- | Personal install artifact | `.phasegate-local/phasegate.config.json`, `.phasegate-local/claude/settings.json`, `.phasegate-local/skills/`, ignored root `.claude/settings.json` and `.claude/skills` shims, `.git/info/exclude` PhaseGate block | One developer on one machine | Created by `phasegate install --personal --agent claude`; team `.gitignore`, team-owned files, GitHub CLI config, repo secrets, and CI settings are not modified |
19
+ | Personal install artifact | `.phasegate-local/phasegate.config.json`, ignored real `.claude/settings.json` + `.claude/skills/`, ignored real `.codex/hooks.json` + `.codex/skills/`, `.git/info/exclude` PhaseGate block | One developer on one machine | Created by `phasegate install --personal --agent <agent>`; team `.gitignore`, team-owned files, GitHub CLI config, repo secrets, and CI settings are not modified |
19
20
  | Legacy artifact | `.harness-hooks.yml`, old Fuse hook files, `.harness/session-state.json`, `.harness/context-priority.json`, `.harness/reports` fallback | Compatibility only | Not required for current install lifecycle unless a project intentionally keeps an archived integration |
20
21
  | User-level setting | Codex CLI `hooks` feature flag | User machine | Must be enabled manually with `codex features enable hooks`; project commands do not modify it |
21
22
 
@@ -28,6 +29,7 @@ PhaseGate setup is more than `phasegate.config.json`. A healthy installation is
28
29
  - Husky scripts when requested: `.husky/pre-commit`, `.husky/commit-msg`, `.husky/pre-push`
29
30
  - CI workflow when requested: `.github/workflows/phasegate-aidlc-gate.yml`
30
31
  - Agent skill links: `.claude/skills`, `.codex/skills`
32
+ - Project config: `phasegate.config.json` when absent, so installed agent hooks have a discoverable runtime config
31
33
  - Package metadata: PhaseGate scripts and `devDependencies.phasegate` in `package.json`
32
34
  - Manifest: `.phasegate/manifest.json`
33
35
 
@@ -37,9 +39,9 @@ PhaseGate setup is more than `phasegate.config.json`. A healthy installation is
37
39
 
38
40
  ## Personal Install Artifacts
39
41
 
40
- `phasegate install --personal --agent claude --apply` is a local-only bootstrap for evaluating PhaseGate in a team-owned repository. Substantive files are stored under `.phasegate-local/`, while root `.claude/settings.json` and `.claude/skills` are symlinks so Claude Code can discover the settings and skills. The root shims are hidden by `.git/info/exclude`; PhaseGate does not edit team `.gitignore`.
42
+ `phasegate install --personal --agent claude --apply` is a local-only bootstrap for evaluating PhaseGate in a team-owned repository. PhaseGate config is stored under `.phasegate-local/`, while selected agent runtime paths are real project-local files/directories: `.claude/settings.json` and `.claude/skills/` for Claude Code, `.codex/hooks.json` and `.codex/skills/` for Codex. These paths are hidden by `.git/info/exclude`; PhaseGate does not edit team `.gitignore`.
41
43
 
42
- If `.claude/settings.json` or `.claude/skills` already exists and is not the PhaseGate personal shim, personal install reports manual review and preserves the existing path.
44
+ If `.claude/*` or `.codex/*` already exists and is not a PhaseGate-managed personal artifact, personal install reports manual review and preserves the existing path. <!-- @work-item-id WI-209 -->
43
45
 
44
46
  ## Doctor Findings
45
47
 
@@ -0,0 +1,29 @@
1
+ {
2
+ "project": {
3
+ "name": "phasegate-project",
4
+ "preset": "standard"
5
+ },
6
+ "architecture": {
7
+ "preset": "clean"
8
+ },
9
+ "layers": {},
10
+ "quickMode": {},
11
+ "phaseDependencies": {
12
+ "preset": "default",
13
+ "override": false,
14
+ "customRules": []
15
+ },
16
+ "planningMode": {
17
+ "default": "interactive",
18
+ "perPhase": {}
19
+ },
20
+ "harnesses": {},
21
+ "paths": {
22
+ "designDocs": "docs/product/construction",
23
+ "inceptionDocs": "docs/inception"
24
+ },
25
+ "reporting": {
26
+ "format": "json",
27
+ "outputDir": "reports"
28
+ }
29
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phasegate",
3
- "version": "0.160.14",
3
+ "version": "0.160.15",
4
4
  "packageManager": "pnpm@10.30.1",
5
5
  "description": "Phasegate — AI-agnostic quality defense toolkit. Enforces structural integrity between design intent and code.",
6
6
  "license": "MIT",
@@ -1,6 +1,7 @@
1
1
  // @unit installation
2
2
  // @layer application
3
3
  // @work-item-id WI-145
4
+ // @work-item-id WI-209
4
5
 
5
6
  import { join } from "node:path";
6
7
  import type { CheckId } from "../../domain/check-id.js";
@@ -63,3 +64,7 @@ export function skillTargetLooksValid(target: string | null): boolean {
63
64
  const normalized = target.replaceAll("\\", "/").replace(/\/+$/, "");
64
65
  return normalized === "skills" || normalized === "../skills" || normalized.endsWith("/skills");
65
66
  }
67
+
68
+ export function skillDirectoryLooksValid(files: readonly string[]): boolean {
69
+ return files.some((file) => file.endsWith("/SKILL.md") || file.endsWith("\\SKILL.md") || file.endsWith(".harness-version"));
70
+ }
@@ -1,11 +1,12 @@
1
1
  // @unit installation
2
2
  // @layer application
3
3
  // @work-item-id WI-145
4
+ // @work-item-id WI-209
4
5
 
5
6
  import type { FileInspectorPort } from "../ports/file-inspector-port.js";
6
7
  import type { HeuristicCheck } from "../../domain/ports/heuristic-check.js";
7
8
  import type { DiagnosticFinding } from "../../domain/diagnostic-finding.js";
8
- import { createFinding, projectPath, skillTargetLooksValid } from "./check-utils.js";
9
+ import { createFinding, projectPath, skillDirectoryLooksValid, skillTargetLooksValid } from "./check-utils.js";
9
10
 
10
11
  export class ClaudeSkillsSymlinkCheck implements HeuristicCheck {
11
12
  readonly checkId = "claude-skills-symlink" as const;
@@ -14,11 +15,13 @@ export class ClaudeSkillsSymlinkCheck implements HeuristicCheck {
14
15
  async run(projectRoot: string, inspector: FileInspectorPort): Promise<DiagnosticFinding | null> {
15
16
  const link = await inspector.readSymlink(projectPath(projectRoot, this.target));
16
17
  if (skillTargetLooksValid(link)) return null;
18
+ const files = await inspector.listFiles(projectPath(projectRoot, this.target));
19
+ if (skillDirectoryLooksValid(files)) return null;
17
20
  return createFinding({
18
21
  checkId: this.checkId,
19
22
  severity: "red",
20
23
  target: this.target,
21
- message: ".claude/skills phasegate skills を指していません",
24
+ message: ".claude/skills phasegate skills がありません",
22
25
  repairMode: link === null ? "mechanical" : "manual",
23
26
  });
24
27
  }
@@ -1,11 +1,12 @@
1
1
  // @unit installation
2
2
  // @layer application
3
3
  // @work-item-id WI-145
4
+ // @work-item-id WI-209
4
5
 
5
6
  import type { FileInspectorPort } from "../ports/file-inspector-port.js";
6
7
  import type { HeuristicCheck } from "../../domain/ports/heuristic-check.js";
7
8
  import type { DiagnosticFinding } from "../../domain/diagnostic-finding.js";
8
- import { createFinding, projectPath, skillTargetLooksValid } from "./check-utils.js";
9
+ import { createFinding, projectPath, skillDirectoryLooksValid, skillTargetLooksValid } from "./check-utils.js";
9
10
 
10
11
  export class CodexSkillsSymlinkCheck implements HeuristicCheck {
11
12
  readonly checkId = "codex-skills-symlink" as const;
@@ -14,11 +15,13 @@ export class CodexSkillsSymlinkCheck implements HeuristicCheck {
14
15
  async run(projectRoot: string, inspector: FileInspectorPort): Promise<DiagnosticFinding | null> {
15
16
  const link = await inspector.readSymlink(projectPath(projectRoot, this.target));
16
17
  if (skillTargetLooksValid(link)) return null;
18
+ const files = await inspector.listFiles(projectPath(projectRoot, this.target));
19
+ if (skillDirectoryLooksValid(files)) return null;
17
20
  return createFinding({
18
21
  checkId: this.checkId,
19
22
  severity: "red",
20
23
  target: this.target,
21
- message: ".codex/skills phasegate skills を指していません",
24
+ message: ".codex/skills phasegate skills がありません",
22
25
  repairMode: link === null ? "mechanical" : "manual",
23
26
  });
24
27
  }
@@ -9,6 +9,7 @@
9
9
  // @work-item-id WI-183
10
10
  // @work-item-id WI-207
11
11
  // @work-item-id WI-208
12
+ // @work-item-id WI-209
12
13
 
13
14
  import { mkdir, readFile, writeFile, copyFile, chmod, access, lstat, readlink, symlink, readdir } from "node:fs/promises";
14
15
  import { dirname, join } from "node:path";
@@ -84,8 +85,7 @@ const MARKDOWN_BEGIN = "<!-- phasegate:managed-section:start -->";
84
85
  const MARKDOWN_END = "<!-- phasegate:managed-section:end -->";
85
86
  const TEXT_BEGIN = "# phasegate personal install exclude (BEGIN)";
86
87
  const TEXT_END = "# phasegate personal install exclude (END)";
87
- const PERSONAL_CLAUDE_SETTINGS_LINK = "../.phasegate-local/claude/settings.json";
88
- const PERSONAL_CLAUDE_SKILLS_LINK = "../.phasegate-local/skills";
88
+ const PERSONAL_AGENT_RUNTIME_FILES = new Set([".claude/settings.json", ".codex/hooks.json"]);
89
89
 
90
90
  function isRecord(value: unknown): value is Record<string, unknown> {
91
91
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -312,7 +312,7 @@ export class RunInstallUseCase {
312
312
  const workflow = input.workflow ?? "standard";
313
313
  const agent = input.agent ?? (includeClaude && includeCodex ? "both" : includeCodex ? "codex" : "claude");
314
314
  const targets = input.personal
315
- ? this.createPersonalTargets({ includeClaude })
315
+ ? this.createPersonalTargets({ includeClaude, includeCodex })
316
316
  : this.createTargets({ includeClaude, includeCodex, includeHusky, includeCi });
317
317
  const existingManifest = await this.manifestRepository.load(input.projectRoot);
318
318
  const baseManifest = existingManifest ?? DeploymentManifest.create(input.phasegateVersion);
@@ -330,8 +330,14 @@ export class RunInstallUseCase {
330
330
  const template = target.strategy === "markdown-managed"
331
331
  ? renderAgentContextTemplate(rawTemplate, { agent, skillSet, workflow, includeHusky, includeCi })
332
332
  : rawTemplate;
333
- const repairMode = this.repairMode(target, before);
334
- const next = this.merge(target, before, template, input.phasegateVersion);
333
+ const existingEntry = baseManifest.findEntry(target.path);
334
+ const beforeHash = before === null ? null : this.hashCalculator.compute(before);
335
+ const unmanagedPersonalRuntimeFile = input.personal
336
+ && PERSONAL_AGENT_RUNTIME_FILES.has(target.path)
337
+ && before !== null
338
+ && (existingEntry === null || beforeHash === null || !beforeHash.equals(existingEntry.hash));
339
+ const repairMode = unmanagedPersonalRuntimeFile ? "manual" : this.repairMode(target, before);
340
+ const next = unmanagedPersonalRuntimeFile ? before : this.merge(target, before, template, input.phasegateVersion);
335
341
  const didChange = before !== next;
336
342
  const action = this.actionFor(before, didChange, input.force);
337
343
  const item: InstallPlanItem = {
@@ -340,8 +346,10 @@ export class RunInstallUseCase {
340
346
  repairMode,
341
347
  strategy: target.strategy,
342
348
  changed: didChange,
343
- summary: didChange ? `${target.path}: ${action}` : `${target.path}: already up to date`,
344
- diff: this.diffSummary(before, next),
349
+ summary: unmanagedPersonalRuntimeFile
350
+ ? `${target.path}: existing non-phasegate path requires manual review`
351
+ : didChange ? `${target.path}: ${action}` : `${target.path}: already up to date`,
352
+ diff: unmanagedPersonalRuntimeFile ? "manual review required" : this.diffSummary(before, next),
345
353
  skillHint: repairMode === "ai-assisted" ? SKILL_HINT : null,
346
354
  };
347
355
  plan.push(item);
@@ -378,9 +386,9 @@ export class RunInstallUseCase {
378
386
 
379
387
  const mode = before === null ? "created" : "merged";
380
388
  const hash = this.hashCalculator.compute(next);
381
- const existingEntry = baseManifest.findEntry(target.path);
382
- if (existingEntry !== null && existingEntry.hash.equals(hash)) {
383
- manifest = manifest.addEntry(existingEntry);
389
+ const manifestEntry = baseManifest.findEntry(target.path);
390
+ if (manifestEntry !== null && manifestEntry.hash.equals(hash)) {
391
+ manifest = manifest.addEntry(manifestEntry);
384
392
  } else {
385
393
  manifest = manifest.addEntry(
386
394
  DeploymentEntry.create({
@@ -394,14 +402,20 @@ export class RunInstallUseCase {
394
402
  }
395
403
  }
396
404
 
397
- if (input.personal && includeClaude) {
398
- const item = await this.planPersonalSkills(input);
405
+ const personalSkillTargets = input.personal
406
+ ? [
407
+ ...(includeClaude ? [".claude/skills"] : []),
408
+ ...(includeCodex ? [".codex/skills"] : []),
409
+ ]
410
+ : [];
411
+ for (const skillPath of personalSkillTargets) {
412
+ const item = await this.planPersonalSkillDirectory(input, skillPath, baseManifest);
399
413
  plan.push(item);
400
- if (input.apply && item.changed) {
414
+ if (input.apply && item.changed && item.repairMode === "mechanical") {
401
415
  try {
402
- await copyDirectory(join(input.harnessRoot, "skills"), join(input.projectRoot, ".phasegate-local", "skills"));
416
+ await copyDirectory(join(input.harnessRoot, "skills"), join(input.projectRoot, skillPath));
403
417
  await writeFile(
404
- join(input.projectRoot, ".phasegate-local", "skills", ".harness-version"),
418
+ join(input.projectRoot, skillPath, ".harness-version"),
405
419
  `${JSON.stringify({ version: input.phasegateVersion, deployedAt: new Date().toISOString(), skillSet }, null, 2)}\n`,
406
420
  "utf8",
407
421
  );
@@ -412,20 +426,13 @@ export class RunInstallUseCase {
412
426
  manifest = this.addManifestEntry(baseManifest, manifest, {
413
427
  path: item.path,
414
428
  mode: "created",
415
- contentForHash: this.personalSkillsHashInput(input.phasegateVersion, skillSet),
429
+ contentForHash: this.personalSkillsHashInput(item.path, input.phasegateVersion, skillSet),
416
430
  });
417
431
  }
418
432
  }
419
433
 
420
434
  const linkSpecs = input.personal
421
- ? [
422
- ...(includeClaude
423
- ? [
424
- { path: ".claude/settings.json", target: PERSONAL_CLAUDE_SETTINGS_LINK },
425
- { path: ".claude/skills", target: PERSONAL_CLAUDE_SKILLS_LINK },
426
- ]
427
- : []),
428
- ]
435
+ ? []
429
436
  : [
430
437
  ...(includeClaude ? [{ path: ".claude/skills", target: "../skills" }] : []),
431
438
  ...(includeCodex ? [{ path: ".codex/skills", target: "../skills" }] : []),
@@ -455,13 +462,13 @@ export class RunInstallUseCase {
455
462
 
456
463
  if (input.personal && includeCodex) {
457
464
  plan.push({
458
- path: "~/.codex/hooks.json",
465
+ path: "~/.codex/config.toml",
459
466
  action: "will-skip",
460
467
  repairMode: "manual",
461
468
  strategy: "json",
462
469
  changed: false,
463
- summary: "~/.codex/hooks.json: personal mode does not write user-level Codex settings; configure hooks manually",
464
- diff: "manual user-level Codex hook setup required",
470
+ summary: "~/.codex/config.toml: personal mode does not write user-level Codex feature flags; enable hooks manually when needed",
471
+ diff: "manual Codex hooks feature enablement may be required",
465
472
  skillHint: null,
466
473
  });
467
474
  }
@@ -504,6 +511,11 @@ export class RunInstallUseCase {
504
511
  readonly includeCi: boolean;
505
512
  }): readonly InstallTarget[] {
506
513
  return [
514
+ {
515
+ path: "phasegate.config.json",
516
+ strategy: "copy" as const,
517
+ templatePath: "docs/templates/project/phasegate.config.json",
518
+ },
507
519
  ...(options.includeClaude
508
520
  ? [
509
521
  { path: ".claude/settings.json", strategy: "json" as const, templatePath: "templates/.claude/settings.json" },
@@ -564,7 +576,7 @@ export class RunInstallUseCase {
564
576
  ];
565
577
  }
566
578
 
567
- private createPersonalTargets(options: { readonly includeClaude: boolean }): readonly InstallTarget[] {
579
+ private createPersonalTargets(options: { readonly includeClaude: boolean; readonly includeCodex: boolean }): readonly InstallTarget[] {
568
580
  return [
569
581
  {
570
582
  path: ".phasegate-local/phasegate.config.json",
@@ -574,12 +586,21 @@ export class RunInstallUseCase {
574
586
  ...(options.includeClaude
575
587
  ? [
576
588
  {
577
- path: ".phasegate-local/claude/settings.json",
589
+ path: ".claude/settings.json",
578
590
  strategy: "copy" as const,
579
591
  templatePath: "templates/.claude/settings.json",
580
592
  },
581
593
  ]
582
594
  : []),
595
+ ...(options.includeCodex
596
+ ? [
597
+ {
598
+ path: ".codex/hooks.json",
599
+ strategy: "copy" as const,
600
+ templatePath: "templates/.codex/hooks.json",
601
+ },
602
+ ]
603
+ : []),
583
604
  {
584
605
  path: ".git/info/exclude",
585
606
  strategy: "text-managed" as const,
@@ -639,25 +660,35 @@ export class RunInstallUseCase {
639
660
  };
640
661
  }
641
662
 
642
- private async planPersonalSkills(input: RunInstallInput): Promise<InstallPlanItem> {
643
- const versionPath = join(input.projectRoot, ".phasegate-local", "skills", ".harness-version");
663
+ private async planPersonalSkillDirectory(
664
+ input: RunInstallInput,
665
+ relativePath: string,
666
+ baseManifest: DeploymentManifest,
667
+ ): Promise<InstallPlanItem> {
668
+ const absolutePath = join(input.projectRoot, relativePath);
669
+ const versionPath = join(absolutePath, ".harness-version");
644
670
  const current = await readTextOrNull(versionPath);
645
671
  const expectedNeedle = `"version": "${input.phasegateVersion}"`;
646
- const changed = current === null || !current.includes(expectedNeedle);
672
+ const manifestEntry = baseManifest.findEntry(relativePath);
673
+ const pathExists = await exists(absolutePath);
674
+ const unmanagedExisting = pathExists && (current === null || manifestEntry === null);
675
+ const changed = !unmanagedExisting && (current === null || !current.includes(expectedNeedle));
647
676
  return {
648
- path: ".phasegate-local/skills",
649
- action: changed ? "missing" : "will-skip",
650
- repairMode: "mechanical",
677
+ path: relativePath,
678
+ action: changed ? "missing" : unmanagedExisting ? "will-merge" : "will-skip",
679
+ repairMode: unmanagedExisting ? "manual" : "mechanical",
651
680
  strategy: "copy-dir",
652
681
  changed,
653
- summary: changed ? ".phasegate-local/skills: deploy bundled skills" : ".phasegate-local/skills: already up to date",
654
- diff: changed ? "+ bundled skills" : "no changes",
682
+ summary: unmanagedExisting
683
+ ? `${relativePath}: existing non-phasegate directory requires manual review`
684
+ : changed ? `${relativePath}: deploy bundled skills` : `${relativePath}: already up to date`,
685
+ diff: unmanagedExisting ? "manual review required" : changed ? "+ bundled skills" : "no changes",
655
686
  skillHint: null,
656
687
  };
657
688
  }
658
689
 
659
- private personalSkillsHashInput(version: string, skillSet: "core" | "all"): string {
660
- return `personal-skills:${version}:${skillSet}`;
690
+ private personalSkillsHashInput(path: string, version: string, skillSet: "core" | "all"): string {
691
+ return `personal-skills:${path}:${version}:${skillSet}`;
661
692
  }
662
693
 
663
694
  private addManifestEntry(
@@ -5,6 +5,7 @@
5
5
  // @work-item-id WI-199
6
6
  // @work-item-id WI-207
7
7
  // @work-item-id WI-208
8
+ // @work-item-id WI-209
8
9
 
9
10
  import { access, copyFile, lstat, mkdir, readFile, readlink, rm, rmdir, writeFile } from "node:fs/promises";
10
11
  import { dirname, join, relative, resolve } from "node:path";
package/skills/README.md CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
  `.agent/skills` は旧 setup 由来の互換パスです。新規導入では管理対象にしません。<!-- @work-item-id WI-157 -->
18
18
 
19
- `phasegate install --personal` はチーム所有リポジトリでの個人評価用です。このモードでは `.claude/skills` / `.codex/skills` のリンクを作成せず、`.phasegate-local/config.json` と `.git/info/exclude` の local-only managed block だけを扱います。通常の共有導入が必要になった時点で `phasegate install --dry-run` / `phasegate install --apply` を使います。<!-- @work-item-id WI-207 -->
19
+ `phasegate install --personal` はチーム所有リポジトリでの個人評価用です。このモードでは team-owned files を変更せず、`.phasegate-local/phasegate.config.json` と `.git/info/exclude` の local-only managed block、選択 agent 用の real `.claude/*` / `.codex/*` runtime artifacts だけを扱います。通常の共有導入が必要になった時点で `phasegate install --dry-run` / `phasegate install --apply` を使います。<!-- @work-item-id WI-207 --> <!-- @work-item-id WI-209 -->
20
20
 
21
21
  ## 新しいスキルの追加
22
22
 
@@ -129,7 +129,7 @@ product-architect で Unit を作り、いくつかの logical_design を書い
129
129
  #### 観点 9: setup lifecycle と doctor finding
130
130
 
131
131
  - `phasegate doctor --json` の finding に `repairMode: "ai-assisted"` と `suggestedSkill.skillName = "phasegate-config-doctor"` がある → 本 skill が merge 方針、保持する user content、実行すべき `install --apply` / `--force` / `reconcile --apply` を提案する
132
- - チーム所有リポジトリで個人評価だけを行う相談では、通常の `install --apply` ではなく `phasegate install --personal --agent claude --dry-run` を先に提案する。personal install は `package.json`, `AGENTS.md`, `CLAUDE.md`, `.husky/*`, `.github/workflows/*`, `.gitignore`, GitHub CLI config, repo secrets, CI settings, `.codex/hooks.json` を変更対象にしない。Claude Code向けには `.phasegate-local/phasegate.config.json`, `.phasegate-local/claude/settings.json`, `.phasegate-local/skills/`, ignored root `.claude/settings.json` / `.claude/skills` shim, `.phasegate/manifest.json`, `.git/info/exclude` の managed block を自動作成する。既存 `.claude/*` がある場合は上書きせず manual review として扱う。Codex user-level hook setup は manual action として扱う。<!-- @work-item-id WI-207 --> <!-- @work-item-id WI-208 -->
132
+ - チーム所有リポジトリで個人評価だけを行う相談では、通常の `install --apply` ではなく `phasegate install --personal --agent claude --dry-run` を先に提案する。personal install は `package.json`, `AGENTS.md`, `CLAUDE.md`, `.husky/*`, `.github/workflows/*`, `.gitignore`, GitHub CLI config, repo secrets, CI settings を変更対象にしない。選択 agent 向けには `.phasegate-local/phasegate.config.json`, real `.claude/settings.json` / `.claude/skills/`, real `.codex/hooks.json` / `.codex/skills/`, `.phasegate/manifest.json`, `.git/info/exclude` の managed block を自動作成する。既存 `.claude/*` / `.codex/*` がある場合は上書きせず manual review として扱う。Codex user-level hook feature enablement は manual action として扱う。<!-- @work-item-id WI-207 --> <!-- @work-item-id WI-208 --> <!-- @work-item-id WI-209 -->
133
133
  - Claude-only / Codex-only 導入後は `phasegate doctor --agent claude --json` または `phasegate doctor --agent codex --json` を使って selected agent の readiness を読む。`scopedOutFindings` は未選択 agent の `not-applicable` 情報なので、ユーザーがその agent を導入したいと言っていない限り repair 提案にしない。`repairHint: null` / `suggestedSkill: null` は意図的な抑制で、`currentScopeRepairTarget: false` と `repairModeApplicability: "only-if-agent-selected"` は raw `repairMode` が current scope の修復指示ではないという印である。<!-- @work-item-id WI-178, WI-179, WI-180 -->
134
134
  - `repairHint` がある mechanical finding → 原則として hint のコマンドを優先し、実行前に対象ファイルと manifest の差分を確認
135
135
  - manifest parse error → `.phasegate/manifest.json` を手で修復する前に backup / uninstall / reinstall の選択肢を提示
@@ -130,7 +130,7 @@ docs/guide/ # phasegate リポジトリ自体 (dogfood)
130
130
  - 既存プロジェクト導入: `docs/guide/retrofit-adoption.md`
131
131
  - setup artifact / doctor finding / legacy artifact: `docs/guide/setup-artifacts.md`
132
132
 
133
- チーム所有リポジトリで個人評価だけを行いたい場合は、`docs/guide/installation.md` の personal install セクションを読む。`phasegate install --personal --agent claude` は `package.json`、`AGENTS.md`、`CLAUDE.md`、`.husky/*`、`.github/workflows/*`、`.gitignore`、GitHub CLI config、repo secrets、CI settings、`.codex/hooks.json` を変更せず、`.phasegate-local/phasegate.config.json`、`.phasegate-local/claude/settings.json`、`.phasegate-local/skills/`、ignored root `.claude/settings.json` / `.claude/skills` shim、`.git/info/exclude` の managed block を使う。Codex user-level hook setup は manual action として説明する。<!-- @work-item-id WI-207 --> <!-- @work-item-id WI-208 -->
133
+ チーム所有リポジトリで個人評価だけを行いたい場合は、`docs/guide/installation.md` の personal install セクションを読む。`phasegate install --personal --agent <agent>` は `package.json`、`AGENTS.md`、`CLAUDE.md`、`.husky/*`、`.github/workflows/*`、`.gitignore`、GitHub CLI config、repo secrets、CI settings を変更せず、`.phasegate-local/phasegate.config.json`、real `.claude/settings.json` / `.claude/skills/`、real `.codex/hooks.json` / `.codex/skills/`、`.git/info/exclude` の managed block を使う。Codex user-level hook feature enablement は manual action として説明する。<!-- @work-item-id WI-207 --> <!-- @work-item-id WI-208 --> <!-- @work-item-id WI-209 -->
134
134
 
135
135
  `setup-artifacts.md` は managed target / generated artifact / runtime state / legacy artifact / user-level setting の分類を持つ。`doctor --report-out` は明示 path への出力で、`.phasegate/last-doctor-report.json` は固定生成物ではない点もここを参照する。<!-- @work-item-id WI-153 -->
136
136