phasegate 0.134.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 +27 -0
- package/README.ja.md +4 -4
- package/README.md +4 -3
- package/bin/phasegate +2 -0
- package/docs/contracts/lesson-artifact.schema.json +62 -0
- package/docs/contracts/requirement-test-matrix.schema.json +62 -0
- package/docs/guide/layer-model.md +2 -2
- package/docs/principles/architecture-philosophy.md +26 -55
- package/docs/principles/model-routing.md +30 -165
- package/docs/principles/testing-rules.md +66 -648
- package/docs/templates/ci/aidlc-gate.yml +97 -0
- package/docs/templates/ci/consistency-check.yml +117 -0
- package/docs/templates/hooks/commit-msg +13 -0
- package/docs/templates/hooks/pre-commit +62 -0
- package/package.json +5 -3
- package/scripts/harness/ci-governance/composition-root.ts +1 -1
- package/scripts/harness/ci-governance/infrastructure/adapters/yaml-template-renderer-adapter.ts +14 -31
- package/scripts/harness/ci-governance/presentation/handlers/generate-ci-template-handler.ts +19 -1
- package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +2 -2
- package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +1 -1
- package/scripts/harness/config-foundation/infrastructure/presets/standard.json +1 -1
- package/scripts/harness/config-foundation/infrastructure/presets/strict.json +1 -1
- package/scripts/harness/harness-error/infrastructure/adapters/validator-registry-bridge-adapter.ts +2 -0
- package/scripts/harness/harness-error/infrastructure/registry/l4-error-definitions.ts +14 -0
- package/scripts/harness/main.ts +19 -7
- package/scripts/harness/phase2-extensions/composition-root.ts +7 -1
- package/scripts/harness/phase2-extensions/infrastructure/adapters/file-system-document-scanner-adapter.ts +16 -2
- package/scripts/harness/phase2-extensions/infrastructure/adapters/harness-config-freshness-adapter.ts +13 -2
- package/scripts/harness/phase2-extensions/infrastructure/adapters/regex-pointer-extractor-adapter.ts +35 -4
- package/scripts/harness/quick-mode/infrastructure/adapters/validator-system-validator-id-registry-adapter.ts +1 -1
- package/scripts/harness/setup/skill-deployer.ts +48 -2
- package/scripts/harness/skill-quality/infrastructure/adapters/validator-id-registry-bridge-adapter.ts +1 -1
- package/scripts/harness/traceability-model/domain/value-objects/work-item-frontmatter.ts +3 -1
- package/scripts/harness/validator-system/application/dto/run-l4-validators-input.ts +1 -0
- package/scripts/harness/validator-system/application/use-cases/run-full-validation-usecase.ts +1 -0
- package/scripts/harness/validator-system/application/use-cases/run-l4-validators-usecase.ts +133 -3
- package/scripts/harness/validator-system/composition-root.ts +8 -2
- package/scripts/harness/validator-system/domain/value-objects/validator-id.ts +11 -4
- package/scripts/harness/validator-system/infrastructure/adapters/biome-ast-source-code-analyzer-adapter.ts +29 -10
- package/scripts/harness/validator-system/infrastructure/adapters/harness-config-validator-config-adapter.ts +11 -2
- package/scripts/harness/validator-system/infrastructure/adapters/phase-dependency-phase-gate-policy-adapter.ts +4 -0
|
@@ -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,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phasegate",
|
|
3
|
-
"version": "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",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/junpei-9898/phasegate.git"
|
|
9
|
+
"url": "git+https://github.com/junpei-9898/phasegate.git"
|
|
10
10
|
},
|
|
11
11
|
"homepage": "https://github.com/junpei-9898/phasegate#readme",
|
|
12
12
|
"bugs": {
|
|
@@ -35,14 +35,16 @@
|
|
|
35
35
|
"skills/**",
|
|
36
36
|
"templates/**",
|
|
37
37
|
"docs/ADR/**",
|
|
38
|
+
"docs/contracts/**",
|
|
38
39
|
"docs/principles/**",
|
|
39
40
|
"docs/guide/**",
|
|
41
|
+
"docs/templates/**",
|
|
40
42
|
"docs/folder_management_rules.md",
|
|
41
43
|
"LICENSE",
|
|
42
44
|
"CHANGELOG.md"
|
|
43
45
|
],
|
|
44
46
|
"bin": {
|
|
45
|
-
"phasegate": "
|
|
47
|
+
"phasegate": "bin/phasegate"
|
|
46
48
|
},
|
|
47
49
|
"scripts": {
|
|
48
50
|
"phasegate": "npx tsx scripts/harness/main.ts",
|
|
@@ -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();
|
package/scripts/harness/ci-governance/infrastructure/adapters/yaml-template-renderer-adapter.ts
CHANGED
|
@@ -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
|
|
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,
|
package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @layer application
|
|
3
3
|
* @unit config-foundation
|
|
4
|
-
* @work-item-id WI-092 / WI-094
|
|
4
|
+
* @work-item-id WI-092 / WI-094 / WI-033
|
|
5
5
|
*/
|
|
6
6
|
import type { HarnessConfigV2 } from '../../domain/harness-config.js';
|
|
7
7
|
|
|
@@ -13,7 +13,7 @@ export function toValidatorSystemConfig(resolvedConfig: HarnessConfigV2 | undefi
|
|
|
13
13
|
layers: {
|
|
14
14
|
L2: { enabled: resolvedConfig.layers.L2.enabled },
|
|
15
15
|
L3: { enabled: resolvedConfig.layers.L3.enabled },
|
|
16
|
-
L4: { enabled: resolvedConfig.layers.L4.enabled },
|
|
16
|
+
L4: { enabled: resolvedConfig.layers.L4.enabled, validators: resolvedConfig.layers.L4.validators },
|
|
17
17
|
},
|
|
18
18
|
validate: {
|
|
19
19
|
failOnWarning: resolvedConfig.validate.failOnWarning,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"L4": {
|
|
17
17
|
"enabled": true,
|
|
18
|
-
"validators": ["drift-detector", "dead-code-detector"],
|
|
18
|
+
"validators": ["drift-detector", "consistency-checker", "dead-code-detector", "doc-freshness-checker", "pointer-validator"],
|
|
19
19
|
"schedule": "0 1 * * *"
|
|
20
20
|
}
|
|
21
21
|
},
|
|
@@ -50,4 +50,18 @@ export const L4_ERROR_DEFINITIONS = Object.freeze([
|
|
|
50
50
|
ownerValidatorId: 'dead-code',
|
|
51
51
|
defaultFixExample: 'const actual = "remove unused export";',
|
|
52
52
|
}),
|
|
53
|
+
createDefinition({
|
|
54
|
+
code: 'L4-004',
|
|
55
|
+
title: '設計ドキュメントの鮮度が閾値を超過した',
|
|
56
|
+
category: 'consistency',
|
|
57
|
+
ownerValidatorId: 'doc-freshness',
|
|
58
|
+
defaultFixExample: 'const actual = "review or refresh stale design document";',
|
|
59
|
+
}),
|
|
60
|
+
createDefinition({
|
|
61
|
+
code: 'L4-005',
|
|
62
|
+
title: '設計ドキュメント内のポインタ参照が解決できない',
|
|
63
|
+
category: 'consistency',
|
|
64
|
+
ownerValidatorId: 'pointer-validation',
|
|
65
|
+
defaultFixExample: 'const actual = "fix unresolved document pointer";',
|
|
66
|
+
}),
|
|
53
67
|
]);
|
package/scripts/harness/main.ts
CHANGED
|
@@ -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
|
|
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");
|
|
@@ -25,7 +25,13 @@ import { ValidatePointersHandler } from './presentation/handlers/validate-pointe
|
|
|
25
25
|
|
|
26
26
|
export function buildPhase2Extensions(projectRoot: string, config?: HarnessConfigV2) {
|
|
27
27
|
const configAdapter = new HarnessConfigFreshnessAdapter(config);
|
|
28
|
-
const
|
|
28
|
+
const inceptionDocsRoot = config?.paths?.inceptionDocs.replace(/\\/g, '/').replace(/\/+$/g, '') ?? 'docs/inception';
|
|
29
|
+
const documentScanner = new FileSystemDocumentScannerAdapter(projectRoot, {
|
|
30
|
+
excludePatterns: [
|
|
31
|
+
new RegExp(`^${inceptionDocsRoot.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/`),
|
|
32
|
+
/^docs\/.*\/archive\//,
|
|
33
|
+
],
|
|
34
|
+
});
|
|
29
35
|
const documentAge = new GitLogDocumentAgeAdapter(projectRoot);
|
|
30
36
|
const pointerExtractor = new RegexPointerExtractorAdapter(projectRoot);
|
|
31
37
|
const pointerResolver = new FileSystemPointerResolverAdapter(projectRoot);
|
|
@@ -6,6 +6,10 @@ import * as fs from 'node:fs/promises';
|
|
|
6
6
|
import * as path from 'node:path';
|
|
7
7
|
import type { DocumentScannerPort } from '../../domain/ports/document-scanner-port.js';
|
|
8
8
|
|
|
9
|
+
export interface FileSystemDocumentScannerAdapterOptions {
|
|
10
|
+
readonly excludePatterns?: readonly RegExp[];
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
function escapeRegex(value: string): string {
|
|
10
14
|
return value.replace(/[|\\{}()[\]^$+?.]/g, '\\$&');
|
|
11
15
|
}
|
|
@@ -67,11 +71,21 @@ async function walk(root: string, current = ''): Promise<string[]> {
|
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
export class FileSystemDocumentScannerAdapter implements DocumentScannerPort {
|
|
70
|
-
|
|
74
|
+
private readonly excludePatterns: readonly RegExp[];
|
|
75
|
+
|
|
76
|
+
constructor(
|
|
77
|
+
private readonly projectRoot: string,
|
|
78
|
+
options: FileSystemDocumentScannerAdapterOptions = {},
|
|
79
|
+
) {
|
|
80
|
+
this.excludePatterns = options.excludePatterns ?? [];
|
|
81
|
+
}
|
|
71
82
|
|
|
72
83
|
async scan(pattern: string): Promise<string[]> {
|
|
73
84
|
const files = await walk(this.projectRoot);
|
|
74
85
|
const regex = toPatternRegex(pattern);
|
|
75
|
-
return files
|
|
86
|
+
return files
|
|
87
|
+
.filter((file) => regex.test(file))
|
|
88
|
+
.filter((file) => !this.excludePatterns.some((excludePattern) => excludePattern.test(file)))
|
|
89
|
+
.sort();
|
|
76
90
|
}
|
|
77
91
|
}
|