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 +1 -1
- package/docs/guide/getting-started.md +1 -1
- package/docs/guide/installation.md +1 -1
- package/docs/guide/setup-artifacts.md +7 -5
- package/docs/templates/project/phasegate.config.json +29 -0
- package/package.json +1 -1
- package/scripts/harness/installation/application/checks/check-utils.ts +5 -0
- package/scripts/harness/installation/application/checks/claude-skills-symlink-check.ts +5 -2
- package/scripts/harness/installation/application/checks/codex-skills-symlink-check.ts +5 -2
- package/scripts/harness/installation/application/usecases/run-install.ts +70 -39
- package/scripts/harness/installation/application/usecases/run-uninstall.ts +1 -0
- package/skills/README.md +1 -1
- package/skills/phasegate-config-doctor/SKILL.md +1 -1
- package/skills/phasegate-toolkit-guide/SKILL.md +1 -1
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.
|
|
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
|
|
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.
|
|
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
|
|
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`, `.
|
|
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.
|
|
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
|
|
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,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
|
|
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
|
|
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
|
|
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
|
|
334
|
-
const
|
|
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:
|
|
344
|
-
|
|
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
|
|
382
|
-
if (
|
|
383
|
-
manifest = manifest.addEntry(
|
|
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
|
-
|
|
398
|
-
|
|
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,
|
|
416
|
+
await copyDirectory(join(input.harnessRoot, "skills"), join(input.projectRoot, skillPath));
|
|
403
417
|
await writeFile(
|
|
404
|
-
join(input.projectRoot,
|
|
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/
|
|
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/
|
|
464
|
-
diff: "manual
|
|
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: ".
|
|
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
|
|
643
|
-
|
|
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
|
|
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:
|
|
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:
|
|
654
|
-
|
|
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` はチーム所有リポジトリでの個人評価用です。このモードでは
|
|
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
|
|
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
|
|
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
|
|