phasegate 0.160.21 → 0.161.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 +18 -0
- package/README.md +6 -1
- package/docs/guide/cli-reference.md +1 -1
- package/docs/guide/configuration.md +31 -1
- package/docs/guide/skills-overview.md +2 -0
- package/docs/principles/model-routing.md +1 -0
- package/docs/templates/personal/phasegate-local-config.json +4 -1
- package/docs/templates/project/phasegate.config.json +4 -1
- package/package.json +1 -1
- package/scripts/harness/agent-integration/domain/value-objects/write-target-scope.ts +54 -5
- package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +5 -1
- package/scripts/harness/config-foundation/domain/harness-config.ts +9 -0
- package/scripts/harness/config-foundation/domain/services/preset-resolution-service.ts +2 -0
- package/scripts/harness/config-foundation/domain/value-objects/project-config.ts +20 -5
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +25 -0
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +25 -0
- package/scripts/harness/installation/application/usecases/run-install.ts +16 -3
- package/scripts/harness/installation/application/usecases/run-reconcile.ts +48 -5
- package/scripts/harness/main.ts +31 -1
- package/scripts/harness/quick-mode/domain/value-objects/quick-mode-config.ts +6 -1
- package/scripts/harness/setup/skill-deployer.ts +47 -0
- package/scripts/harness/skill-quality/domain/services/skill-structure-validator.ts +21 -0
- package/scripts/harness/skill-quality/domain/value-objects/skill-structure.ts +2 -0
- package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +18 -5
- package/scripts/harness/validator-system/application/use-cases/run-l4-validators-usecase.ts +16 -3
- package/scripts/harness/validator-system/domain/ports/validator-config-port.ts +2 -0
- package/scripts/harness/validator-system/domain/services/validator-language-capability-service.ts +64 -0
- package/scripts/harness/validator-system/infrastructure/adapters/harness-config-validator-config-adapter.ts +7 -1
- package/skills/cascade-updater/SKILL.md +1 -0
- package/skills/codebase-mapper/SKILL.md +1 -0
- package/skills/codex-delegator/SKILL.md +1 -0
- package/skills/consistency-checker/SKILL.md +1 -0
- package/skills/doc-freshness-checker/SKILL.md +1 -0
- package/skills/domain-designer/SKILL.md +1 -0
- package/skills/engineering-perspective/SKILL.md +1 -0
- package/skills/environment-designer/SKILL.md +1 -0
- package/skills/implementation-planner/SKILL.md +1 -0
- package/skills/implementation-readiness-checker/SKILL.md +1 -0
- package/skills/it-test-designer/SKILL.md +1 -0
- package/skills/it-test-logic-designer/SKILL.md +1 -0
- package/skills/logical-designer/SKILL.md +1 -0
- package/skills/mock-designer/SKILL.md +1 -0
- package/skills/phasegate-config-doctor/SKILL.md +1 -0
- package/skills/phasegate-toolkit-guide/SKILL.md +1 -0
- package/skills/pointer-validator/SKILL.md +1 -0
- package/skills/product-architect/SKILL.md +1 -0
- package/skills/quick-implementor/SKILL.md +1 -0
- package/skills/scenario-test-designer/SKILL.md +1 -0
- package/skills/scenario-test-logic-designer/SKILL.md +1 -0
- package/skills/skill-creator/SKILL.md +1 -0
- package/skills/story-implementor/SKILL.md +1 -0
- package/skills/story-mapper/SKILL.md +1 -0
- package/skills/story-writer/SKILL.md +1 -0
- package/skills/test-coverage-checker/SKILL.md +1 -0
- package/skills/uiux-designer/SKILL.md +1 -0
- package/skills/unit-designer/SKILL.md +1 -0
- package/skills/unit-test-designer/SKILL.md +1 -0
- package/skills/unit-test-logic-designer/SKILL.md +1 -0
- package/templates/.claude/settings.json +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,9 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.161.0] - 2026-07-04
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **WI-220 — phase-gate bypass and quick-mode relaxation defects** — closes four defects reproduced against the published `0.160.21` package:
|
|
15
|
+
- Claude Code の PreToolUse `Bash` matcher に `phasegate hook pre-tool-use` を配線し、Bash 経由(`cat >`, heredoc, `tee`, `sed -i`, `cp`, `mv` 等)の書き込みもフェーズゲート・保護ファイル判定を通すようにした(従来は Codex のみ配線され Claude では素通りしていた)。
|
|
16
|
+
- `WriteTargetScope.fromPath` の `normalize()` で `.`/`..` セグメントを解決し、`.../__tests__/../domain/*` トラバーサルによる保護回避を塞いだ(本物のテストファイルの除外は維持)。
|
|
17
|
+
- `WriteTargetScope` のプレフィックス一致を大文字小文字非依存にし、大小非依存ファイルシステム(macOS/Windows)での `Scripts/harness/...` 経由の保護回避を塞いだ。
|
|
18
|
+
- `QuickModeConfig.isMaintained` がレイヤー名エントリ(`"L2"`)を配下バリデータ ID(`"L2-002"` 等)にマッチさせるようにし、`maintainedLayers: ["L1","L2"]` で L2 が全件 skip されていた不具合を修正(ID 完全一致も後方互換)。
|
|
19
|
+
|
|
20
|
+
## [0.160.22] - 2026-06-12
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **WI-212 — language-aware validator and skill metadata** — adds `project.languages` configuration, `init --language`, unsupported-language skips for TypeScript-only validators, bundled skill `languages` frontmatter, and a supported languages matrix for adoption planning.
|
|
25
|
+
- **WI-219 — configurable model delegation policy** — adds `modelRouting.delegation` so projects can keep the existing `delegate-sonnet` skill behavior or disable fixed Sonnet delegation while preserving local review guidance.
|
|
26
|
+
|
|
10
27
|
### Fixed
|
|
11
28
|
|
|
12
29
|
- **WI-217 — personal inception/product consistency** — makes L4-002 validate configured personal documentation roots, scopes L4-004 freshness to `paths.designDocs`, adds personal hook L4 backstop behavior for `.phasegate-local` docs, and lets `scaffold-wi` use custom IDs and personal inception roots.
|
|
30
|
+
- **WI-218 — WI description scope classification** — treats `docs/inception/**/WI-*/description.md` as a Phase 1 description artifact instead of Level 3, matching hook guidance and allowing description-only inception work without Full Mode.
|
|
13
31
|
|
|
14
32
|
## [0.160.16] - 2026-05-22
|
|
15
33
|
|
package/README.md
CHANGED
|
@@ -413,6 +413,9 @@ For selection guidance and config examples see [Preset Selection Guide](docs/gui
|
|
|
413
413
|
"preset": "standard",
|
|
414
414
|
"storyReflection": { "enabled": true }
|
|
415
415
|
},
|
|
416
|
+
"modelRouting": {
|
|
417
|
+
"delegation": "delegate-sonnet"
|
|
418
|
+
},
|
|
416
419
|
"protectedFiles": {
|
|
417
420
|
"exclude": ["tsconfig.json", "package.json"]
|
|
418
421
|
},
|
|
@@ -433,6 +436,8 @@ For selection guidance and config examples see [Preset Selection Guide](docs/gui
|
|
|
433
436
|
|
|
434
437
|
`paths` maps PhaseGate's design and reference documentation roots. Repositories that do not use `docs/` can point `designDocs`, `inceptionDocs`, `principlesDocs`, and `folderRulesDoc` at their own documentation layout; product-wide Level 1 artifacts are overridden separately with custom `phaseDependencies.gates[]`. <!-- @work-item-id WI-214 -->
|
|
435
438
|
|
|
439
|
+
`modelRouting.delegation` controls whether bundled skills may use the legacy `delegate-sonnet` workflow. Set it to `"none"` when the main agent session should do the work directly; skill deployment removes fixed model routing text and normal `delegate-sonnet` execution is refused. <!-- @work-item-id WI-219 -->
|
|
440
|
+
|
|
436
441
|
`baseline` opts in to the **Phase A-2 retrofit grandfather**: pre-existing files captured in `.phasegate/baseline.json` are exempted from `phase-gate` until they are structurally modified. Generate the snapshot with `npx phasegate baseline` before introducing the harness to an existing repository. Since v0.71.0 the `baseline.enabled` flag defaults to `true`, so simply running `npx phasegate baseline` after `init` is enough — no manual config edit needed. For a step-by-step retrofit walkthrough see [Retrofit Adoption Guide](docs/guide/retrofit-adoption.md).
|
|
437
442
|
|
|
438
443
|
---
|
|
@@ -560,7 +565,7 @@ README keeps only the entry points most users need. The full public/compatibilit
|
|
|
560
565
|
| `hook <pre-tool-use\|post-tool-use\|stop\|session-start\|user-prompt-submit>` | Run an agent hook (reads JSON from stdin; session-start/user-prompt-submit write JSON context) |
|
|
561
566
|
| `pre-commit` | Run L2 pre-commit validators on staged files |
|
|
562
567
|
| `bypass:audit --base <ref> [--head <ref>]` | Replay pre-commit validation over a push/CI range and require structured bypass evidence for gate failures |
|
|
563
|
-
| `delegate-sonnet [...args]` | Delegate task to Sonnet 4.6
|
|
568
|
+
| `delegate-sonnet [...args]` | Delegate task to Sonnet 4.6 when `modelRouting.delegation` allows it; `--help` and `--dry-run` remain available |
|
|
564
569
|
| `migrate work-items --dry-run` / `--apply` | Migrate legacy `ISSUE-XXX` / `H{NN}-{NN}` directories under `docs/inception/` to the unified `WI-XXX` layout (frontmatter `type` / `legacy_id` / `affects` injected). Sequential allocator skips numbers already used by existing WIs. See [CLI Reference -- Work Item Migration](docs/guide/cli-reference.md#work-item-migration). |
|
|
565
570
|
| `migrate --schema v3` | Upgrade `phasegate.config.json` to v3 schema by adding the `architecture` key (idempotent). |
|
|
566
571
|
|
|
@@ -22,7 +22,7 @@ Command names in this document are split into three surfaces:
|
|
|
22
22
|
|
|
23
23
|
| Command | Description |
|
|
24
24
|
|---|---|
|
|
25
|
-
| `init --name <name>` | Legacy-compatible bootstrap for new projects: deploy skills, generate config, and optionally add hooks/CI. Options: `--preset <full\|standard\|minimal\|custom>`, `--skills <core\|all>`, `--agent <claude\|codex\|both>`, `--workflow <standard\|strict>`, `--with-husky`, `--with-ci`, `--yes`. |
|
|
25
|
+
| `init --name <name>` | Legacy-compatible bootstrap for new projects: deploy skills, generate config, and optionally add hooks/CI. Options: `--preset <full\|standard\|minimal\|custom>`, `--skills <core\|all>`, `--agent <claude\|codex\|both>`, `--workflow <standard\|strict>`, `--language <language>`, `--with-husky`, `--with-ci`, `--yes`. `--language` writes `project.languages: ["<language>"]`; omitted language keeps the TypeScript-compatible default. <!-- @work-item-id WI-212 --> |
|
|
26
26
|
| `install --dry-run` / `--apply` | Idempotently merge PhaseGate into an existing project, preserve user content, add package scripts/devDependency, create selected `AGENTS.md` / `CLAUDE.md` managed sections, and write `.phasegate/manifest.json`. `--agent <claude\|codex\|both>`, `--skills <core\|all>`, and `--workflow <standard\|strict>` affect rendered agent context; `--force` replaces managed targets after backup. |
|
|
27
27
|
| `doctor` | Diagnose silent or partial installations and report repair hints (`--json`, `--strict`, `--agent <claude\|codex\|both>`, `--report-out <path>`). `--agent` defaults to `both`; single-agent scopes keep shared targets applicable and mark the other agent's findings as not applicable. `--report-out` writes exactly to the supplied path, not to `reporting.outputDir`. |
|
|
28
28
|
| `uninstall --dry-run` / `--apply` | Remove PhaseGate-managed files and managed blocks using `.phasegate/manifest.json`; `--force` handles managed conflict cases. |
|
|
@@ -24,7 +24,8 @@ The plan identifies target fields, managed artifacts, commands, validation, risk
|
|
|
24
24
|
{
|
|
25
25
|
"project": {
|
|
26
26
|
"name": "my-project",
|
|
27
|
-
"preset": "standard"
|
|
27
|
+
"preset": "standard", // "minimal" | "standard" | "strict"
|
|
28
|
+
"languages": ["typescript"]
|
|
28
29
|
},
|
|
29
30
|
"layers": {
|
|
30
31
|
"L1": { "enabled": true },
|
|
@@ -56,6 +57,9 @@ The plan identifies target fields, managed artifacts, commands, validation, risk
|
|
|
56
57
|
"default": "interactive", // "interactive" | "embedded-qa"
|
|
57
58
|
"perPhase": {}
|
|
58
59
|
},
|
|
60
|
+
"modelRouting": {
|
|
61
|
+
"delegation": "delegate-sonnet" // "delegate-sonnet" | "none"
|
|
62
|
+
},
|
|
59
63
|
"harnesses": {
|
|
60
64
|
"agentLessonCollection": false,
|
|
61
65
|
"cascadeUpdate": false,
|
|
@@ -121,6 +125,24 @@ The `strict` preset also enables:
|
|
|
121
125
|
|-----------|----------|--------------|----------------------------------------------------------------|
|
|
122
126
|
| `name` | `string` | -- | Project identifier. Used in reports and validation output. |
|
|
123
127
|
| `preset` | `string` | `"standard"` | One of `"minimal"`, `"standard"`, `"strict"`. Controls which layers and harnesses are active by default. Individual settings override the preset. |
|
|
128
|
+
| `languages` | `string[]` | `["typescript"]` | Project implementation languages used for validator dispatch and skill applicability. Unknown values are accepted as declarations; unsupported validator/language pairs are reported as skips with `unsupported-language` reasons. <!-- @work-item-id WI-212 --> |
|
|
129
|
+
|
|
130
|
+
#### Supported Languages
|
|
131
|
+
|
|
132
|
+
<!-- @work-item-id WI-212 -->
|
|
133
|
+
|
|
134
|
+
PhaseGate currently ships TypeScript-backed source, test, and coverage validators. Non-TypeScript declarations are supported as configuration metadata so architecture, phase-gate, documentation, pointer, and WI workflow checks can still run while TypeScript-only validators skip with a clear reason.
|
|
135
|
+
|
|
136
|
+
| Capability | TypeScript | Python / Go / Rust / other declared languages |
|
|
137
|
+
|---|---:|---:|
|
|
138
|
+
| `project.languages` config | Supported | Accepted |
|
|
139
|
+
| `init --language <lang>` bootstrap | Supported | Accepted |
|
|
140
|
+
| Architecture preset / phase-gate / WI workflow | Supported | Supported |
|
|
141
|
+
| Markdown document validators (`L4-002`, `L4-004`, `L4-005`, `L4-006`) | Supported | Supported |
|
|
142
|
+
| L3 performance AST validator (`L3-002`) | Supported | Skipped as `unsupported-language` |
|
|
143
|
+
| L3 coverage validator (`L3-003`) | Supported through Vitest coverage | Skipped as `unsupported-language` |
|
|
144
|
+
| L4 dead-code validator (`L4-003`) | Supported | Skipped as `unsupported-language` |
|
|
145
|
+
| Bundled skills | `languages: [typescript]` metadata present | Future language-specific skills can coexist by declaring their own `languages` metadata |
|
|
124
146
|
|
|
125
147
|
#### `layers`
|
|
126
148
|
|
|
@@ -454,6 +476,14 @@ Quick Mode with `relaxedGates: ["phase-gate"]` relaxes `storyReflection` as well
|
|
|
454
476
|
| `default` | `string` | `"interactive"` | `"interactive"` prompts the user at each decision point. `"embedded-qa"` embeds QA checks inline without prompting. |
|
|
455
477
|
| `perPhase` | `object` | `{}` | Override the planning mode for specific phases. Keys are phase names, values are `"interactive"` or `"embedded-qa"`. |
|
|
456
478
|
|
|
479
|
+
#### `modelRouting`
|
|
480
|
+
|
|
481
|
+
<!-- @work-item-id WI-219 -->
|
|
482
|
+
|
|
483
|
+
| Sub-field | Type | Default | Description |
|
|
484
|
+
|--------------|----------|----------------------|-------------|
|
|
485
|
+
| `delegation` | `string` | `"delegate-sonnet"` | `"delegate-sonnet"` preserves the legacy skill routing and `phasegate delegate-sonnet` behavior. `"none"` deploys bundled skills without fixed `model` / `review` frontmatter or `delegate-sonnet` body instructions, and blocks normal `phasegate delegate-sonnet` execution. Help and `--dry-run` remain available. |
|
|
486
|
+
|
|
457
487
|
#### `harnesses`
|
|
458
488
|
|
|
459
489
|
| Sub-field | Type | Default | Description |
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Phasegate provides 30 skills covering the full AIDLC (AI-Driven Development Life Cycle). `npx phasegate init` and project `npx phasegate install` deploy skill bodies to root `skills/` and expose them to enabled agents through `.claude/skills/` / `.codex/skills/` links. Personal install instead writes real local-only per-agent skill directories. <!-- @work-item-id WI-210 -->
|
|
4
4
|
|
|
5
|
+
Bundled `SKILL.md` files include `languages: [typescript]` frontmatter so PhaseGate can distinguish current TypeScript-oriented guidance from future language-specific skill variants. The metadata is advisory for applicability and does not prevent non-TypeScript projects from installing the catalog. <!-- @work-item-id WI-212 -->
|
|
6
|
+
|
|
5
7
|
## AIDLC Process — Skill Execution Order
|
|
6
8
|
|
|
7
9
|
The AIDLC follows a 3-level execution flow, each level producing design artifacts that gate the next.
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
- 実装、テスト、リポジトリ内の整合修正は Implementor が担当する。
|
|
18
18
|
- 委任結果は必ず親エージェントがレビューし、配置ルール、上位文書整合、スコープ逸脱を確認する。
|
|
19
19
|
- ユーザーまたは実行環境の指示でサブエージェント利用が制限されている場合は、その指示を優先する。
|
|
20
|
+
- `phasegate.config.json` の `modelRouting.delegation` はPhaseGate配備物に対する委任可否の正本である。`"none"` の場合、配備済みスキルは固定モデル委任を指示せず、`delegate-sonnet` の通常実行も拒否される。@work-item-id WI-219
|
|
20
21
|
|
|
21
22
|
## 委任してよい作業
|
|
22
23
|
|
package/package.json
CHANGED
|
@@ -75,11 +75,15 @@ export class WriteTargetScope {
|
|
|
75
75
|
|
|
76
76
|
const inceptionMatch = matchPrefix(normalizedPath, inceptionPath);
|
|
77
77
|
if (inceptionMatch !== null) {
|
|
78
|
-
const [unitId, secondSegment] = inceptionMatch;
|
|
78
|
+
const [unitId, secondSegment, thirdSegment] = inceptionMatch;
|
|
79
79
|
|
|
80
80
|
// 横断的 WI: docs/inception/_cross/{WI-XXX}/ → Level 3
|
|
81
81
|
if (unitId === "_cross") {
|
|
82
82
|
if (secondSegment !== undefined && WORK_ITEM_ID_PATTERN.test(secondSegment)) {
|
|
83
|
+
if (thirdSegment === "description.md") {
|
|
84
|
+
return WriteTargetScope.create({ level: 1 });
|
|
85
|
+
}
|
|
86
|
+
|
|
83
87
|
return WriteTargetScope.create({ level: 3, unitId, storyId: secondSegment });
|
|
84
88
|
}
|
|
85
89
|
|
|
@@ -88,6 +92,10 @@ export class WriteTargetScope {
|
|
|
88
92
|
|
|
89
93
|
// Unit 所有 WI / 既存 US パス: docs/inception/{unit}/{storyId}/ → Level 3
|
|
90
94
|
if (unitId !== undefined && secondSegment !== undefined && WORK_ITEM_ID_PATTERN.test(secondSegment)) {
|
|
95
|
+
if (secondSegment.startsWith("WI-") && thirdSegment === "description.md") {
|
|
96
|
+
return WriteTargetScope.create({ level: 1 });
|
|
97
|
+
}
|
|
98
|
+
|
|
91
99
|
return WriteTargetScope.create({ level: 3, unitId, storyId: secondSegment });
|
|
92
100
|
}
|
|
93
101
|
}
|
|
@@ -121,24 +129,65 @@ export class WriteTargetScope {
|
|
|
121
129
|
}
|
|
122
130
|
}
|
|
123
131
|
|
|
124
|
-
const normalize = (value: string): string =>
|
|
125
|
-
value
|
|
132
|
+
const normalize = (value: string): string => {
|
|
133
|
+
const cleaned = value
|
|
126
134
|
.replaceAll("\\", "/")
|
|
127
135
|
.replace(/^\.\/+/, "")
|
|
128
136
|
.replace(/\/+/g, "/")
|
|
129
137
|
.replace(/\/$/, "");
|
|
130
138
|
|
|
139
|
+
return resolveTraversal(cleaned);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// posix セマンティクスで `.`/`..` セグメントを解決する(プロジェクト相対パス想定)。
|
|
143
|
+
// これにより `a/b/../c` → `a/c`。フェーズゲート保護回避(P-2)対策として、
|
|
144
|
+
// __tests__ 除外や prefix 一致判定はこの解決後のパスに対して適用される。
|
|
145
|
+
const resolveTraversal = (value: string): string => {
|
|
146
|
+
if (value === "") {
|
|
147
|
+
return "";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const segments = value.split("/");
|
|
151
|
+
const resolved: string[] = [];
|
|
152
|
+
|
|
153
|
+
for (const segment of segments) {
|
|
154
|
+
if (segment === "" || segment === ".") {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (segment === "..") {
|
|
159
|
+
if (resolved.length > 0 && resolved[resolved.length - 1] !== "..") {
|
|
160
|
+
resolved.pop();
|
|
161
|
+
} else {
|
|
162
|
+
// プロジェクトルートより上への参照はそのまま残す(保護判定に影響させない)
|
|
163
|
+
resolved.push("..");
|
|
164
|
+
}
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
resolved.push(segment);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return resolved.join("/");
|
|
172
|
+
};
|
|
173
|
+
|
|
131
174
|
const matchPrefix = (targetPath: string, basePath: string): string[] | null => {
|
|
132
175
|
const normalizedBase = normalize(basePath);
|
|
133
176
|
|
|
134
|
-
|
|
177
|
+
// フェーズゲートはセキュリティ境界のため、大小非依存 FS(macOS/Windows)での
|
|
178
|
+
// 保護回避(P-3)を防ぐべく prefix 一致は大文字小文字を無視して判定する。
|
|
179
|
+
const lowerTarget = targetPath.toLowerCase();
|
|
180
|
+
const lowerBase = normalizedBase.toLowerCase();
|
|
181
|
+
|
|
182
|
+
if (lowerTarget === lowerBase) {
|
|
135
183
|
return [];
|
|
136
184
|
}
|
|
137
185
|
|
|
138
|
-
if (!
|
|
186
|
+
if (!lowerTarget.startsWith(`${lowerBase}/`)) {
|
|
139
187
|
return null;
|
|
140
188
|
}
|
|
141
189
|
|
|
190
|
+
// remainder(unitId 等の抽出結果)は元の大小を保持する。
|
|
142
191
|
const remainder = targetPath.slice(normalizedBase.length + 1);
|
|
143
192
|
return remainder === "" ? [] : remainder.split("/");
|
|
144
193
|
};
|
package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @unit config-foundation
|
|
4
4
|
* @work-item-id WI-133 / WI-156
|
|
5
5
|
* @work-item-id WI-217
|
|
6
|
+
* @work-item-id WI-212
|
|
6
7
|
*/
|
|
7
8
|
import type { HarnessConfigV2 } from '../../domain/harness-config.js';
|
|
8
9
|
|
|
@@ -33,7 +34,10 @@ export function toValidatorSystemConfig(resolvedConfig: HarnessConfigV2 | undefi
|
|
|
33
34
|
: l4Validators;
|
|
34
35
|
|
|
35
36
|
return {
|
|
36
|
-
project: {
|
|
37
|
+
project: {
|
|
38
|
+
preset: resolvedConfig.project.preset,
|
|
39
|
+
languages: resolvedConfig.project.languages ?? ['typescript'],
|
|
40
|
+
},
|
|
37
41
|
paths: {
|
|
38
42
|
designDocs: resolvedConfig.paths.designDocs,
|
|
39
43
|
inceptionDocs: resolvedConfig.paths.inceptionDocs,
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* @layer domain
|
|
3
3
|
* @unit config-foundation
|
|
4
4
|
* @work-item-id WI-012
|
|
5
|
+
* @work-item-id WI-212
|
|
6
|
+
* @work-item-id WI-219
|
|
5
7
|
*/
|
|
6
8
|
import { ConfigFoundationDomainError } from './errors/config-foundation-domain-error.js';
|
|
7
9
|
import { ConfigValidationError } from './errors/config-validation-error.js';
|
|
@@ -30,6 +32,7 @@ import type {
|
|
|
30
32
|
export type LayerId = 'L1' | 'L2' | 'L3' | 'L4';
|
|
31
33
|
export type PresetId = 'minimal' | 'standard' | 'strict';
|
|
32
34
|
export type PlanningModeValue = 'interactive' | 'embedded-qa' | 'manual';
|
|
35
|
+
export type ModelDelegationPolicy = 'delegate-sonnet' | 'none';
|
|
33
36
|
export type { PhaseDependenciesPresetId };
|
|
34
37
|
type DeepPartial<T> = {
|
|
35
38
|
[K in keyof T]?: T[K] extends Array<infer TItem>
|
|
@@ -43,6 +46,7 @@ export interface HarnessConfigSourceDocument {
|
|
|
43
46
|
project: {
|
|
44
47
|
name: string;
|
|
45
48
|
preset: PresetId;
|
|
49
|
+
languages?: string[];
|
|
46
50
|
};
|
|
47
51
|
layers: DeepPartial<HarnessConfigResolvedDocument['layers']>;
|
|
48
52
|
quickMode: Partial<HarnessConfigResolvedDocument['quickMode']>;
|
|
@@ -54,6 +58,7 @@ export interface HarnessConfigSourceDocument {
|
|
|
54
58
|
ci?: HarnessConfigResolvedDocument['ci'];
|
|
55
59
|
validate?: HarnessConfigResolvedDocument['validate'];
|
|
56
60
|
preCommit?: Partial<HarnessConfigResolvedDocument['preCommit']>;
|
|
61
|
+
modelRouting?: HarnessConfigResolvedDocument['modelRouting'];
|
|
57
62
|
phase2Extensions?: HarnessConfigResolvedDocument['phase2Extensions'];
|
|
58
63
|
architecture?: ArchitectureConfigSource;
|
|
59
64
|
}
|
|
@@ -62,6 +67,7 @@ export interface HarnessConfigResolvedDocument {
|
|
|
62
67
|
project: {
|
|
63
68
|
name: string;
|
|
64
69
|
preset: PresetId;
|
|
70
|
+
languages?: string[];
|
|
65
71
|
};
|
|
66
72
|
layers: {
|
|
67
73
|
L1: {
|
|
@@ -125,6 +131,9 @@ export interface HarnessConfigResolvedDocument {
|
|
|
125
131
|
preCommit?: {
|
|
126
132
|
implementationExtensions: string[];
|
|
127
133
|
};
|
|
134
|
+
modelRouting?: {
|
|
135
|
+
delegation: ModelDelegationPolicy;
|
|
136
|
+
};
|
|
128
137
|
phase2Extensions?: {
|
|
129
138
|
initialCreationExpirationRules?: Array<{
|
|
130
139
|
ruleId: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @layer domain
|
|
3
3
|
* @unit config-foundation
|
|
4
|
+
* @work-item-id WI-212
|
|
4
5
|
*/
|
|
5
6
|
import { ConfigFoundationDomainError } from '../errors/config-foundation-domain-error.js';
|
|
6
7
|
import type {
|
|
@@ -152,6 +153,7 @@ export class PresetResolutionService {
|
|
|
152
153
|
project: {
|
|
153
154
|
name: sourceDocument.project.name,
|
|
154
155
|
preset: sourceDocument.project.preset,
|
|
156
|
+
languages: sourceDocument.project.languages ?? ['typescript'],
|
|
155
157
|
},
|
|
156
158
|
layers: deepMerge(presetDefinition.layers, sourceDocument.layers, 'layers'),
|
|
157
159
|
quickMode: deepMerge(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @layer domain
|
|
3
3
|
* @unit config-foundation
|
|
4
|
+
* @work-item-id WI-212
|
|
4
5
|
*
|
|
5
6
|
* ProjectConfig値オブジェクト - プロジェクト名とPresetを保持する
|
|
6
7
|
*/
|
|
@@ -10,35 +11,49 @@ import { Preset } from './preset.js';
|
|
|
10
11
|
interface ProjectConfigProps {
|
|
11
12
|
readonly name: string;
|
|
12
13
|
readonly preset: Preset;
|
|
14
|
+
readonly languages?: readonly string[];
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export class ProjectConfig {
|
|
16
18
|
readonly name: string;
|
|
17
19
|
readonly preset: Preset;
|
|
20
|
+
readonly languages: readonly string[];
|
|
18
21
|
|
|
19
22
|
constructor(props: ProjectConfigProps) {
|
|
20
23
|
if (!props.name || props.name.trim() === '') {
|
|
21
24
|
throw new ConfigValidationError('ProjectConfig: name must not be empty');
|
|
22
25
|
}
|
|
26
|
+
const sourceLanguages = props.languages ?? ['typescript'];
|
|
27
|
+
if (sourceLanguages.length === 0) {
|
|
28
|
+
throw new ConfigValidationError('ProjectConfig: languages must not be empty');
|
|
29
|
+
}
|
|
30
|
+
const normalizedLanguages = sourceLanguages.map((language) => language.trim()).filter((language) => language.length > 0);
|
|
31
|
+
if (normalizedLanguages.length !== sourceLanguages.length) {
|
|
32
|
+
throw new ConfigValidationError('ProjectConfig: languages must contain non-empty strings');
|
|
33
|
+
}
|
|
23
34
|
this.name = props.name;
|
|
24
35
|
this.preset = props.preset;
|
|
36
|
+
this.languages = Object.freeze([...normalizedLanguages]);
|
|
25
37
|
Object.freeze(this);
|
|
26
38
|
}
|
|
27
39
|
|
|
28
|
-
static create(raw: { name: string; preset: string }): ProjectConfig {
|
|
40
|
+
static create(raw: { name: string; preset: string; languages?: readonly string[] }): ProjectConfig {
|
|
29
41
|
const preset = Preset.create(raw.preset);
|
|
30
|
-
return new ProjectConfig({ name: raw.name, preset });
|
|
42
|
+
return new ProjectConfig({ name: raw.name, preset, languages: raw.languages ?? ['typescript'] });
|
|
31
43
|
}
|
|
32
44
|
|
|
33
45
|
rename(name: string): ProjectConfig {
|
|
34
|
-
return new ProjectConfig({ name, preset: this.preset });
|
|
46
|
+
return new ProjectConfig({ name, preset: this.preset, languages: this.languages });
|
|
35
47
|
}
|
|
36
48
|
|
|
37
49
|
changePreset(preset: Preset): ProjectConfig {
|
|
38
|
-
return new ProjectConfig({ name: this.name, preset });
|
|
50
|
+
return new ProjectConfig({ name: this.name, preset, languages: this.languages });
|
|
39
51
|
}
|
|
40
52
|
|
|
41
53
|
equals(other: ProjectConfig): boolean {
|
|
42
|
-
return this.name === other.name
|
|
54
|
+
return this.name === other.name
|
|
55
|
+
&& this.preset.equals(other.preset)
|
|
56
|
+
&& this.languages.length === other.languages.length
|
|
57
|
+
&& this.languages.every((language, index) => language === other.languages[index]);
|
|
43
58
|
}
|
|
44
59
|
}
|
package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json
CHANGED
|
@@ -33,6 +33,15 @@
|
|
|
33
33
|
"strict"
|
|
34
34
|
]
|
|
35
35
|
},
|
|
36
|
+
"languages": {
|
|
37
|
+
"type": "array",
|
|
38
|
+
"items": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"minLength": 1
|
|
41
|
+
},
|
|
42
|
+
"minItems": 1,
|
|
43
|
+
"uniqueItems": true
|
|
44
|
+
},
|
|
36
45
|
"paths": {
|
|
37
46
|
"type": "object",
|
|
38
47
|
"additionalProperties": false,
|
|
@@ -474,6 +483,22 @@
|
|
|
474
483
|
}
|
|
475
484
|
}
|
|
476
485
|
},
|
|
486
|
+
"modelRouting": {
|
|
487
|
+
"type": "object",
|
|
488
|
+
"additionalProperties": false,
|
|
489
|
+
"required": [
|
|
490
|
+
"delegation"
|
|
491
|
+
],
|
|
492
|
+
"properties": {
|
|
493
|
+
"delegation": {
|
|
494
|
+
"type": "string",
|
|
495
|
+
"enum": [
|
|
496
|
+
"delegate-sonnet",
|
|
497
|
+
"none"
|
|
498
|
+
]
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
},
|
|
477
502
|
"protectedFiles": {
|
|
478
503
|
"type": "object",
|
|
479
504
|
"additionalProperties": false,
|
package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json
CHANGED
|
@@ -43,6 +43,15 @@
|
|
|
43
43
|
"strict"
|
|
44
44
|
]
|
|
45
45
|
},
|
|
46
|
+
"languages": {
|
|
47
|
+
"type": "array",
|
|
48
|
+
"items": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"minLength": 1
|
|
51
|
+
},
|
|
52
|
+
"minItems": 1,
|
|
53
|
+
"uniqueItems": true
|
|
54
|
+
},
|
|
46
55
|
"paths": {
|
|
47
56
|
"type": "object",
|
|
48
57
|
"additionalProperties": false,
|
|
@@ -492,6 +501,22 @@
|
|
|
492
501
|
}
|
|
493
502
|
}
|
|
494
503
|
},
|
|
504
|
+
"modelRouting": {
|
|
505
|
+
"type": "object",
|
|
506
|
+
"additionalProperties": false,
|
|
507
|
+
"required": [
|
|
508
|
+
"delegation"
|
|
509
|
+
],
|
|
510
|
+
"properties": {
|
|
511
|
+
"delegation": {
|
|
512
|
+
"type": "string",
|
|
513
|
+
"enum": [
|
|
514
|
+
"delegate-sonnet",
|
|
515
|
+
"none"
|
|
516
|
+
]
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
},
|
|
495
520
|
"protectedFiles": {
|
|
496
521
|
"type": "object",
|
|
497
522
|
"additionalProperties": false,
|
|
@@ -15,9 +15,11 @@
|
|
|
15
15
|
// @work-item-id WI-214
|
|
16
16
|
// @work-item-id WI-215
|
|
17
17
|
// @work-item-id WI-216
|
|
18
|
+
// @work-item-id WI-219
|
|
18
19
|
|
|
19
20
|
import { mkdir, readFile, writeFile, copyFile, chmod, access, lstat, readlink, symlink, readdir, rm } from "node:fs/promises";
|
|
20
21
|
import { dirname, join } from "node:path";
|
|
22
|
+
import { readModelDelegationPolicy, renderSkillForModelDelegation } from "../../../setup/skill-deployer.js";
|
|
21
23
|
import { DeploymentEntry } from "../../domain/deployment-entry.js";
|
|
22
24
|
import { DeploymentManifest } from "../../domain/deployment-manifest.js";
|
|
23
25
|
import type { ManagedBlockInput } from "../../domain/managed-block.js";
|
|
@@ -132,13 +134,24 @@ async function copyDirectory(src: string, dest: string): Promise<void> {
|
|
|
132
134
|
}
|
|
133
135
|
}
|
|
134
136
|
|
|
135
|
-
async function copySelectedSkillDirectories(
|
|
137
|
+
async function copySelectedSkillDirectories(
|
|
138
|
+
harnessRoot: string,
|
|
139
|
+
projectRoot: string,
|
|
140
|
+
targetRoot: string,
|
|
141
|
+
skills: readonly string[],
|
|
142
|
+
): Promise<void> {
|
|
136
143
|
await mkdir(targetRoot, { recursive: true });
|
|
144
|
+
const modelDelegationPolicy = await readModelDelegationPolicy(projectRoot);
|
|
137
145
|
for (const skill of skills) {
|
|
138
146
|
const source = join(harnessRoot, "skills", skill);
|
|
139
147
|
const target = join(targetRoot, skill);
|
|
140
148
|
await rm(target, { recursive: true, force: true });
|
|
141
149
|
await copyDirectory(source, target);
|
|
150
|
+
if (modelDelegationPolicy === "none") {
|
|
151
|
+
const skillPath = join(target, "SKILL.md");
|
|
152
|
+
const content = await readFile(skillPath, "utf8");
|
|
153
|
+
await writeFile(skillPath, renderSkillForModelDelegation(content, modelDelegationPolicy), "utf8");
|
|
154
|
+
}
|
|
142
155
|
}
|
|
143
156
|
}
|
|
144
157
|
|
|
@@ -445,7 +458,7 @@ export class RunInstallUseCase {
|
|
|
445
458
|
plan.push(item);
|
|
446
459
|
if (input.apply && item.changed && item.repairMode === "mechanical") {
|
|
447
460
|
try {
|
|
448
|
-
await copySelectedSkillDirectories(input.harnessRoot, join(input.projectRoot, skillPath), selectedPersonalSkills);
|
|
461
|
+
await copySelectedSkillDirectories(input.harnessRoot, input.projectRoot, join(input.projectRoot, skillPath), selectedPersonalSkills);
|
|
449
462
|
await writeFile(
|
|
450
463
|
join(input.projectRoot, skillPath, ".harness-version"),
|
|
451
464
|
`${JSON.stringify({ version: input.phasegateVersion, deployedAt: new Date().toISOString(), skillSet }, null, 2)}\n`,
|
|
@@ -862,7 +875,7 @@ export class RunInstallUseCase {
|
|
|
862
875
|
|
|
863
876
|
private async deploySharedSkills(input: RunInstallInput, skills: readonly string[], skillSet: SkillSet): Promise<void> {
|
|
864
877
|
const targetRoot = join(input.projectRoot, "skills");
|
|
865
|
-
await copySelectedSkillDirectories(input.harnessRoot, targetRoot, skills);
|
|
878
|
+
await copySelectedSkillDirectories(input.harnessRoot, input.projectRoot, targetRoot, skills);
|
|
866
879
|
await writeFile(
|
|
867
880
|
join(targetRoot, ".harness-version"),
|
|
868
881
|
`${JSON.stringify({ version: input.phasegateVersion, deployedAt: new Date().toISOString(), skillSet }, null, 2)}\n`,
|