phasegate 0.135.0 → 0.136.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 CHANGED
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.136.0] - 2026-05-09
11
+
12
+ ### Added
13
+
14
+ - **WI-031 — CI template の二系統統一 + `phasegate init --with-ci`** — `ci:generate-template --render` が bundled template を正本として stdout に出力するようにし、`phasegate init --with-ci` で `.github/workflows/aidlc-gate.yml` と `.github/workflows/consistency-check.yml` を opt-in 配置できるようにした。
15
+ - `docs/templates/ci/{aidlc-gate,consistency-check}.yml` と `docs/templates/hooks/pre-commit` を render の正本に統一。
16
+ - `consistency-check` の cron と GitHub Issue 自動作成 logic が CLI render と bundled template で一致。
17
+ - 既存 workflow は上書きせず skipped として扱い、新規 config 作成時は `ci.enabled: true` を保存。
18
+ - `docs/templates/**` を npm package 同梱対象に追加。
19
+
20
+ ### Fixed
21
+
22
+ - **WI-031 / WI-097 — inception 配下の WI ID 重複を解消** — `_cross/WI-031` と `agent-integration/WI-031` が別内容で同一 ID を持っていたため、H11-04 由来の agent-integration 側を `WI-097` に再採番した。
23
+ - `legacy_id: H11-04` は維持し、agent-integration の product docs に `@work-item-id WI-097` を追記。
24
+ - 再発防止の follow-up として `WI-106`(inception 全体での WI ID 重複防止)を起票。
25
+
10
26
  ## [0.135.0] - 2026-05-09
11
27
 
12
28
  ### Added
package/README.ja.md CHANGED
@@ -70,7 +70,7 @@ Node.js >= 18, npm >= 9, TypeScript 5.x
70
70
  npm install --save-dev phasegate
71
71
 
72
72
  # 2. プロジェクトを初期化
73
- npx phasegate init --name my-project --with-husky
73
+ npx phasegate init --name my-project --with-husky --with-ci
74
74
 
75
75
  # 3. AI agent を起動して /product-architect から始める
76
76
  claude
@@ -87,6 +87,7 @@ claude
87
87
  - `docs/principles/*.md` — アーキテクチャ哲学・テスト規約(immutable)
88
88
  - `docs/folder_management_rules.md` — ドキュメント配置ルール(**正本**)
89
89
  - `--with-husky` を付けると `.husky/pre-commit` ・ `.husky/commit-msg` も配置
90
+ - `--with-ci` を付けると `.github/workflows/aidlc-gate.yml` ・ `.github/workflows/consistency-check.yml` も配置
90
91
 
91
92
  **`init` が生成しないもの**(後で skill が作る):
92
93
 
@@ -282,7 +283,7 @@ npx phasegate <command> [options]
282
283
 
283
284
  | コマンド | 説明 |
284
285
  |---|---|
285
- | `init --name <name>` | 初期化(skills/config/hooks 配置)。`--agent claude\|codex\|both`、`--with-husky`、`--preset <full\|standard\|minimal\|custom>` |
286
+ | `init --name <name>` | 初期化(skills/config/hooks 配置)。`--agent claude\|codex\|both`、`--with-husky`、`--with-ci`、`--preset <full\|standard\|minimal\|custom>` |
286
287
  | `update-skills` | スキルを最新版に再デプロイ |
287
288
  | `lint` | L1 Biome AST チェック |
288
289
  | `validate --layer <L1\|L2\|L3\|L4\|all>` | 指定レイヤーのバリデータ実行(`--format human\|agent\|ci`) |
@@ -295,7 +296,7 @@ npx phasegate <command> [options]
295
296
  | `phasegate:detect-drift` | 設計-コード乖離レポート |
296
297
  | `migrate work-items --dry-run` / `--apply` | 既存リポジトリの旧 `ISSUE-XXX` / `H{NN}-{NN}` directory を WI 統一レイアウト(`_cross/{WI-XXX}/` / `{unit}/{WI-XXX}/`)へ移行。frontmatter(`type` / `severity` / `legacy_id` / `affects`)を自動注入。冪等。`--json` で CI/スクリプト連携可。詳細: [Work Item Migration](docs/guide/cli-reference.md#work-item-migration) |
297
298
  | `migrate --schema v3` | `phasegate.config.json` を v3 schema へ昇格(`architecture` キー追加) |
298
- | `ci:generate-template --type <aidlc-gate\|pre-commit\|consistency-check>` | CI/CD テンプレート生成(`--render` でファイル出力) |
299
+ | `ci:generate-template --type <aidlc-gate\|pre-commit\|consistency-check>` | CI/CD テンプレート生成(`--render` bundled template を stdout 出力) |
299
300
  | `list-errors --layer <L0-L4>` | エラー定義一覧 |
300
301
  | `hook <pre-tool-use\|post-tool-use\|stop>` | agent hook を起動(stdin から JSON) |
301
302
  | `pre-commit` | L2 pre-commit バリデータをステージファイルに適用 |
@@ -451,7 +452,6 @@ reports/
451
452
 
452
453
  | Work Item | 内容 |
453
454
  |---|---|
454
- | **[WI-031](docs/inception/_cross/WI-031/description.md)** | CI template の二系統統一 + `phasegate init --with-ci` |
455
455
  | **[WI-032](docs/inception/_cross/WI-032/description.md)** | AGENTS.md / CLAUDE.md auto-refresh パイプライン |
456
456
  | **[WI-033](docs/inception/_cross/WI-033/description.md)** | `doc-freshness` / `pointer-validation` を L4 validator に昇格 |
457
457
 
package/README.md CHANGED
@@ -71,7 +71,7 @@ Node.js >= 18, npm >= 9, TypeScript 5.x
71
71
  npm install --save-dev phasegate
72
72
 
73
73
  # 2. Initialize the project
74
- npx phasegate init --name my-project --with-husky
74
+ npx phasegate init --name my-project --with-husky --with-ci
75
75
 
76
76
  # 3. Start your AI agent and begin with product design
77
77
  claude
@@ -86,6 +86,7 @@ claude
86
86
  - `.claude/settings.json` and/or `.codex/hooks.json` hook configuration
87
87
  - `docs/principles/*.md` and `docs/folder_management_rules.md`
88
88
  - `.husky/pre-commit` and `.husky/commit-msg` when `--with-husky` is passed
89
+ - `.github/workflows/aidlc-gate.yml` and `.github/workflows/consistency-check.yml` when `--with-ci` is passed
89
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
92
 
@@ -463,10 +464,11 @@ npx phasegate <command> [options]
463
464
 
464
465
  | Command | Description |
465
466
  |---|---|
466
- | `init --name <name>` | Initialize project, deploy skills, generate config |
467
+ | `init --name <name>` | Initialize project, deploy skills, generate config. Use `--with-ci` to deploy GitHub Actions workflows. |
467
468
  | `lint` | Run L1 Biome AST checks |
468
469
  | `validate --layer <L2-L4\|all>` | Run validators for specified layer (`--layer L0` prints runtime hook guidance) |
469
470
  | `ci-check` | Full CI check (L2-L4) |
471
+ | `ci:generate-template --type <aidlc-gate\|consistency-check\|pre-commit> --render` | Render the bundled CI/hook template to stdout |
470
472
  | `update-skills` | Update skills to latest version |
471
473
  | `phasegate:status` | Display overall harness health summary |
472
474
  | `phasegate:check-phase --unit <id>` | Check current phase for a Unit |
@@ -512,7 +514,6 @@ The main path is ready for project use, but a few documented behaviors still req
512
514
 
513
515
  | Work Item | Title | Why it matters |
514
516
  |---|---|---|
515
- | **[WI-031](docs/inception/_cross/WI-031/description.md)** | CI template unification + `phasegate init --with-ci` | Today the bundled YAML and the `ci:generate-template` output diverge (different cron schedule, only the bundled one creates GitHub Issues). `phasegate init` does not deploy the workflow, so L4 only runs after the user manually copies the file. WI-031 unifies the two paths and adds an opt-in deploy flag. |
516
517
  | **[WI-032](docs/inception/_cross/WI-032/description.md)** | AGENTS.md / CLAUDE.md auto-refresh pipeline | `ci:migrate-agents-md` exists as a one-shot CLI but there is no scheduled job, and CLAUDE.md is fully hand-maintained. WI-032 adds an `auto-refresh-agent-context` workflow plus a template-driven CLAUDE.md regenerator that preserves user-owned sections. |
517
518
  | **[WI-033](docs/inception/_cross/WI-033/description.md)** | Promote `doc-freshness` / `pointer-validation` to L4 validators | Both capabilities exist as `p2:check-freshness` / `p2:validate-pointers` CLI commands but are not registered as L4 validators, so `validate --layer L4` skips them. WI-033 plumbs them through `validator-system` so they run via the standard L4 path and presets. |
518
519
 
@@ -0,0 +1,97 @@
1
+ # Phasegate — PR検証ワークフロー
2
+ #
3
+ # 使い方:
4
+ # このファイルを .github/workflows/aidlc-gate.yml にコピーして使用する。
5
+ #
6
+ # 実行タイミング: Pull Request(open / synchronize / reopen)
7
+ # 実行内容:
8
+ # 1. harness:lint — L1 Biome AST ルール検証
9
+ # 2. harness:ci-check — L3 CI バリデータ(security / performance / coverage / nyquist)
10
+ # 3. 失敗時: PR にエラーサマリーをコメント
11
+
12
+ name: AIDLC Quality Gate
13
+
14
+ on:
15
+ pull_request:
16
+ types: [opened, synchronize, reopened]
17
+
18
+ jobs:
19
+ harness-gate:
20
+ name: Harness Quality Gate
21
+ runs-on: ubuntu-latest
22
+ permissions:
23
+ pull-requests: write # PRコメント書き込みに必要
24
+
25
+ steps:
26
+ - name: Checkout
27
+ uses: actions/checkout@v4
28
+
29
+ - name: Setup Node.js
30
+ uses: actions/setup-node@v4
31
+ with:
32
+ node-version: '20'
33
+ cache: 'pnpm'
34
+
35
+ - name: Install pnpm
36
+ uses: pnpm/action-setup@v4
37
+ with:
38
+ version: 9
39
+
40
+ - name: Install dependencies
41
+ run: pnpm install --frozen-lockfile
42
+
43
+ # L1: Biome AST ルール
44
+ - name: L1 Lint (Biome AST)
45
+ id: lint
46
+ run: |
47
+ set +e
48
+ RESULT=$(pnpm run harness lint --json 2>&1)
49
+ EXIT_CODE=$?
50
+ echo "result<<EOF" >> $GITHUB_OUTPUT
51
+ echo "$RESULT" >> $GITHUB_OUTPUT
52
+ echo "EOF" >> $GITHUB_OUTPUT
53
+ echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
54
+ exit $EXIT_CODE
55
+
56
+ # L3: CI バリデータ(security / performance / coverage / nyquist)
57
+ - name: L3 CI Check
58
+ id: ci_check
59
+ if: always()
60
+ run: |
61
+ set +e
62
+ RESULT=$(pnpm run harness harness:ci-check --json 2>&1)
63
+ EXIT_CODE=$?
64
+ echo "result<<EOF" >> $GITHUB_OUTPUT
65
+ echo "$RESULT" >> $GITHUB_OUTPUT
66
+ echo "EOF" >> $GITHUB_OUTPUT
67
+ echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
68
+ exit $EXIT_CODE
69
+
70
+ # 失敗時のみ PR にコメント
71
+ - name: Post failure comment
72
+ if: failure()
73
+ uses: actions/github-script@v7
74
+ with:
75
+ script: |
76
+ const lintResult = `${{ steps.lint.outputs.result }}`;
77
+ const ciResult = `${{ steps.ci_check.outputs.result }}`;
78
+ const lintFailed = '${{ steps.lint.outputs.exit_code }}' !== '0';
79
+ const ciFailed = '${{ steps.ci_check.outputs.exit_code }}' !== '0';
80
+
81
+ let body = '## ❌ AIDLC Quality Gate — 失敗\n\n';
82
+
83
+ if (lintFailed) {
84
+ body += '### L1 Lint エラー\n```json\n' + lintResult + '\n```\n\n';
85
+ }
86
+ if (ciFailed) {
87
+ body += '### L3 CI Check エラー\n```json\n' + ciResult + '\n```\n\n';
88
+ }
89
+
90
+ body += '---\n> 修正方法は各 `HarnessError.suggestion` および `HarnessError.fix_example` を参照してください。';
91
+
92
+ github.rest.issues.createComment({
93
+ issue_number: context.issue.number,
94
+ owner: context.repo.owner,
95
+ repo: context.repo.repo,
96
+ body,
97
+ });
@@ -0,0 +1,117 @@
1
+ # Phasegate — 週次整合性チェックワークフロー
2
+ #
3
+ # 使い方:
4
+ # このファイルを .github/workflows/consistency-check.yml にコピーして使用する。
5
+ #
6
+ # 実行タイミング: 毎週月曜 09:00 UTC(手動トリガーも可)
7
+ # 実行内容:
8
+ # 1. harness:detect-drift — L4 設計⇔コード乖離検出(drift-detect)
9
+ # 2. harness:complete-check — L4 全バリデータ(drift-detect / consistency-check / dead-code)
10
+ # 3. 乖離検出時: GitHub Issue を自動作成
11
+
12
+ name: AIDLC Consistency Check (Weekly)
13
+
14
+ on:
15
+ schedule:
16
+ - cron: '0 9 * * 1' # 毎週月曜 09:00 UTC
17
+ workflow_dispatch: # 手動トリガー
18
+
19
+ jobs:
20
+ consistency-check:
21
+ name: L4 Consistency & Drift Check
22
+ runs-on: ubuntu-latest
23
+ permissions:
24
+ issues: write # Issue 自動作成に必要
25
+
26
+ steps:
27
+ - name: Checkout
28
+ uses: actions/checkout@v4
29
+
30
+ - name: Setup Node.js
31
+ uses: actions/setup-node@v4
32
+ with:
33
+ node-version: '20'
34
+ cache: 'pnpm'
35
+
36
+ - name: Install pnpm
37
+ uses: pnpm/action-setup@v4
38
+ with:
39
+ version: 9
40
+
41
+ - name: Install dependencies
42
+ run: pnpm install --frozen-lockfile
43
+
44
+ # L4-001: Drift Detection(設計⇔コード双方向乖離)
45
+ - name: L4 Drift Detection
46
+ id: drift
47
+ run: |
48
+ set +e
49
+ RESULT=$(pnpm run harness harness:detect-drift --json 2>&1)
50
+ EXIT_CODE=$?
51
+ echo "result<<EOF" >> $GITHUB_OUTPUT
52
+ echo "$RESULT" >> $GITHUB_OUTPUT
53
+ echo "EOF" >> $GITHUB_OUTPUT
54
+ echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
55
+ exit 0 # Issue作成のため常に続行
56
+
57
+ # L4: 全バリデータ(drift-detect + consistency-check + dead-code)
58
+ - name: L4 Complete Check
59
+ id: complete
60
+ run: |
61
+ set +e
62
+ RESULT=$(pnpm run harness harness:complete-check --json 2>&1)
63
+ EXIT_CODE=$?
64
+ echo "result<<EOF" >> $GITHUB_OUTPUT
65
+ echo "$RESULT" >> $GITHUB_OUTPUT
66
+ echo "EOF" >> $GITHUB_OUTPUT
67
+ echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
68
+ exit 0 # Issue作成のため常に続行
69
+
70
+ # 乖離が検出された場合のみ Issue を作成
71
+ - name: Create issue on drift detected
72
+ if: |
73
+ steps.drift.outputs.exit_code != '0' ||
74
+ steps.complete.outputs.exit_code != '0'
75
+ uses: actions/github-script@v7
76
+ with:
77
+ script: |
78
+ const driftResult = `${{ steps.drift.outputs.result }}`;
79
+ const completeResult = `${{ steps.complete.outputs.result }}`;
80
+ const driftFailed = '${{ steps.drift.outputs.exit_code }}' !== '0';
81
+ const completeFailed = '${{ steps.complete.outputs.exit_code }}' !== '0';
82
+
83
+ const today = new Date().toISOString().slice(0, 10);
84
+ let body = `## 🔍 AIDLC 整合性チェック結果 — ${today}\n\n`;
85
+ body += '> 週次スケジュール実行により設計⇔コードの乖離が検出されました。\n\n';
86
+
87
+ if (driftFailed) {
88
+ body += '### L4-001 Drift Detection\n```json\n' + driftResult + '\n```\n\n';
89
+ }
90
+ if (completeFailed) {
91
+ body += '### L4 Complete Check\n```json\n' + completeResult + '\n```\n\n';
92
+ }
93
+
94
+ body += '---\n';
95
+ body += '### 対処方法\n';
96
+ body += '1. 乖離の内容を確認し、設計文書またはコードのどちらを修正すべきか判断\n';
97
+ body += '2. `cascade-updater` スキルで設計文書を更新、または実装を修正\n';
98
+ body += '3. `pnpm run harness harness:detect-drift` で再検証して乖離が解消されたことを確認\n';
99
+
100
+ github.rest.issues.create({
101
+ owner: context.repo.owner,
102
+ repo: context.repo.repo,
103
+ title: `[AIDLC] 設計⇔コード乖離検出 (${today})`,
104
+ body,
105
+ labels: ['aidlc', 'drift-detected', 'automated'],
106
+ });
107
+
108
+ - name: Summary
109
+ run: |
110
+ DRIFT_CODE="${{ steps.drift.outputs.exit_code }}"
111
+ COMPLETE_CODE="${{ steps.complete.outputs.exit_code }}"
112
+ if [ "$DRIFT_CODE" = "0" ] && [ "$COMPLETE_CODE" = "0" ]; then
113
+ echo "✅ 設計⇔コード整合性: 問題なし"
114
+ else
115
+ echo "❌ 乖離を検出しました。GitHub Issue を確認してください。"
116
+ exit 1
117
+ fi
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env sh
2
+ # PhaseGate commit-msg hook template.
3
+ #
4
+ # Usage:
5
+ # 1. Copy this file to .husky/commit-msg
6
+ # 2. chmod +x .husky/commit-msg
7
+ #
8
+ # The hook validates commit-message trailers that require access to the final
9
+ # commit message, such as `Work-Item: WI-XXX` for WI document changes.
10
+
11
+ . "$(dirname -- "$0")/_/husky.sh"
12
+
13
+ npx phasegate commit-msg "$1"
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env sh
2
+ # GSDLC Quality Harness — Pre-commit Hook テンプレート
3
+ #
4
+ # 使い方:
5
+ # 1. このファイルを .husky/pre-commit にコピーする
6
+ # 2. chmod +x .husky/pre-commit
7
+ # 3. pnpm add --save-dev husky && npx husky install
8
+ #
9
+ # 実行内容:
10
+ # L1: Biome AST ルール(require-unit-comment, no-layer-violation など 8ルール)
11
+ # L2: Pre-commit バリデータ(phase-gate / metadata / test-quality)
12
+ #
13
+ # ⚠️ Quick Mode の場合は HARNESS_QUICK_MODE=1 を設定してコミットする:
14
+ # HARNESS_QUICK_MODE=1 git commit -m "fix: ..."
15
+
16
+ . "$(dirname -- "$0")/_/husky.sh"
17
+
18
+ HARNESS_CMD="npx tsx scripts/harness/main.ts"
19
+
20
+ echo "🔍 AIDLC Quality Gate (Pre-commit)"
21
+ echo "──────────────────────────────────"
22
+
23
+ # -----------------------------------------------------------------------
24
+ # L1: Biome AST ルール
25
+ # -----------------------------------------------------------------------
26
+ echo "▶ L1 Lint (Biome AST)..."
27
+ $HARNESS_CMD lint
28
+ L1_EXIT=$?
29
+
30
+ if [ $L1_EXIT -ne 0 ]; then
31
+ echo ""
32
+ echo "❌ L1 Lint が失敗しました。"
33
+ echo " 修正してから再度コミットしてください。"
34
+ echo " 詳細: pnpm run harness lint"
35
+ exit 1
36
+ fi
37
+ echo "✅ L1 Lint: passed"
38
+
39
+ # -----------------------------------------------------------------------
40
+ # L2: Pre-commit バリデータ
41
+ # -----------------------------------------------------------------------
42
+ # Quick Mode では phase-gate をスキップ
43
+ if [ "${HARNESS_QUICK_MODE:-0}" = "1" ]; then
44
+ echo "▶ L2 Validators (Quick Mode: phase-gate スキップ)..."
45
+ $HARNESS_CMD check-phase-gate --skip-phase-gate
46
+ else
47
+ echo "▶ L2 Validators (phase-gate / metadata / test-quality)..."
48
+ $HARNESS_CMD check-phase-gate
49
+ fi
50
+ L2_EXIT=$?
51
+
52
+ if [ $L2_EXIT -ne 0 ]; then
53
+ echo ""
54
+ echo "❌ L2 Validators が失敗しました。"
55
+ echo " 修正してから再度コミットしてください。"
56
+ echo " 詳細: pnpm run harness check-phase-gate"
57
+ exit 1
58
+ fi
59
+ echo "✅ L2 Validators: passed"
60
+
61
+ echo "──────────────────────────────────"
62
+ echo "✅ AIDLC Quality Gate: passed"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phasegate",
3
- "version": "0.135.0",
3
+ "version": "0.136.0",
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",
@@ -38,6 +38,7 @@
38
38
  "docs/contracts/**",
39
39
  "docs/principles/**",
40
40
  "docs/guide/**",
41
+ "docs/templates/**",
41
42
  "docs/folder_management_rules.md",
42
43
  "LICENSE",
43
44
  "CHANGELOG.md"
@@ -70,7 +70,7 @@ export function buildCiGovernance(
70
70
  const presetConfigAdapter = new PresetConfigAdapter();
71
71
  const errorRepetitionRepository = new ErrorRepetitionJsonRepository(baseDir);
72
72
  const escalationExecutorPort = new EscalationLogExecutorAdapter();
73
- const templateRendererPort = new YamlTemplateRendererAdapter();
73
+ const templateRendererPort = new YamlTemplateRendererAdapter(harnessRoot);
74
74
  const fileExistencePort = new FileSystemExistenceAdapter(baseDir);
75
75
  const commandExistencePort = new HarnessApiCommandExistenceAdapter();
76
76
  const adrExistencePort = new AdrFoundationExistenceAdapter();
@@ -5,6 +5,8 @@
5
5
  * TemplateRendererPort実装(YAML書き出し)
6
6
  */
7
7
 
8
+ import { readFile } from 'node:fs/promises';
9
+ import { join } from 'node:path';
8
10
  import type { TemplateRendererPort, TemplateRenderOutput } from '../../domain/ports/template-renderer-port.js';
9
11
  import type { CiTemplate } from '../../domain/aggregates/ci-template.js';
10
12
 
@@ -14,40 +16,21 @@ const OUTPUT_PATH_MAP: Record<string, string> = {
14
16
  'pre-commit': '.husky/pre-commit',
15
17
  };
16
18
 
19
+ const TEMPLATE_PATH_MAP: Record<string, string> = {
20
+ 'aidlc-gate': 'docs/templates/ci/aidlc-gate.yml',
21
+ 'consistency-check': 'docs/templates/ci/consistency-check.yml',
22
+ 'pre-commit': 'docs/templates/hooks/pre-commit',
23
+ };
24
+
17
25
  export class YamlTemplateRendererAdapter implements TemplateRendererPort {
26
+ constructor(private readonly harnessRoot: string = process.cwd()) {}
27
+
18
28
  async render(ciTemplate: CiTemplate): Promise<TemplateRenderOutput> {
19
29
  const outputPath = OUTPUT_PATH_MAP[ciTemplate.templateType] ?? '';
20
- const content = this.generateContent(ciTemplate);
30
+ const templatePath = TEMPLATE_PATH_MAP[ciTemplate.templateType];
31
+ const content = templatePath === undefined
32
+ ? `# ${ciTemplate.templateType} template (not configured)`
33
+ : await readFile(join(this.harnessRoot, templatePath), 'utf-8');
21
34
  return { outputPath, content };
22
35
  }
23
-
24
- private generateContent(ciTemplate: CiTemplate): string {
25
- const { templateType, config } = ciTemplate;
26
-
27
- if (!config) {
28
- return `# ${templateType} template (not configured)`;
29
- }
30
-
31
- if (templateType === 'pre-commit') {
32
- return [
33
- '#!/bin/sh',
34
- '. "$(dirname "$0")/_/husky.sh"',
35
- '',
36
- `npx phasegate lint --validators ${config.targetValidatorIds.join(',')}`,
37
- ].join('\n');
38
- }
39
-
40
- return [
41
- `name: ${templateType}`,
42
- `on:`,
43
- ` ${config.triggerCondition === 'pull_request' ? 'pull_request' : 'schedule'}:`,
44
- config.triggerCondition === 'schedule' ? ' - cron: "0 2 * * *"' : '',
45
- `jobs:`,
46
- ` validate:`,
47
- ` runs-on: ubuntu-latest`,
48
- ` steps:`,
49
- ` - uses: actions/checkout@v4`,
50
- ` - run: npx phasegate lint --validators ${config.targetValidatorIds.join(',')}`,
51
- ].filter((l) => l !== '').join('\n');
52
- }
53
36
  }
@@ -31,7 +31,25 @@ export class GenerateCiTemplateHandler {
31
31
  ) {}
32
32
 
33
33
  async handle(args: GenerateCiTemplateHandlerArgs): Promise<GenerateCiTemplateHandlerResult> {
34
- const { presetId, templateType, format = 'human' } = args;
34
+ const { presetId, templateType, render = false, format = 'human' } = args;
35
+
36
+ if (render) {
37
+ const result = await this.renderUseCase.execute({
38
+ presetId,
39
+ templateType: templateType as TemplateType,
40
+ });
41
+ const hasErrors = result.errors.length > 0;
42
+ const output = format === 'json'
43
+ ? JSON.stringify(result, null, 2)
44
+ : hasErrors
45
+ ? result.errors.map((err) => `[${err.code}] ${err.message}`).join('\n')
46
+ : result.content;
47
+
48
+ return {
49
+ exitCode: hasErrors ? 1 : 0,
50
+ output,
51
+ };
52
+ }
35
53
 
36
54
  const result = await this.generateUseCase.execute({
37
55
  presetId,
@@ -41,6 +41,7 @@ import type { SkillSet } from "./setup/skill-deployer.js";
41
41
  import {
42
42
  deployAgentSkillLinks,
43
43
  deployCodexHooks,
44
+ deployCiWorkflows,
44
45
  deployDesignDocs,
45
46
  deployHookScripts,
46
47
  deployHuskyCommitMsgHook,
@@ -89,7 +90,7 @@ Usage: phasegate <command> [options]
89
90
  Setup:
90
91
  init Initialize project: deploy skills + design docs + phasegate.config.json
91
92
  (--name <project-name>, --preset <full|standard|minimal|custom>,
92
- --skills <core|all>, --agent <claude|codex|both>, --with-husky, --yes)
93
+ --skills <core|all>, --agent <claude|codex|both>, --with-husky, --with-ci, --yes)
93
94
  update-skills Re-deploy skills from current harness version
94
95
 
95
96
  Commands:
@@ -251,6 +252,7 @@ Options:
251
252
  --skills <core|all> Skill set to deploy (default: "all")
252
253
  --agent <claude|codex|both> Agent integration target (default: "claude")
253
254
  --with-husky Install Husky pre-commit hooks
255
+ --with-ci Install GitHub Actions workflows
254
256
  --yes Skip confirmation prompts
255
257
  --help, -h Show this help`,
256
258
  "update-skills": `Usage: phasegate update-skills [options]
@@ -636,7 +638,7 @@ async function main(): Promise<void> {
636
638
  switch (command) {
637
639
  // ── harness setup ──
638
640
  case "init": {
639
- const KNOWN_INIT_FLAGS = ["--name", "--preset", "--skills", "--agent", "--with-husky", "--yes"];
641
+ const KNOWN_INIT_FLAGS = ["--name", "--preset", "--skills", "--agent", "--with-husky", "--with-ci", "--yes"];
640
642
  const flagError = validateKnownFlags(args, KNOWN_INIT_FLAGS);
641
643
  if (flagError) {
642
644
  console.error(flagError);
@@ -674,7 +676,8 @@ async function main(): Promise<void> {
674
676
  claude: deployClaude,
675
677
  codex: deployCodex,
676
678
  });
677
- const configResult = await initHarnessConfig(rootDir, projectName, phasePreset);
679
+ const withCi = hasFlag(args, "--with-ci");
680
+ const configResult = await initHarnessConfig(rootDir, projectName, phasePreset, { ciEnabled: withCi });
678
681
  const hooksResult = deployClaude
679
682
  ? await deployHookScripts(harnessRoot, rootDir)
680
683
  : {
@@ -689,6 +692,7 @@ async function main(): Promise<void> {
689
692
  const withHusky = hasFlag(args, "--with-husky");
690
693
  const huskyResult = withHusky ? await deployHuskyHook(harnessRoot, rootDir) : null;
691
694
  const huskyCommitMsgResult = withHusky ? await deployHuskyCommitMsgHook(harnessRoot, rootDir) : null;
695
+ const ciWorkflowResult = withCi ? await deployCiWorkflows(harnessRoot, rootDir) : null;
692
696
  console.log(
693
697
  `✓ Skills deployed to ${result.targetDir} (${result.deployedSkills.length} skills, set: ${skillSet})`,
694
698
  );
@@ -753,6 +757,14 @@ async function main(): Promise<void> {
753
757
  console.log(` .husky/commit-msg already exists, skipped`);
754
758
  }
755
759
  }
760
+ if (ciWorkflowResult !== null) {
761
+ if (ciWorkflowResult.copiedFiles.length > 0) {
762
+ console.log(`✓ CI workflows deployed (${ciWorkflowResult.copiedFiles.length} files)`);
763
+ }
764
+ for (const skipped of ciWorkflowResult.skippedFiles) {
765
+ console.log(` ${skipped} already exists, skipped`);
766
+ }
767
+ }
756
768
  console.log(`✓ Harness v${result.version} initialized (agent: ${agent})`);
757
769
  console.log("");
758
770
  console.log("Next steps:");
@@ -1199,7 +1211,7 @@ Examples:
1199
1211
  phasegate ci:generate-template --preset strict --type pre-commit --render`);
1200
1212
  process.exit(0);
1201
1213
  }
1202
- const mod = buildCiGovernance(rootDir);
1214
+ const mod = buildCiGovernance(rootDir, harnessRoot);
1203
1215
  const presetId = parseFlag(args, "--preset") ?? "default";
1204
1216
  const templateType = parseFlag(args, "--type") ?? "aidlc-gate";
1205
1217
  const render = hasFlag(args, "--render");
@@ -1211,7 +1223,7 @@ Examples:
1211
1223
  }
1212
1224
 
1213
1225
  case "ci:migrate-agents-md": {
1214
- const mod = buildCiGovernance(rootDir);
1226
+ const mod = buildCiGovernance(rootDir, harnessRoot);
1215
1227
  const dryRun = hasFlag(args, "--dry-run");
1216
1228
  const validateOnly = hasFlag(args, "--validate-only");
1217
1229
  const format = json ? "json" : "human";
@@ -1222,7 +1234,7 @@ Examples:
1222
1234
  }
1223
1235
 
1224
1236
  case "ci:check-repetition": {
1225
- const mod = buildCiGovernance(rootDir);
1237
+ const mod = buildCiGovernance(rootDir, harnessRoot);
1226
1238
  const errorCode = parseFlag(args, "--code") ?? "";
1227
1239
  const reset = hasFlag(args, "--reset");
1228
1240
  const format = json ? "json" : "human";
@@ -1233,7 +1245,7 @@ Examples:
1233
1245
  }
1234
1246
 
1235
1247
  case "baseline": {
1236
- const mod = buildCiGovernance(rootDir);
1248
+ const mod = buildCiGovernance(rootDir, harnessRoot);
1237
1249
  const dryRun = hasFlag(args, "--dry-run");
1238
1250
  const force = hasFlag(args, "--force");
1239
1251
  const pathsFlag = parseFlag(args, "--paths");
@@ -420,10 +420,15 @@ export async function deployHookScripts(harnessRoot: string, projectRoot: string
420
420
  };
421
421
  }
422
422
 
423
+ export interface InitHarnessConfigOptions {
424
+ ciEnabled?: boolean;
425
+ }
426
+
423
427
  export async function initHarnessConfig(
424
428
  projectRoot: string,
425
429
  projectName: string,
426
430
  phasePreset?: "full" | "standard" | "minimal" | "custom",
431
+ options: InitHarnessConfigOptions = {},
427
432
  ): Promise<{ created: boolean; path: string }> {
428
433
  const configPath = join(projectRoot, HARNESS_CONFIG_FILE);
429
434
  try {
@@ -461,6 +466,7 @@ export async function initHarnessConfig(
461
466
  format: "json",
462
467
  outputDir: "reports",
463
468
  },
469
+ ...(options.ciEnabled ? { ci: { enabled: true } } : {}),
464
470
  };
465
471
 
466
472
  await fs.writeFile(configPath, JSON.stringify(template, null, 2) + "\n", "utf-8");
@@ -532,6 +538,46 @@ export interface DeployHuskyHookResult {
532
538
  path: string;
533
539
  }
534
540
 
541
+ export interface DeployCiWorkflowsResult {
542
+ copiedFiles: string[];
543
+ skippedFiles: string[];
544
+ }
545
+
546
+ export async function deployCiWorkflows(harnessRoot: string, projectRoot: string): Promise<DeployCiWorkflowsResult> {
547
+ const copiedFiles: string[] = [];
548
+ const skippedFiles: string[] = [];
549
+ const workflows = [
550
+ {
551
+ relativeSource: join("docs", "templates", "ci", "aidlc-gate.yml"),
552
+ relativeTarget: join(".github", "workflows", "aidlc-gate.yml"),
553
+ },
554
+ {
555
+ relativeSource: join("docs", "templates", "ci", "consistency-check.yml"),
556
+ relativeTarget: join(".github", "workflows", "consistency-check.yml"),
557
+ },
558
+ ];
559
+
560
+ await fs.mkdir(join(projectRoot, ".github", "workflows"), { recursive: true });
561
+
562
+ for (const workflow of workflows) {
563
+ const sourcePath = join(harnessRoot, workflow.relativeSource);
564
+ const targetPath = join(projectRoot, workflow.relativeTarget);
565
+
566
+ try {
567
+ await fs.access(targetPath);
568
+ skippedFiles.push(workflow.relativeTarget);
569
+ continue;
570
+ } catch {
571
+ // ファイルが存在しない場合のみコピーする
572
+ }
573
+
574
+ await fs.copyFile(sourcePath, targetPath);
575
+ copiedFiles.push(workflow.relativeTarget);
576
+ }
577
+
578
+ return { copiedFiles, skippedFiles };
579
+ }
580
+
535
581
  export async function deployHuskyHook(harnessRoot: string, projectRoot: string): Promise<DeployHuskyHookResult> {
536
582
  const targetPath = join(projectRoot, ".husky", "pre-commit");
537
583
 
@@ -540,7 +586,7 @@ export async function deployHuskyHook(harnessRoot: string, projectRoot: string):
540
586
  return { created: false, path: targetPath };
541
587
  } catch {}
542
588
 
543
- const sourcePath = join(harnessRoot, "templates", ".husky", "pre-commit");
589
+ const sourcePath = join(harnessRoot, "docs", "templates", "hooks", "pre-commit");
544
590
  await fs.mkdir(join(projectRoot, ".husky"), { recursive: true });
545
591
  await fs.copyFile(sourcePath, targetPath);
546
592
  await fs.chmod(targetPath, 0o755);
@@ -559,7 +605,7 @@ export async function deployHuskyCommitMsgHook(
559
605
  return { created: false, path: targetPath };
560
606
  } catch {}
561
607
 
562
- const sourcePath = join(harnessRoot, "templates", ".husky", "commit-msg");
608
+ const sourcePath = join(harnessRoot, "docs", "templates", "hooks", "commit-msg");
563
609
  await fs.mkdir(join(projectRoot, ".husky"), { recursive: true });
564
610
  await fs.copyFile(sourcePath, targetPath);
565
611
  await fs.chmod(targetPath, 0o755);
@@ -13,7 +13,8 @@ export type WorkItemStatus =
13
13
  | 'drafted'
14
14
  | 'reflected'
15
15
  | 'implemented'
16
- | 'tested';
16
+ | 'tested'
17
+ | 'completed';
17
18
 
18
19
  export interface WorkItemFrontmatter {
19
20
  readonly id: string;
@@ -54,4 +55,5 @@ export const WORK_ITEM_STATUSES: ReadonlySet<WorkItemStatus> = new Set([
54
55
  'reflected',
55
56
  'implemented',
56
57
  'tested',
58
+ 'completed',
57
59
  ]);
@@ -12,6 +12,10 @@ export class PhaseDependencyPhaseGatePolicyAdapter implements PhaseGatePolicyPor
12
12
  satisfied: boolean;
13
13
  violations: readonly HarnessErrorLike[];
14
14
  }> {
15
+ if (context.unitName.trim().length === 0) {
16
+ return { satisfied: true, violations: [] };
17
+ }
18
+
15
19
  try {
16
20
  // WI-085: paths config を phase-dependency-model に流入させる
17
21
  const { createConfigFoundationModule } = await import('../../../config-foundation/composition-root.js');