phasegate 0.145.4 → 0.147.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.ja.md +13 -6
- package/README.md +13 -6
- package/docs/guide/cli-reference.md +6 -2
- package/docs/guide/installation.md +8 -5
- package/package.json +1 -1
- package/scripts/harness/biome-ast-engine/infrastructure/adapters/typescript-source-module-analyzer-adapter.ts +1 -0
- package/scripts/harness/harness-api/domain/services/command-dispatch-service.ts +1 -0
- package/scripts/harness/installation/application/checks/wi-workflow-drift-check.ts +64 -0
- package/scripts/harness/installation/composition-root.ts +2 -0
- package/scripts/harness/installation/domain/check-id.ts +1 -0
- package/scripts/harness/installation/infrastructure/adapters/node-fs-file-inspector-adapter.ts +10 -1
- package/scripts/harness/integrations/pre-commit.ts +1 -0
- package/scripts/harness/main.ts +162 -4
- package/scripts/harness/setup/skill-deployer.ts +8 -1
- package/scripts/harness/validator-system/application/use-cases/run-l4-validators-usecase.ts +1 -0
- package/skills/implementation-planner/SKILL.md +8 -2
- package/skills/logical-designer/SKILL.md +13 -8
- package/skills/story-writer/SKILL.md +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.147.0] - 2026-05-12
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **WI-144 — install/uninstall idempotency umbrella closure** — publish-ready rollup for the installation lifecycle work delivered across WI-145〜WI-148.
|
|
15
|
+
- Documents the complete lifecycle commands (`install`, `doctor`, `uninstall`, `reconcile`) in README / README.ja / guide docs.
|
|
16
|
+
- Marks the WI-144 umbrella as tested after the manifest, doctor, structured install, clean uninstall, reconcile, and init deprecation slices reached tested status.
|
|
17
|
+
|
|
10
18
|
## [0.145.4] - 2026-05-11
|
|
11
19
|
|
|
12
20
|
### Added
|
package/README.ja.md
CHANGED
|
@@ -18,7 +18,7 @@ Claude Code / Codex / Cursor / Copilot — どの AI agent でも設計意図・
|
|
|
18
18
|
2. **コミット前に L1〜L3 のバリデーションが自動で走り**、レイヤー違反・テスト品質違反・依存方向違反を弾く
|
|
19
19
|
3. **ブロック時のエラーは AI が読んで自己修正できる形式**(理由・必要な設計文書・次に打つべきスキル名が出る)
|
|
20
20
|
|
|
21
|
-
`npx phasegate init`
|
|
21
|
+
既存プロジェクトには `npx phasegate install` で既存 hooks / scripts を壊さずに組み込み、新規プロジェクトの legacy bootstrap には `npx phasegate init` を使います。導入後は `phasegate doctor` / `uninstall` / `reconcile` で状態確認・削除・アップグレード追従ができます。
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
@@ -77,7 +77,7 @@ claude
|
|
|
77
77
|
> /product-architect
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
`init`
|
|
80
|
+
`init` は初期 bootstrap として以下を生成します:
|
|
81
81
|
|
|
82
82
|
- `phasegate.config.json` — 品質設定の Single Source of Truth
|
|
83
83
|
- `skills/` — 28 の AIDLC スキル一式
|
|
@@ -95,7 +95,7 @@ claude
|
|
|
95
95
|
- `docs/product/` 配下の確定設計文書 — `/domain-designer` `/logical-designer` 等が生成
|
|
96
96
|
- `docs/ADR/` — `/skill-creator` や手動で必要に応じて作成
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
`init` は legacy 互換の bootstrap 経路です。既存 hooks / scripts / package metadata に構造化 merge したい場合は `install` を使います。「設計してから書け」を強制する仕組みなので、設計文書はユーザーがスキル経由で作るのが既定動作です。
|
|
99
99
|
|
|
100
100
|
既存プロジェクトに導入する場合は、構造化 install で差分を確認してから適用します。
|
|
101
101
|
|
|
@@ -138,9 +138,12 @@ codex features enable codex_hooks # Codex 本体の feature flag を手動で
|
|
|
138
138
|
|
|
139
139
|
```bash
|
|
140
140
|
npm update phasegate
|
|
141
|
-
npx phasegate
|
|
141
|
+
npx phasegate reconcile --dry-run
|
|
142
|
+
npx phasegate reconcile --apply
|
|
142
143
|
```
|
|
143
144
|
|
|
145
|
+
`update-skills` は互換 alias として残っていますが、推奨は `reconcile` です。`.phasegate/manifest.json` に記録された PhaseGate 管理ファイル全体を最新版 template に追従できます。
|
|
146
|
+
|
|
144
147
|
---
|
|
145
148
|
|
|
146
149
|
## 主な機能
|
|
@@ -312,8 +315,12 @@ npx phasegate <command> [options]
|
|
|
312
315
|
|
|
313
316
|
| コマンド | 説明 |
|
|
314
317
|
|---|---|
|
|
315
|
-
| `init --name <name>` |
|
|
316
|
-
| `
|
|
318
|
+
| `init --name <name>` | 新規プロジェクト向け legacy bootstrap(skills/config/hooks 配置)。既存 hooks/scripts/CI がある場合は `install` を推奨 |
|
|
319
|
+
| `install --dry-run` / `--apply` | 既存設定を保持しながら PhaseGate を構造化 merge し、`.phasegate/manifest.json` を作成 |
|
|
320
|
+
| `doctor` | silent / partial installation を診断し、修復 hint を表示(`--json`, `--strict`, `--report-out <path>`) |
|
|
321
|
+
| `uninstall --dry-run` / `--apply` | manifest に基づいて PhaseGate 管理ファイル・管理 block を削除し、ユーザー設定は保持 |
|
|
322
|
+
| `reconcile --dry-run` / `--apply` | 現在の package template に PhaseGate 管理ファイルを追従し、manifest hash を更新 |
|
|
323
|
+
| `update-skills` | `reconcile` の互換 alias |
|
|
317
324
|
| `lint` | L1 Biome AST チェック |
|
|
318
325
|
| `validate --layer <L1\|L2\|L3\|L4\|all>` | 指定レイヤーのバリデータ実行(`--format human\|agent\|ci`) |
|
|
319
326
|
| `ci-check` | CI フルチェック(L2-L4)。`--quick` で Quick Mode |
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ Phasegate adds project-local hooks, validators, and agent skills that keep gener
|
|
|
19
19
|
2. **Before commit and CI, validators check layer boundaries, metadata, test quality, security, performance, and traceability.**
|
|
20
20
|
3. **Every failure is returned in an agent-readable format** with the reason, missing artifacts, references, and the next skill or command to run.
|
|
21
21
|
|
|
22
|
-
Run `npx phasegate
|
|
22
|
+
Run `npx phasegate install` to merge those guardrails into an existing project, or `npx phasegate init` for the legacy bootstrap path on a new project. `phasegate doctor`, `uninstall`, and `reconcile` keep the installation observable, removable, and upgradeable.
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
@@ -78,7 +78,7 @@ claude
|
|
|
78
78
|
> /product-architect
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
`init` creates:
|
|
81
|
+
`init` creates the initial project-local harness files:
|
|
82
82
|
|
|
83
83
|
- `phasegate.config.json` as the quality settings source of truth
|
|
84
84
|
- `skills/` with 28 AIDLC skills
|
|
@@ -88,7 +88,7 @@ claude
|
|
|
88
88
|
- `.husky/pre-commit`, `.husky/commit-msg`, and `.husky/pre-push` when `--with-husky` is passed
|
|
89
89
|
- `.github/workflows/aidlc-gate.yml`, `.github/workflows/consistency-check.yml`, and `.github/workflows/agent-context-refresh.yml` when `--with-ci` is passed
|
|
90
90
|
|
|
91
|
-
`init` intentionally does **not** create `docs/inception/` work item directories or `docs/product/` design documents. Those are produced later by skills such as `/product-architect`, `/domain-designer`, and `/logical-designer`. That is the core contract: no design, no code.
|
|
91
|
+
`init` is the legacy-compatible bootstrap path. For idempotent setup with structured merge into existing hooks, scripts, and package metadata, use `install`. `init` intentionally does **not** create `docs/inception/` work item directories or `docs/product/` design documents. Those are produced later by skills such as `/product-architect`, `/domain-designer`, and `/logical-designer`. That is the core contract: no design, no code.
|
|
92
92
|
|
|
93
93
|
For an existing project, preview and apply a structured install instead:
|
|
94
94
|
|
|
@@ -131,9 +131,12 @@ Use `--agent both` for projects that use Claude Code and Codex together. Codex n
|
|
|
131
131
|
|
|
132
132
|
```bash
|
|
133
133
|
npm update phasegate
|
|
134
|
-
npx phasegate
|
|
134
|
+
npx phasegate reconcile --dry-run
|
|
135
|
+
npx phasegate reconcile --apply
|
|
135
136
|
```
|
|
136
137
|
|
|
138
|
+
`update-skills` remains available as a compatibility alias, but `reconcile` is the preferred upgrade path because it updates all PhaseGate-managed files recorded in `.phasegate/manifest.json`, not just skills.
|
|
139
|
+
|
|
137
140
|
---
|
|
138
141
|
|
|
139
142
|
## Core Capabilities
|
|
@@ -494,7 +497,11 @@ npx phasegate <command> [options]
|
|
|
494
497
|
|
|
495
498
|
| Command | Description |
|
|
496
499
|
|---|---|
|
|
497
|
-
| `init --name <name>` |
|
|
500
|
+
| `init --name <name>` | Legacy-compatible bootstrap for new projects: deploy skills, generate config, and optionally add hooks/CI. Prefer `install` when the project may already have hooks, scripts, or CI files. |
|
|
501
|
+
| `install --dry-run` / `--apply` | Idempotently merge PhaseGate into the current project, preserve existing user content, add package scripts/devDependency, and write `.phasegate/manifest.json`. |
|
|
502
|
+
| `doctor` | Diagnose silent or partial installations and report repair hints (`--json`, `--strict`, `--report-out <path>`). |
|
|
503
|
+
| `uninstall --dry-run` / `--apply` | Remove PhaseGate-managed files and managed blocks using `.phasegate/manifest.json`, preserving user content. |
|
|
504
|
+
| `reconcile --dry-run` / `--apply` | Update PhaseGate-managed files to the current package templates and refresh manifest hashes. |
|
|
498
505
|
| `lint` | Run L1 Biome AST checks |
|
|
499
506
|
| `validate --layer <L1-L4\|all>` | Run validators for specified layer (`--layer L0` prints runtime hook guidance; explicit L4 runs even when scheduled L4 is disabled) |
|
|
500
507
|
| `ci-check` | Full CI check (L2-L4; disabled L4 is reported as skipped) |
|
|
@@ -502,7 +509,7 @@ npx phasegate <command> [options]
|
|
|
502
509
|
| `ci:auto-refresh-agent-context --dry-run` / `--apply` | Refresh AGENTS.md pointers and CLAUDE.md standard sections |
|
|
503
510
|
| `refresh-claude-md --dry-run` / `--apply` | Refresh only CLAUDE.md while preserving the user-owned section |
|
|
504
511
|
| `p2:check-agent-context` | Check AGENTS.md / CLAUDE.md freshness |
|
|
505
|
-
| `update-skills` |
|
|
512
|
+
| `update-skills` | Compatibility alias for `reconcile` |
|
|
506
513
|
| `phasegate:status` | Display overall harness health summary |
|
|
507
514
|
| `work-items:status --dry-run` / `--apply` | Derive WI status from artifacts and optionally update stale `description.md` frontmatter. Apply refuses downgrades unless `--allow-downgrade` is supplied. |
|
|
508
515
|
| `phasegate:check-phase --unit <id>` | Check current phase for a Unit |
|
|
@@ -12,8 +12,12 @@ npx phasegate <command> [options]
|
|
|
12
12
|
|
|
13
13
|
| Command | Description |
|
|
14
14
|
|---|---|
|
|
15
|
-
| `init --name <name>` |
|
|
16
|
-
| `
|
|
15
|
+
| `init --name <name>` | Legacy-compatible bootstrap for new projects: deploy skills, generate config, and optionally add hooks/CI |
|
|
16
|
+
| `install --dry-run` / `--apply` | Idempotently merge PhaseGate into an existing project, preserve user content, add package scripts/devDependency, and write `.phasegate/manifest.json` |
|
|
17
|
+
| `doctor` | Diagnose silent or partial installations and report repair hints (`--json`, `--strict`, `--report-out <path>`) |
|
|
18
|
+
| `uninstall --dry-run` / `--apply` | Remove PhaseGate-managed files and managed blocks using `.phasegate/manifest.json` |
|
|
19
|
+
| `reconcile --dry-run` / `--apply` | Update PhaseGate-managed files to current package templates and refresh manifest hashes |
|
|
20
|
+
| `update-skills` | Compatibility alias for `reconcile` |
|
|
17
21
|
| `list-features` | List available features |
|
|
18
22
|
| `enable-feature <name>` | Enable a feature |
|
|
19
23
|
| `disable-feature <name>` | Disable a feature |
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
- **npm** 9+ or **pnpm**
|
|
7
7
|
- **TypeScript** 5.x (included as a devDependency)
|
|
8
8
|
|
|
9
|
-
## Install from
|
|
9
|
+
## Install from npm
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
npm install --save-dev phasegate
|
|
@@ -17,7 +17,7 @@ Or add it directly to your `package.json`:
|
|
|
17
17
|
```json
|
|
18
18
|
{
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"phasegate": "^0.
|
|
20
|
+
"phasegate": "^0.147.0"
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
```
|
|
@@ -38,6 +38,8 @@ npx phasegate init --name <project-name>
|
|
|
38
38
|
|
|
39
39
|
This deploys 28 skills to `skills/`, creates the agent-facing skill links (for example `.claude/skills/` or `.codex/skills/`), and generates `phasegate.config.json`.
|
|
40
40
|
|
|
41
|
+
`init` is the legacy-compatible bootstrap path for new projects. Use `install` when the project may already have hooks, package scripts, or CI files that should be preserved.
|
|
42
|
+
|
|
41
43
|
For Codex, project initialization stops at the project boundary. After `npx phasegate init --agent codex`, enable the Codex CLI feature flag manually:
|
|
42
44
|
|
|
43
45
|
```bash
|
|
@@ -82,8 +84,6 @@ npx phasegate reconcile --apply
|
|
|
82
84
|
|
|
83
85
|
`reconcile` updates PhaseGate-managed portions, preserves user content, adds newly introduced managed targets, and refreshes `.phasegate/manifest.json` with current version/hash metadata. If a managed file was edited after install, `reconcile --apply` refuses that entry until you rerun with `--force`, which creates a backup under `.phasegate/backups/reconcile-<timestamp>/`.
|
|
84
86
|
|
|
85
|
-
`phasegate update-skills` remains available as a compatibility alias for `phasegate reconcile`.
|
|
86
|
-
|
|
87
87
|
### Manual Setup Pieces
|
|
88
88
|
|
|
89
89
|
If you do not use `init` or `install`, copy the design principle documents manually:
|
|
@@ -110,9 +110,12 @@ Run `/product-architect` to begin the AIDLC process.
|
|
|
110
110
|
|
|
111
111
|
```bash
|
|
112
112
|
npm update phasegate
|
|
113
|
-
npx phasegate
|
|
113
|
+
npx phasegate reconcile --dry-run
|
|
114
|
+
npx phasegate reconcile --apply
|
|
114
115
|
```
|
|
115
116
|
|
|
117
|
+
`phasegate update-skills` remains available as a compatibility alias, but `reconcile` is the preferred upgrade path because it updates all managed files recorded in `.phasegate/manifest.json`.
|
|
118
|
+
|
|
116
119
|
## Recommended .gitignore additions
|
|
117
120
|
|
|
118
121
|
```
|
package/package.json
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// @unit installation
|
|
2
|
+
// @layer application
|
|
3
|
+
// @work-item-id WI-143
|
|
4
|
+
|
|
5
|
+
import { join, relative, sep } from "node:path";
|
|
6
|
+
import { DiagnosticFinding } from "../../domain/diagnostic-finding.js";
|
|
7
|
+
import type { HeuristicCheck } from "../../domain/ports/heuristic-check.js";
|
|
8
|
+
import type { FileInspectorPort } from "../ports/file-inspector-port.js";
|
|
9
|
+
|
|
10
|
+
interface PhasegateConfigProbe {
|
|
11
|
+
readonly quickMode?: {
|
|
12
|
+
readonly relaxedGates?: readonly string[];
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class WiWorkflowDriftCheck implements HeuristicCheck {
|
|
17
|
+
readonly checkId = "wi-workflow-drift" as const;
|
|
18
|
+
|
|
19
|
+
async run(projectRoot: string, inspector: FileInspectorPort): Promise<DiagnosticFinding | null> {
|
|
20
|
+
const inceptionRoot = join(projectRoot, "docs", "inception");
|
|
21
|
+
const files = await inspector.listFiles(inceptionRoot);
|
|
22
|
+
const relativeFiles = files.map((file) => toPosix(relative(projectRoot, file)));
|
|
23
|
+
const workItemCount = relativeFiles.filter(isWorkItemDescription).length;
|
|
24
|
+
const adHocPlans = relativeFiles.filter(isAdHocPlan);
|
|
25
|
+
const hasPhaseGateRelaxed = await this.hasRelaxedPhaseGate(projectRoot, inspector);
|
|
26
|
+
|
|
27
|
+
if (workItemCount > 0 || adHocPlans.length === 0) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const message = hasPhaseGateRelaxed
|
|
32
|
+
? `WI-first drift detected: 0 WI directories, ${adHocPlans.length} ad-hoc plan file(s), and quickMode.relaxedGates includes phase-gate.`
|
|
33
|
+
: `WI-first drift detected: 0 WI directories and ${adHocPlans.length} ad-hoc plan file(s).`;
|
|
34
|
+
|
|
35
|
+
return DiagnosticFinding.create({
|
|
36
|
+
checkId: this.checkId,
|
|
37
|
+
severity: "red",
|
|
38
|
+
target: "docs/inception",
|
|
39
|
+
message,
|
|
40
|
+
repairMode: "mechanical",
|
|
41
|
+
repairHint: "phasegate migrate work-items --apply",
|
|
42
|
+
suggestedSkill: null,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private async hasRelaxedPhaseGate(projectRoot: string, inspector: FileInspectorPort): Promise<boolean> {
|
|
47
|
+
const config = await inspector.readJson<PhasegateConfigProbe>(join(projectRoot, "phasegate.config.json"));
|
|
48
|
+
return config?.quickMode?.relaxedGates?.includes("phase-gate") ?? false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function toPosix(path: string): string {
|
|
53
|
+
return path.split(sep).join("/");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isWorkItemDescription(path: string): boolean {
|
|
57
|
+
return /^docs\/inception\/(?:_cross|[^/]+)\/WI-\d{3}\/description\.md$/.test(path);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isAdHocPlan(path: string): boolean {
|
|
61
|
+
if (!path.startsWith("docs/inception/")) return false;
|
|
62
|
+
if (/\/WI-\d{3}\//.test(path)) return false;
|
|
63
|
+
return path.includes("/codding_plan/") || path.endsWith("_plan.md");
|
|
64
|
+
}
|
|
@@ -12,6 +12,7 @@ import { HuskyCommitMsgMissingCheck } from "./application/checks/husky-commit-ms
|
|
|
12
12
|
import { HuskyPreCommitMissingCheck } from "./application/checks/husky-pre-commit-missing-check.js";
|
|
13
13
|
import { HuskyPrePushMissingCheck } from "./application/checks/husky-pre-push-missing-check.js";
|
|
14
14
|
import { PackageJsonDevdepMissingCheck } from "./application/checks/package-json-devdep-missing-check.js";
|
|
15
|
+
import { WiWorkflowDriftCheck } from "./application/checks/wi-workflow-drift-check.js";
|
|
15
16
|
import { RunInstallUseCase } from "./application/usecases/run-install.js";
|
|
16
17
|
import { RunReconcileUseCase } from "./application/usecases/run-reconcile.js";
|
|
17
18
|
import { RunUninstallUseCase } from "./application/usecases/run-uninstall.js";
|
|
@@ -49,6 +50,7 @@ export function createInstallationModule() {
|
|
|
49
50
|
new PackageJsonDevdepMissingCheck(),
|
|
50
51
|
new ClaudeSkillsSymlinkCheck(),
|
|
51
52
|
new CodexSkillsSymlinkCheck(),
|
|
53
|
+
new WiWorkflowDriftCheck(),
|
|
52
54
|
];
|
|
53
55
|
const runDoctorDiagnosticsUseCase = new RunDoctorDiagnosticsUseCase(checks, inspector, manifestRepository);
|
|
54
56
|
const runInstallUseCase = new RunInstallUseCase(manifestRepository, hashCalculator);
|
package/scripts/harness/installation/infrastructure/adapters/node-fs-file-inspector-adapter.ts
CHANGED
|
@@ -45,7 +45,16 @@ export class NodeFsFileInspectorAdapter implements FileInspectorPort {
|
|
|
45
45
|
async listFiles(absolutePath: string): Promise<string[]> {
|
|
46
46
|
try {
|
|
47
47
|
const entries = await readdir(absolutePath, { withFileTypes: true });
|
|
48
|
-
|
|
48
|
+
const files: string[] = [];
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
const entryPath = join(absolutePath, entry.name);
|
|
51
|
+
if (entry.isFile()) {
|
|
52
|
+
files.push(entryPath);
|
|
53
|
+
} else if (entry.isDirectory()) {
|
|
54
|
+
files.push(...(await this.listFiles(entryPath)));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return files;
|
|
49
58
|
} catch {
|
|
50
59
|
return [];
|
|
51
60
|
}
|
package/scripts/harness/main.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @unit harness-api
|
|
3
3
|
* @layer presentation
|
|
4
|
+
* @work-item-id WI-142
|
|
4
5
|
*
|
|
5
6
|
* Phasegate CLI エントリポイント。
|
|
6
7
|
* 各Unitの Composition Root からハンドラーを取得し、コマンドに応じてディスパッチする。
|
|
@@ -8,7 +9,13 @@
|
|
|
8
9
|
* 起動時に config-foundation で設定を解決し、他Unit に注入する(Cross-unit wiring)。
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
mkdir as fsMkdir,
|
|
14
|
+
readFile as fsReadFile,
|
|
15
|
+
readdir as fsReaddir,
|
|
16
|
+
readlink as fsReadlink,
|
|
17
|
+
writeFile as fsWriteFile,
|
|
18
|
+
} from "node:fs/promises";
|
|
12
19
|
import { dirname, join, resolve } from "node:path";
|
|
13
20
|
import { createAdrFoundationModule } from "./adr-foundation/composition-root.js";
|
|
14
21
|
import { createBiomeAstEngineModule } from "./biome-ast-engine/composition-root.js";
|
|
@@ -138,9 +145,12 @@ Usage: phasegate <command> [options]
|
|
|
138
145
|
Setup:
|
|
139
146
|
init Initialize project: deploy skills + design docs + phasegate.config.json
|
|
140
147
|
(--name <project-name>, --preset <full|standard|minimal|custom>,
|
|
141
|
-
--skills <core|all>, --agent <claude|codex|both>, --
|
|
148
|
+
--skills <core|all>, --agent <claude|codex|both>, --workflow <standard|strict>,
|
|
149
|
+
--with-husky, --with-ci, --yes)
|
|
142
150
|
update-skills Alias for reconcile (kept for compatibility)
|
|
143
151
|
doctor Diagnose silent installation failures (--json, --strict, --report-out <path>)
|
|
152
|
+
scaffold-wi <unit> <type> Create docs/inception/{unit}/WI-XXX/description.md
|
|
153
|
+
emit-agent-rules Print AGENTS.md / CLAUDE.md WI workflow rules block
|
|
144
154
|
install Install phasegate managed files (--dry-run|--apply, --force)
|
|
145
155
|
uninstall Uninstall phasegate managed files (--dry-run|--apply, --force)
|
|
146
156
|
reconcile Reconcile phasegate managed files (--dry-run|--apply, --force)
|
|
@@ -232,6 +242,103 @@ function hasFlag(args: readonly string[], flag: string): boolean {
|
|
|
232
242
|
return args.includes(flag);
|
|
233
243
|
}
|
|
234
244
|
|
|
245
|
+
type WorkflowMode = "standard" | "strict";
|
|
246
|
+
type ScaffoldWorkItemType = "story" | "issue" | "chore";
|
|
247
|
+
|
|
248
|
+
function parseWorkflowMode(value: string | undefined): WorkflowMode {
|
|
249
|
+
return value === "strict" ? "strict" : "standard";
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function parseScaffoldWorkItemType(value: string | undefined): ScaffoldWorkItemType | null {
|
|
253
|
+
if (value === "story" || value === "issue" || value === "chore") return value;
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function emitAgentRulesBlock(): string {
|
|
258
|
+
return [
|
|
259
|
+
"## PhaseGate WI Workflow (auto-generated; do not edit by hand)",
|
|
260
|
+
"- All plans/designs/implementations require a WI directory first.",
|
|
261
|
+
"- Path: `docs/inception/{unit}/WI-XXX/description.md` with required frontmatter.",
|
|
262
|
+
"- Use `phasegate scaffold-wi <unit> <type>` to create one.",
|
|
263
|
+
"- Plans written under `docs/inception/codding_plan/` are legacy; new plans go in WI dirs.",
|
|
264
|
+
].join("\n");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function listFilesRecursive(root: string): Promise<string[]> {
|
|
268
|
+
try {
|
|
269
|
+
const entries = await fsReaddir(root, { withFileTypes: true });
|
|
270
|
+
const files: string[] = [];
|
|
271
|
+
for (const entry of entries) {
|
|
272
|
+
const path = join(root, entry.name);
|
|
273
|
+
if (entry.isFile()) {
|
|
274
|
+
files.push(path);
|
|
275
|
+
} else if (entry.isDirectory()) {
|
|
276
|
+
files.push(...(await listFilesRecursive(path)));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return files;
|
|
280
|
+
} catch {
|
|
281
|
+
return [];
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function nextWorkItemId(rootDir: string): Promise<string> {
|
|
286
|
+
const files = await listFilesRecursive(join(rootDir, "docs", "inception"));
|
|
287
|
+
let max = 0;
|
|
288
|
+
for (const file of files) {
|
|
289
|
+
const match = file.match(/\/WI-(\d{3})\/description\.md$/);
|
|
290
|
+
if (match) max = Math.max(max, Number(match[1]));
|
|
291
|
+
}
|
|
292
|
+
return `WI-${String(max + 1).padStart(3, "0")}`;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function countLegacyPlansWithoutWorkItems(rootDir: string): Promise<number> {
|
|
296
|
+
const files = await listFilesRecursive(join(rootDir, "docs", "inception"));
|
|
297
|
+
const hasWorkItem = files.some((file) => /\/WI-\d{3}\/description\.md$/.test(file));
|
|
298
|
+
if (hasWorkItem) return 0;
|
|
299
|
+
return files.filter((file) => file.includes("/codding_plan/") || file.endsWith("_plan.md")).length;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function scaffoldInceptionRoots(rootDir: string, unit: string | null = null): Promise<void> {
|
|
303
|
+
await fsMkdir(join(rootDir, "docs", "inception", "_shared"), { recursive: true });
|
|
304
|
+
await fsMkdir(join(rootDir, "docs", "inception", "_cross"), { recursive: true });
|
|
305
|
+
if (unit && unit !== "_cross" && unit !== "_shared") {
|
|
306
|
+
await fsMkdir(join(rootDir, "docs", "inception", unit), { recursive: true });
|
|
307
|
+
await fsWriteFile(join(rootDir, "docs", "inception", unit, ".gitkeep"), "", "utf8").catch(() => undefined);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async function scaffoldWorkItem(rootDir: string, unit: string, type: ScaffoldWorkItemType): Promise<string> {
|
|
312
|
+
const id = await nextWorkItemId(rootDir);
|
|
313
|
+
await scaffoldInceptionRoots(rootDir, unit);
|
|
314
|
+
const targetBase = unit === "_cross" ? join(rootDir, "docs", "inception", "_cross") : join(rootDir, "docs", "inception", unit);
|
|
315
|
+
const targetDir = join(targetBase, id);
|
|
316
|
+
await fsMkdir(targetDir, { recursive: true });
|
|
317
|
+
const descriptionPath = join(targetDir, "description.md");
|
|
318
|
+
const titleScope = unit === "_cross" ? "Cross-cutting" : unit;
|
|
319
|
+
const content = [
|
|
320
|
+
"---",
|
|
321
|
+
`id: ${id}`,
|
|
322
|
+
`type: ${type}`,
|
|
323
|
+
"severity: normal",
|
|
324
|
+
"status: drafted",
|
|
325
|
+
"---",
|
|
326
|
+
"",
|
|
327
|
+
`# ${id}: ${titleScope} work item`,
|
|
328
|
+
"",
|
|
329
|
+
"## Context",
|
|
330
|
+
"",
|
|
331
|
+
"TBD",
|
|
332
|
+
"",
|
|
333
|
+
"## Acceptance Criteria",
|
|
334
|
+
"",
|
|
335
|
+
"- [ ] TBD",
|
|
336
|
+
"",
|
|
337
|
+
].join("\n");
|
|
338
|
+
await fsWriteFile(descriptionPath, content, "utf8");
|
|
339
|
+
return descriptionPath;
|
|
340
|
+
}
|
|
341
|
+
|
|
235
342
|
async function createFileManifestRecord(
|
|
236
343
|
rootDir: string,
|
|
237
344
|
relativePath: string,
|
|
@@ -349,6 +456,7 @@ Options:
|
|
|
349
456
|
--preset <full|standard|minimal|custom> Phase dependency preset (default: "standard")
|
|
350
457
|
--skills <core|all> Skill set to deploy (default: "all")
|
|
351
458
|
--agent <claude|codex|both> Agent integration target (default: "claude")
|
|
459
|
+
--workflow <standard|strict> Workflow enforcement defaults (default: "standard")
|
|
352
460
|
--with-husky Install Husky pre-commit hooks
|
|
353
461
|
--with-ci Install GitHub Actions workflows
|
|
354
462
|
--yes Skip confirmation prompts
|
|
@@ -777,7 +885,16 @@ async function main(): Promise<void> {
|
|
|
777
885
|
console.log("Warning: phasegate init is deprecated and will be removed in v1.0.");
|
|
778
886
|
console.log("Use phasegate install for idempotent setup with structured merge.");
|
|
779
887
|
console.log("Existing legacy init behavior is preserved. Run phasegate doctor to verify installation state.");
|
|
780
|
-
const KNOWN_INIT_FLAGS = [
|
|
888
|
+
const KNOWN_INIT_FLAGS = [
|
|
889
|
+
"--name",
|
|
890
|
+
"--preset",
|
|
891
|
+
"--skills",
|
|
892
|
+
"--agent",
|
|
893
|
+
"--workflow",
|
|
894
|
+
"--with-husky",
|
|
895
|
+
"--with-ci",
|
|
896
|
+
"--yes",
|
|
897
|
+
];
|
|
781
898
|
const flagError = validateKnownFlags(args, KNOWN_INIT_FLAGS);
|
|
782
899
|
if (flagError) {
|
|
783
900
|
console.error(flagError);
|
|
@@ -808,6 +925,12 @@ async function main(): Promise<void> {
|
|
|
808
925
|
process.exit(2);
|
|
809
926
|
}
|
|
810
927
|
const agent = agentRaw;
|
|
928
|
+
const workflowRaw = parseFlag(args, "--workflow");
|
|
929
|
+
if (workflowRaw !== undefined && workflowRaw !== "standard" && workflowRaw !== "strict") {
|
|
930
|
+
console.error(`Invalid --workflow value: "${workflowRaw}". Use "standard" or "strict".`);
|
|
931
|
+
process.exit(2);
|
|
932
|
+
}
|
|
933
|
+
const workflow = parseWorkflowMode(workflowRaw);
|
|
811
934
|
const deployClaude = agent === "claude" || agent === "both";
|
|
812
935
|
const deployCodex = agent === "codex" || agent === "both";
|
|
813
936
|
const result = await deploySkills(harnessRoot, rootDir, skillSet);
|
|
@@ -817,7 +940,13 @@ async function main(): Promise<void> {
|
|
|
817
940
|
codex: deployCodex,
|
|
818
941
|
});
|
|
819
942
|
const withCi = hasFlag(args, "--with-ci");
|
|
820
|
-
const configResult = await initHarnessConfig(rootDir, projectName, phasePreset, {
|
|
943
|
+
const configResult = await initHarnessConfig(rootDir, projectName, phasePreset, {
|
|
944
|
+
ciEnabled: withCi,
|
|
945
|
+
workflow,
|
|
946
|
+
});
|
|
947
|
+
if (workflow === "strict") {
|
|
948
|
+
await scaffoldInceptionRoots(rootDir);
|
|
949
|
+
}
|
|
821
950
|
const hooksResult = deployClaude
|
|
822
951
|
? await deployHookScripts(harnessRoot, rootDir)
|
|
823
952
|
: {
|
|
@@ -891,6 +1020,9 @@ async function main(): Promise<void> {
|
|
|
891
1020
|
}
|
|
892
1021
|
if (configResult.created) {
|
|
893
1022
|
console.log(`✓ phasegate.config.json created`);
|
|
1023
|
+
if (workflow === "strict") {
|
|
1024
|
+
console.log(`✓ strict workflow configured (quickMode.relaxedGates: [], allowedCategories: ["chore"])`);
|
|
1025
|
+
}
|
|
894
1026
|
} else {
|
|
895
1027
|
console.log(` phasegate.config.json already exists, skipped`);
|
|
896
1028
|
}
|
|
@@ -966,6 +1098,13 @@ async function main(): Promise<void> {
|
|
|
966
1098
|
}
|
|
967
1099
|
}
|
|
968
1100
|
console.log(`✓ Harness v${result.version} initialized (agent: ${agent})`);
|
|
1101
|
+
const legacyPlanCount = await countLegacyPlansWithoutWorkItems(rootDir);
|
|
1102
|
+
if (legacyPlanCount > 0) {
|
|
1103
|
+
console.log("");
|
|
1104
|
+
console.log(`Detected ${legacyPlanCount} legacy plan file(s) with no WI directories.`);
|
|
1105
|
+
console.log("Run migration? [Y/n]");
|
|
1106
|
+
console.log(" phasegate migrate work-items --apply");
|
|
1107
|
+
}
|
|
969
1108
|
console.log("");
|
|
970
1109
|
console.log("Next steps:");
|
|
971
1110
|
if (skillSet === "core") {
|
|
@@ -1035,6 +1174,25 @@ async function main(): Promise<void> {
|
|
|
1035
1174
|
break;
|
|
1036
1175
|
}
|
|
1037
1176
|
|
|
1177
|
+
case "emit-agent-rules": {
|
|
1178
|
+
console.log(emitAgentRulesBlock());
|
|
1179
|
+
process.exit(0);
|
|
1180
|
+
break;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
case "scaffold-wi": {
|
|
1184
|
+
const unit = args[1];
|
|
1185
|
+
const type = parseScaffoldWorkItemType(args[2]);
|
|
1186
|
+
if (!unit || !type) {
|
|
1187
|
+
console.error("Usage: phasegate scaffold-wi <unit|_cross> <story|issue|chore>");
|
|
1188
|
+
process.exit(2);
|
|
1189
|
+
}
|
|
1190
|
+
const descriptionPath = await scaffoldWorkItem(rootDir, unit, type);
|
|
1191
|
+
console.log(`Created ${descriptionPath}`);
|
|
1192
|
+
process.exit(0);
|
|
1193
|
+
break;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1038
1196
|
case "install": {
|
|
1039
1197
|
const KNOWN_INSTALL_FLAGS = ["--dry-run", "--apply", "--force", "--json"];
|
|
1040
1198
|
const flagError = validateKnownFlags(args, KNOWN_INSTALL_FLAGS);
|
|
@@ -422,6 +422,7 @@ export async function deployHookScripts(harnessRoot: string, projectRoot: string
|
|
|
422
422
|
|
|
423
423
|
export interface InitHarnessConfigOptions {
|
|
424
424
|
ciEnabled?: boolean;
|
|
425
|
+
workflow?: "standard" | "strict";
|
|
425
426
|
}
|
|
426
427
|
|
|
427
428
|
export async function initHarnessConfig(
|
|
@@ -438,6 +439,7 @@ export async function initHarnessConfig(
|
|
|
438
439
|
// ファイルが存在しない場合はテンプレートを作成
|
|
439
440
|
}
|
|
440
441
|
|
|
442
|
+
const strictWorkflow = options.workflow === "strict";
|
|
441
443
|
const template = {
|
|
442
444
|
project: {
|
|
443
445
|
name: projectName,
|
|
@@ -447,7 +449,12 @@ export async function initHarnessConfig(
|
|
|
447
449
|
preset: "clean",
|
|
448
450
|
},
|
|
449
451
|
layers: {},
|
|
450
|
-
quickMode:
|
|
452
|
+
quickMode: strictWorkflow
|
|
453
|
+
? {
|
|
454
|
+
allowedCategories: ["chore"],
|
|
455
|
+
relaxedGates: [],
|
|
456
|
+
}
|
|
457
|
+
: {},
|
|
451
458
|
phaseDependencies: {
|
|
452
459
|
preset: phasePreset ?? "default",
|
|
453
460
|
override: false,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: implementation-planner
|
|
3
|
-
description: "Unit
|
|
3
|
+
description: "Unit仕様とドメインモデル設計を元に実装計画を立てる。WI IDや機能名から関連Unitを特定し、API設計・レイヤー別実装方針を整理してmdファイルで出力する。使用タイミング: 実装計画を立てて、WI-XXXの実装方針を決めて、この機能の設計を整理して、など実装前の計画策定時。"
|
|
4
4
|
model: sonnet
|
|
5
5
|
review: opus
|
|
6
6
|
---
|
|
@@ -35,6 +35,11 @@ UnitドキュメントとConstructionのドメインモデル設計を元に、*
|
|
|
35
35
|
|
|
36
36
|
## ワークフロー
|
|
37
37
|
|
|
38
|
+
## Pre-flight check (BLOCKING)
|
|
39
|
+
|
|
40
|
+
Before generating any plan, verify `docs/inception/{unit}/WI-XXX/description.md` exists.
|
|
41
|
+
If not, halt and ask the user to create the WI first, or offer to run `phasegate scaffold-wi <unit> <story|issue|chore>`.
|
|
42
|
+
|
|
38
43
|
```
|
|
39
44
|
入力解析 → Unit特定 → ドメインモデル確認 → 既存実装確認 → 計画作成 → 出力
|
|
40
45
|
```
|
|
@@ -42,7 +47,7 @@ UnitドキュメントとConstructionのドメインモデル設計を元に、*
|
|
|
42
47
|
### Step 1: 入力解析
|
|
43
48
|
|
|
44
49
|
ユーザー入力から抽出:
|
|
45
|
-
-
|
|
50
|
+
- WI ID(WI-XXX形式)
|
|
46
51
|
- 機能名・タスク説明
|
|
47
52
|
- 優先度・制約条件
|
|
48
53
|
|
|
@@ -85,6 +90,7 @@ UnitドキュメントとConstructionのドメインモデル設計を元に、*
|
|
|
85
90
|
計画をmdファイルとして出力。パスの推奨:
|
|
86
91
|
```
|
|
87
92
|
docs/inception/{task_id}_plan.md
|
|
93
|
+
docs/inception/{unit}/WI-XXX/tdd_implementation_plan.md
|
|
88
94
|
```
|
|
89
95
|
|
|
90
96
|
**[Question][Answer]セクション必須**: 不明点や確認事項をまとめ、ユーザーからのフィードバックを受け取れるようにする。
|
|
@@ -18,6 +18,11 @@ Unit単位でアーキテクチャの各層(DB → ドメイン → ユース
|
|
|
18
18
|
|
|
19
19
|
## 前提条件チェック
|
|
20
20
|
|
|
21
|
+
## Pre-flight check (BLOCKING)
|
|
22
|
+
|
|
23
|
+
Before generating any plan, verify `docs/inception/{unit}/WI-XXX/description.md` exists.
|
|
24
|
+
If not, halt and ask the user to create the WI first, or offer to run `phasegate scaffold-wi <unit> <story|issue|chore>`.
|
|
25
|
+
|
|
21
26
|
### 必須インプット(存在しなければ`[Question]`で提供を要求)
|
|
22
27
|
|
|
23
28
|
- **横断モード:**
|
|
@@ -95,7 +100,7 @@ Unit単位でアーキテクチャの各層(DB → ドメイン → ユース
|
|
|
95
100
|
設計方針・スコープ・不明点を整理し、人間の承認を得る。
|
|
96
101
|
|
|
97
102
|
### 出力ファイル
|
|
98
|
-
`docs/inception/{unit}/logical_design_plan.md
|
|
103
|
+
`docs/inception/{unit}/WI-XXX/logical_design_plan.md`
|
|
99
104
|
|
|
100
105
|
### 計画ファイルの構成
|
|
101
106
|
|
|
@@ -173,7 +178,7 @@ Unit単位でアーキテクチャの各層(DB → ドメイン → ユース
|
|
|
173
178
|
| 種別 | 配置先 |
|
|
174
179
|
|------|--------|
|
|
175
180
|
| 横断成果物 | `docs/product/construction/{unit}/logical_design.md` |
|
|
176
|
-
| ストーリー固有成果物 | `docs/inception/{unit}/
|
|
181
|
+
| ストーリー固有成果物 | `docs/inception/{unit}/WI-XXX/logical_design.md` |
|
|
177
182
|
|
|
178
183
|
> **注意**: ストーリー固有の設計は `docs/inception/` に配置する(`docs/folder_management_rules.md` のルール準拠)。`docs/product/construction/` にはUnit全体の共有設計のみを配置する。
|
|
179
184
|
|
|
@@ -194,22 +199,22 @@ traceability:
|
|
|
194
199
|
---
|
|
195
200
|
```
|
|
196
201
|
|
|
197
|
-
`initial_creation: true` は「新規作成であり、後述の `@
|
|
202
|
+
`initial_creation: true` は「新規作成であり、後述の `@work-item-id` 注釈が必須」であることを示す。
|
|
198
203
|
|
|
199
|
-
### 2. `@
|
|
204
|
+
### 2. `@work-item-id` インライン注釈
|
|
200
205
|
|
|
201
|
-
|
|
206
|
+
WI に紐づく設計要素の直前に `@work-item-id WI-XXX` を独立行で記述する。
|
|
202
207
|
|
|
203
208
|
```markdown
|
|
204
|
-
@
|
|
209
|
+
@work-item-id WI-001
|
|
205
210
|
### ユースケース: 注文を確定する
|
|
206
211
|
```
|
|
207
212
|
|
|
208
213
|
形式ルール:
|
|
209
214
|
- **独立行** — 他のテキストと混在させない
|
|
210
215
|
- **直後に設計要素** — 空行を挟まない
|
|
211
|
-
- **
|
|
212
|
-
-
|
|
216
|
+
- **WorkItem 存在** — `WI-XXX` は `docs/inception/{unit}/WI-XXX/description.md` に存在する ID
|
|
217
|
+
- **複数WI時** — 注釈行を連続で並べ、最後の直後に設計要素を置く
|
|
213
218
|
|
|
214
219
|
---
|
|
215
220
|
|
|
@@ -11,6 +11,11 @@ review: opus
|
|
|
11
11
|
|
|
12
12
|
## 前提条件チェック
|
|
13
13
|
|
|
14
|
+
## Pre-flight check (BLOCKING)
|
|
15
|
+
|
|
16
|
+
Before generating any plan, verify `docs/inception/{unit}/WI-XXX/description.md` exists.
|
|
17
|
+
If not, halt and ask the user to create the WI first, or offer to run `phasegate scaffold-wi <unit> <story|issue|chore>`.
|
|
18
|
+
|
|
14
19
|
### 必須インプット(存在しなければ`[Question]`で提供を要求)
|
|
15
20
|
- **要求文書** — 何を作るかを記述した文書。形式は問わない(議事録、要件メモ、口頭要約のテキスト等)
|
|
16
21
|
|