refactor: rename ensemble to repertoire across codebase

This commit is contained in:
nrslib 2026-02-22 10:50:50 +09:00
parent a59ad1d808
commit c630d78806
56 changed files with 665 additions and 658 deletions

View File

@ -30,7 +30,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Internal ### Internal
- Comprehensive ensemble test suite: atomic-update, ensemble-paths, file-filter, github-ref-resolver, github-spec, list, lock-file, pack-summary, package-facet-resolution, remove-reference-check, remove, takt-pack-config, tar-parser, takt-pack-schema - Comprehensive ensemble test suite: atomic-update, ensemble-paths, file-filter, github-ref-resolver, github-spec, list, lock-file, pack-summary, package-facet-resolution, remove-reference-check, remove, takt-ensemble-config, tar-parser, takt-ensemble-schema
- Added `src/faceted-prompting/scope.ts` for @scope reference parsing, validation, and resolution - Added `src/faceted-prompting/scope.ts` for @scope reference parsing, validation, and resolution
- Added scope-ref tests for the faceted-prompting module - Added scope-ref tests for the faceted-prompting module
- Added `inputWait.ts` for shared input-wait state to suppress worker pool log noise - Added `inputWait.ts` for shared input-wait state to suppress worker pool log noise

View File

@ -10,10 +10,10 @@
### Added ### Added
- **Ensemble パッケージシステム** (`takt ensemble add/remove/list`): GitHub から外部 TAKT パッケージをインポート・管理 — `takt ensemble add github:{owner}/{repo}@{ref}` でパッケージを `~/.takt/ensemble/` にダウンロード。アトミックなインストール、バージョン互換チェック、ロックファイル生成、確認前のパッケージ内容サマリ表示に対応 - **Repertoire パッケージシステム** (`takt repertoire add/remove/list`): GitHub から外部 TAKT パッケージをインポート・管理 — `takt repertoire add github:{owner}/{repo}@{ref}` でパッケージを `~/.takt/repertoire/` にダウンロード。アトミックなインストール、バージョン互換チェック、ロックファイル生成、確認前のパッケージ内容サマリ表示に対応
- **@scope 参照**: piece YAML のファセット参照で `@{owner}/{repo}/{facet-name}` 構文をサポート — インストール済み ensemble パッケージのファセットを直接参照可能(例: `persona: @nrslib/takt-fullstack/expert-coder` - **@scope 参照**: piece YAML のファセット参照で `@{owner}/{repo}/{facet-name}` 構文をサポート — インストール済み repertoire パッケージのファセットを直接参照可能(例: `persona: @nrslib/takt-fullstack/expert-coder`
- **4層ファセット解決**: 3層project → user → builtinから4層package-local → project → user → builtinに拡張 — ensemble パッケージのピースは自パッケージ内のファセットを最優先で解決 - **4層ファセット解決**: 3層project → user → builtinから4層package-local → project → user → builtinに拡張 — repertoire パッケージのピースは自パッケージ内のファセットを最優先で解決
- **ピース選択に ensemble カテゴリ追加**: インストール済みの ensemble パッケージがピース選択 UI の「ensemble」カテゴリにサブカテゴリとして自動表示 - **ピース選択に repertoire カテゴリ追加**: インストール済みの repertoire パッケージがピース選択 UI の「repertoire」カテゴリにサブカテゴリとして自動表示
- **implement/fix インストラクションにビルドゲート追加**: `implement``fix` のビルトインインストラクションでテスト実行前にビルド(型チェック)の実行を必須化 - **implement/fix インストラクションにビルドゲート追加**: `implement``fix` のビルトインインストラクションでテスト実行前にビルド(型チェック)の実行を必須化
### Changed ### Changed
@ -22,15 +22,15 @@
### Fixed ### Fixed
- オーバーライドピースの検証が ensemble スコープを含むリゾルバー経由で実行されるよう修正 - オーバーライドピースの検証が repertoire スコープを含むリゾルバー経由で実行されるよう修正
- `takt export-cc` が新しい `builtins/{lang}/facets/` ディレクトリ構造からファセットを読み込むよう修正 - `takt export-cc` が新しい `builtins/{lang}/facets/` ディレクトリ構造からファセットを読み込むよう修正
- `confirm()` プロンプトがパイプ経由の stdin に対応(例: `echo "y" | takt ensemble add ...` - `confirm()` プロンプトがパイプ経由の stdin に対応(例: `echo "y" | takt repertoire add ...`
- イテレーション入力待ち中の `poll_tick` デバッグログ連続出力を抑制 - イテレーション入力待ち中の `poll_tick` デバッグログ連続出力を抑制
- ピースリゾルバーの `stat()` 呼び出しでアクセス不能エントリ時にクラッシュせずエラーハンドリング - ピースリゾルバーの `stat()` 呼び出しでアクセス不能エントリ時にクラッシュせずエラーハンドリング
### Internal ### Internal
- Ensemble テストスイート: atomic-update, ensemble-paths, file-filter, github-ref-resolver, github-spec, list, lock-file, pack-summary, package-facet-resolution, remove-reference-check, remove, takt-pack-config, tar-parser, takt-pack-schema - Repertoire テストスイート: atomic-update, repertoire-paths, file-filter, github-ref-resolver, github-spec, list, lock-file, pack-summary, package-facet-resolution, remove-reference-check, remove, takt-repertoire-config, tar-parser, takt-repertoire-schema
- `src/faceted-prompting/scope.ts` を追加(@scope 参照のパース・バリデーション・解決) - `src/faceted-prompting/scope.ts` を追加(@scope 参照のパース・バリデーション・解決)
- faceted-prompting モジュールの scope-ref テストを追加 - faceted-prompting モジュールの scope-ref テストを追加
- `inputWait.ts` を追加(ワーカープールのログノイズ抑制のための入力待ち状態共有) - `inputWait.ts` を追加(ワーカープールのログノイズ抑制のための入力待ち状態共有)

View File

@ -156,7 +156,7 @@ movements:
| `takt #N` | GitHub Issue をタスクとして実行します | | `takt #N` | GitHub Issue をタスクとして実行します |
| `takt switch` | 使う piece を切り替えます | | `takt switch` | 使う piece を切り替えます |
| `takt eject` | ビルトインの piece/persona をコピーしてカスタマイズできます | | `takt eject` | ビルトインの piece/persona をコピーしてカスタマイズできます |
| `takt ensemble add` | GitHub から ensemble パッケージをインストールします | | `takt repertoire add` | GitHub から repertoire パッケージをインストールします |
全コマンド・オプションは [CLI Reference](./cli-reference.ja.md) を参照してください。 全コマンド・オプションは [CLI Reference](./cli-reference.ja.md) を参照してください。
@ -225,7 +225,7 @@ takt --pipeline --task "バグを修正して" --auto-pr
├── config.yaml # プロバイダー、モデル、言語など ├── config.yaml # プロバイダー、モデル、言語など
├── pieces/ # ユーザー定義の piece ├── pieces/ # ユーザー定義の piece
├── facets/ # ユーザー定義のファセットpersonas, policies, knowledge など) ├── facets/ # ユーザー定義のファセットpersonas, policies, knowledge など)
└── ensemble/ # インストール済み ensemble パッケージ └── repertoire/ # インストール済み repertoire パッケージ
.takt/ # プロジェクトレベル .takt/ # プロジェクトレベル
├── config.yaml # プロジェクト設定 ├── config.yaml # プロジェクト設定
@ -261,7 +261,7 @@ await engine.run();
| [Agent Guide](./agents.md) | カスタムエージェントの設定 | | [Agent Guide](./agents.md) | カスタムエージェントの設定 |
| [Builtin Catalog](./builtin-catalog.ja.md) | ビルトイン piece・persona の一覧 | | [Builtin Catalog](./builtin-catalog.ja.md) | ビルトイン piece・persona の一覧 |
| [Faceted Prompting](./faceted-prompting.ja.md) | プロンプト設計の方法論 | | [Faceted Prompting](./faceted-prompting.ja.md) | プロンプト設計の方法論 |
| [Ensemble Packages](./ensemble.ja.md) | パッケージのインストール・共有 | | [Repertoire Packages](./repertoire.ja.md) | パッケージのインストール・共有 |
| [Task Management](./task-management.ja.md) | タスクの追加・実行・隔離 | | [Task Management](./task-management.ja.md) | タスクの追加・実行・隔離 |
| [CI/CD Integration](./ci-cd.ja.md) | GitHub Actions・パイプラインモード | | [CI/CD Integration](./ci-cd.ja.md) | GitHub Actions・パイプラインモード |
| [Changelog](../CHANGELOG.md) ([日本語](./CHANGELOG.ja.md)) | バージョン履歴 | | [Changelog](../CHANGELOG.md) ([日本語](./CHANGELOG.ja.md)) | バージョン履歴 |

View File

@ -300,25 +300,25 @@ takt metrics review
takt metrics review --since 7d takt metrics review --since 7d
``` ```
### takt ensemble ### takt repertoire
Ensemble パッケージGitHub 上の外部 TAKT パッケージ)を管理します。 Repertoire パッケージGitHub 上の外部 TAKT パッケージ)を管理します。
```bash ```bash
# GitHub からパッケージをインストール # GitHub からパッケージをインストール
takt ensemble add github:{owner}/{repo}@{ref} takt repertoire add github:{owner}/{repo}@{ref}
# デフォルトブランチからインストール # デフォルトブランチからインストール
takt ensemble add github:{owner}/{repo} takt repertoire add github:{owner}/{repo}
# インストール済みパッケージを一覧表示 # インストール済みパッケージを一覧表示
takt ensemble list takt repertoire list
# パッケージを削除 # パッケージを削除
takt ensemble remove @{owner}/{repo} takt repertoire remove @{owner}/{repo}
``` ```
インストールされたパッケージは `~/.takt/ensemble/` に保存され、ピース選択やファセット解決で利用可能になります。 インストールされたパッケージは `~/.takt/repertoire/` に保存され、ピース選択やファセット解決で利用可能になります。
### takt purge ### takt purge

View File

@ -300,25 +300,25 @@ takt metrics review
takt metrics review --since 7d takt metrics review --since 7d
``` ```
### takt ensemble ### takt repertoire
Manage ensemble packages (external TAKT packages from GitHub). Manage repertoire packages (external TAKT packages from GitHub).
```bash ```bash
# Install a package from GitHub # Install a package from GitHub
takt ensemble add github:{owner}/{repo}@{ref} takt repertoire add github:{owner}/{repo}@{ref}
# Install from default branch # Install from default branch
takt ensemble add github:{owner}/{repo} takt repertoire add github:{owner}/{repo}
# List installed packages # List installed packages
takt ensemble list takt repertoire list
# Remove a package # Remove a package
takt ensemble remove @{owner}/{repo} takt repertoire remove @{owner}/{repo}
``` ```
Installed packages are stored in `~/.takt/ensemble/` and their pieces/facets become available in piece selection and facet resolution. Installed packages are stored in `~/.takt/repertoire/` and their pieces/facets become available in piece selection and facet resolution.
### takt purge ### takt purge

View File

@ -1,34 +1,34 @@
# Ensemble パッケージ # Repertoire パッケージ
[English](./ensemble.md) [English](./repertoire.md)
Ensemble パッケージを使うと、GitHub リポジトリから TAKT のピースやファセットをインストール・共有できます。 Repertoire パッケージを使うと、GitHub リポジトリから TAKT のピースやファセットをインストール・共有できます。
## クイックスタート ## クイックスタート
```bash ```bash
# パッケージをインストール # パッケージをインストール
takt ensemble add github:nrslib/takt-fullstack takt repertoire add github:nrslib/takt-fullstack
# 特定バージョンを指定してインストール # 特定バージョンを指定してインストール
takt ensemble add github:nrslib/takt-fullstack@v1.0.0 takt repertoire add github:nrslib/takt-fullstack@v1.0.0
# インストール済みパッケージを一覧表示 # インストール済みパッケージを一覧表示
takt ensemble list takt repertoire list
# パッケージを削除 # パッケージを削除
takt ensemble remove @nrslib/takt-fullstack takt repertoire remove @nrslib/takt-fullstack
``` ```
[GitHub CLI](https://cli.github.com/) (`gh`) のインストールと認証が必要です。 [GitHub CLI](https://cli.github.com/) (`gh`) のインストールと認証が必要です。
## パッケージ構造 ## パッケージ構造
TAKT パッケージは `takt-package.yaml` マニフェストとコンテンツディレクトリを持つ GitHub リポジトリです。 TAKT パッケージは `takt-repertoire.yaml` マニフェストとコンテンツディレクトリを持つ GitHub リポジトリです。
``` ```
my-takt-package/ my-takt-repertoire/
takt-package.yaml # マニフェスト(.takt/takt-package.yaml でも可) takt-repertoire.yaml # マニフェスト(.takt/takt-repertoire.yaml でも可)
facets/ facets/
personas/ personas/
expert-coder.md expert-coder.md
@ -44,7 +44,7 @@ my-takt-package/
`facets/``pieces/` ディレクトリのみがインポートされます。その他のファイルは無視されます。 `facets/``pieces/` ディレクトリのみがインポートされます。その他のファイルは無視されます。
### takt-package.yaml ### takt-repertoire.yaml
マニフェストは、リポジトリ内のパッケージコンテンツの場所を TAKT に伝えます。 マニフェストは、リポジトリ内のパッケージコンテンツの場所を TAKT に伝えます。
@ -60,7 +60,7 @@ takt:
min_version: 0.22.0 min_version: 0.22.0
``` ```
マニフェストはリポジトリルート(`takt-package.yaml`)または `.takt/` 内(`.takt/takt-package.yaml`)に配置できます。`.takt/` が優先的に検索されます。 マニフェストはリポジトリルート(`takt-repertoire.yaml`)または `.takt/` 内(`.takt/takt-repertoire.yaml`)に配置できます。`.takt/` が優先的に検索されます。
| フィールド | 必須 | デフォルト | 説明 | | フィールド | 必須 | デフォルト | 説明 |
|-----------|------|-----------|------| |-----------|------|-----------|------|
@ -71,7 +71,7 @@ takt:
## インストール ## インストール
```bash ```bash
takt ensemble add github:{owner}/{repo}@{ref} takt repertoire add github:{owner}/{repo}@{ref}
``` ```
`@{ref}` は省略可能です。省略した場合、リポジトリのデフォルトブランチが使用されます。 `@{ref}` は省略可能です。省略した場合、リポジトリのデフォルトブランチが使用されます。
@ -82,10 +82,10 @@ takt ensemble add github:{owner}/{repo}@{ref}
1. `gh api` 経由で GitHub から tarball をダウンロード 1. `gh api` 経由で GitHub から tarball をダウンロード
2. `facets/``pieces/` のファイルのみを展開(`.md``.yaml``.yml` 2. `facets/``pieces/` のファイルのみを展開(`.md``.yaml``.yml`
3. `takt-package.yaml` マニフェストをバリデーション 3. `takt-repertoire.yaml` マニフェストをバリデーション
4. TAKT バージョン互換性チェック 4. TAKT バージョン互換性チェック
5. `~/.takt/ensemble/@{owner}/{repo}/` にファイルをコピー 5. `~/.takt/repertoire/@{owner}/{repo}/` にファイルをコピー
6. ロックファイル(`.takt-pack-lock.yaml`を生成ソース、ref、コミット SHA 6. ロックファイル(`.takt-repertoire-lock.yaml`を生成ソース、ref、コミット SHA
インストールはアトミックに行われます。途中で失敗しても中途半端な状態は残りません。 インストールはアトミックに行われます。途中で失敗しても中途半端な状態は残りません。
@ -102,7 +102,7 @@ takt ensemble add github:{owner}/{repo}@{ref}
### ピース ### ピース
インストールされたピースはピース選択 UI の「ensemble」カテゴリにパッケージごとのサブカテゴリとして表示されます。直接指定も可能です。 インストールされたピースはピース選択 UI の「repertoire」カテゴリにパッケージごとのサブカテゴリとして表示されます。直接指定も可能です。
```bash ```bash
takt --piece @nrslib/takt-fullstack/expert takt --piece @nrslib/takt-fullstack/expert
@ -122,9 +122,9 @@ movements:
### 4層ファセット解決 ### 4層ファセット解決
ensemble パッケージのピースが名前(@scope なし)でファセットを解決する場合、次の順序で検索されます。 repertoire パッケージのピースが名前(@scope なし)でファセットを解決する場合、次の順序で検索されます。
1. **パッケージローカル**: `~/.takt/ensemble/@{owner}/{repo}/facets/{type}/` 1. **パッケージローカル**: `~/.takt/repertoire/@{owner}/{repo}/facets/{type}/`
2. **プロジェクト**: `.takt/facets/{type}/` 2. **プロジェクト**: `.takt/facets/{type}/`
3. **ユーザー**: `~/.takt/facets/{type}/` 3. **ユーザー**: `~/.takt/facets/{type}/`
4. **ビルトイン**: `builtins/{lang}/facets/{type}/` 4. **ビルトイン**: `builtins/{lang}/facets/{type}/`
@ -136,7 +136,7 @@ ensemble パッケージのピースが名前(@scope なし)でファセッ
### 一覧表示 ### 一覧表示
```bash ```bash
takt ensemble list takt repertoire list
``` ```
インストール済みパッケージのスコープ、説明、ref、コミット SHA を表示します。 インストール済みパッケージのスコープ、説明、ref、コミット SHA を表示します。
@ -144,21 +144,21 @@ takt ensemble list
### 削除 ### 削除
```bash ```bash
takt ensemble remove @{owner}/{repo} takt repertoire remove @{owner}/{repo}
``` ```
削除前に、ユーザーやプロジェクトのピースがパッケージのファセットを参照していないかチェックし、影響がある場合は警告します。 削除前に、ユーザーやプロジェクトのピースがパッケージのファセットを参照していないかチェックし、影響がある場合は警告します。
## ディレクトリ構造 ## ディレクトリ構造
インストールされたパッケージは `~/.takt/ensemble/` に保存されます。 インストールされたパッケージは `~/.takt/repertoire/` に保存されます。
``` ```
~/.takt/ensemble/ ~/.takt/repertoire/
@nrslib/ @nrslib/
takt-fullstack/ takt-fullstack/
takt-package.yaml # マニフェストのコピー takt-repertoire.yaml # マニフェストのコピー
.takt-pack-lock.yaml # ロックファイルソース、ref、コミット .takt-repertoire-lock.yaml # ロックファイルソース、ref、コミット
facets/ facets/
personas/ personas/
policies/ policies/

View File

@ -1,34 +1,34 @@
# Ensemble Packages # Repertoire Packages
[Japanese](./ensemble.ja.md) [Japanese](./repertoire.ja.md)
Ensemble packages let you install and share TAKT pieces and facets from GitHub repositories. Repertoire packages let you install and share TAKT pieces and facets from GitHub repositories.
## Quick Start ## Quick Start
```bash ```bash
# Install a package # Install a package
takt ensemble add github:nrslib/takt-fullstack takt repertoire add github:nrslib/takt-fullstack
# Install a specific version # Install a specific version
takt ensemble add github:nrslib/takt-fullstack@v1.0.0 takt repertoire add github:nrslib/takt-fullstack@v1.0.0
# List installed packages # List installed packages
takt ensemble list takt repertoire list
# Remove a package # Remove a package
takt ensemble remove @nrslib/takt-fullstack takt repertoire remove @nrslib/takt-fullstack
``` ```
**Requirements:** [GitHub CLI](https://cli.github.com/) (`gh`) must be installed and authenticated. **Requirements:** [GitHub CLI](https://cli.github.com/) (`gh`) must be installed and authenticated.
## Package Structure ## Package Structure
A TAKT package is a GitHub repository with a `takt-package.yaml` manifest and content directories: A TAKT package is a GitHub repository with a `takt-repertoire.yaml` manifest and content directories:
``` ```
my-takt-package/ my-takt-repertoire/
takt-package.yaml # Package manifest (or .takt/takt-package.yaml) takt-repertoire.yaml # Package manifest (or .takt/takt-repertoire.yaml)
facets/ facets/
personas/ personas/
expert-coder.md expert-coder.md
@ -44,7 +44,7 @@ my-takt-package/
Only `facets/` and `pieces/` directories are imported. Other files are ignored. Only `facets/` and `pieces/` directories are imported. Other files are ignored.
### takt-package.yaml ### takt-repertoire.yaml
The manifest tells TAKT where to find the package content within the repository. The manifest tells TAKT where to find the package content within the repository.
@ -60,7 +60,7 @@ takt:
min_version: 0.22.0 min_version: 0.22.0
``` ```
The manifest can be placed at the repository root (`takt-package.yaml`) or inside `.takt/` (`.takt/takt-package.yaml`). The `.takt/` location is checked first. The manifest can be placed at the repository root (`takt-repertoire.yaml`) or inside `.takt/` (`.takt/takt-repertoire.yaml`). The `.takt/` location is checked first.
| Field | Required | Default | Description | | Field | Required | Default | Description |
|-------|----------|---------|-------------| |-------|----------|---------|-------------|
@ -71,7 +71,7 @@ The manifest can be placed at the repository root (`takt-package.yaml`) or insid
## Installation ## Installation
```bash ```bash
takt ensemble add github:{owner}/{repo}@{ref} takt repertoire add github:{owner}/{repo}@{ref}
``` ```
The `@{ref}` is optional. Without it, the repository's default branch is used. The `@{ref}` is optional. Without it, the repository's default branch is used.
@ -82,10 +82,10 @@ Before installing, TAKT displays a summary of the package contents (facet counts
1. Downloads the tarball from GitHub via `gh api` 1. Downloads the tarball from GitHub via `gh api`
2. Extracts only `facets/` and `pieces/` files (`.md`, `.yaml`, `.yml`) 2. Extracts only `facets/` and `pieces/` files (`.md`, `.yaml`, `.yml`)
3. Validates the `takt-package.yaml` manifest 3. Validates the `takt-repertoire.yaml` manifest
4. Checks TAKT version compatibility 4. Checks TAKT version compatibility
5. Copies files to `~/.takt/ensemble/@{owner}/{repo}/` 5. Copies files to `~/.takt/repertoire/@{owner}/{repo}/`
6. Generates a lock file (`.takt-pack-lock.yaml`) with source, ref, and commit SHA 6. Generates a lock file (`.takt-repertoire-lock.yaml`) with source, ref, and commit SHA
Installation is atomic — if it fails partway, no partial state is left behind. Installation is atomic — if it fails partway, no partial state is left behind.
@ -102,7 +102,7 @@ Installation is atomic — if it fails partway, no partial state is left behind.
### Pieces ### Pieces
Installed pieces appear in the piece selection UI under the "ensemble" category, organized by package. You can also specify them directly: Installed pieces appear in the piece selection UI under the "repertoire" category, organized by package. You can also specify them directly:
```bash ```bash
takt --piece @nrslib/takt-fullstack/expert takt --piece @nrslib/takt-fullstack/expert
@ -122,9 +122,9 @@ movements:
### 4-layer facet resolution ### 4-layer facet resolution
When a piece from an ensemble package resolves facets by name (without @scope), the resolution order is: When a piece from a repertoire package resolves facets by name (without @scope), the resolution order is:
1. **Package-local**: `~/.takt/ensemble/@{owner}/{repo}/facets/{type}/` 1. **Package-local**: `~/.takt/repertoire/@{owner}/{repo}/facets/{type}/`
2. **Project**: `.takt/facets/{type}/` 2. **Project**: `.takt/facets/{type}/`
3. **User**: `~/.takt/facets/{type}/` 3. **User**: `~/.takt/facets/{type}/`
4. **Builtin**: `builtins/{lang}/facets/{type}/` 4. **Builtin**: `builtins/{lang}/facets/{type}/`
@ -136,7 +136,7 @@ This means package pieces automatically find their own facets first, while still
### List ### List
```bash ```bash
takt ensemble list takt repertoire list
``` ```
Shows installed packages with their scope, description, ref, and commit SHA. Shows installed packages with their scope, description, ref, and commit SHA.
@ -144,21 +144,21 @@ Shows installed packages with their scope, description, ref, and commit SHA.
### Remove ### Remove
```bash ```bash
takt ensemble remove @{owner}/{repo} takt repertoire remove @{owner}/{repo}
``` ```
Before removing, TAKT checks if any user/project pieces reference the package's facets and warns about potential breakage. Before removing, TAKT checks if any user/project pieces reference the package's facets and warns about potential breakage.
## Directory Structure ## Directory Structure
Installed packages are stored under `~/.takt/ensemble/`: Installed packages are stored under `~/.takt/repertoire/`:
``` ```
~/.takt/ensemble/ ~/.takt/repertoire/
@nrslib/ @nrslib/
takt-fullstack/ takt-fullstack/
takt-package.yaml # Copy of the manifest takt-repertoire.yaml # Copy of the manifest
.takt-pack-lock.yaml # Lock file (source, ref, commit) .takt-repertoire-lock.yaml # Lock file (source, ref, commit)
facets/ facets/
personas/ personas/
policies/ policies/

View File

@ -124,13 +124,13 @@ describe('E2E: Piece selection branch coverage', () => {
expect(result.stdout).toContain('Piece completed'); expect(result.stdout).toContain('Piece completed');
}, 240_000); }, 240_000);
it('should execute when --piece is an ensemble @scope name (resolver hit branch)', () => { it('should execute when --piece is a repertoire @scope name (resolver hit branch)', () => {
const pkgRoot = join(isolatedEnv.taktDir, 'ensemble', '@nrslib', 'takt-packages'); const pkgRoot = join(isolatedEnv.taktDir, 'repertoire', '@nrslib', 'takt-ensembles');
writeAgent(pkgRoot); writeAgent(pkgRoot);
writeMinimalPiece(join(pkgRoot, 'pieces', 'critical-thinking.yaml')); writeMinimalPiece(join(pkgRoot, 'pieces', 'critical-thinking.yaml'));
const result = runTaskWithPiece({ const result = runTaskWithPiece({
piece: '@nrslib/takt-packages/critical-thinking', piece: '@nrslib/takt-ensembles/critical-thinking',
cwd: testRepo.path, cwd: testRepo.path,
env: isolatedEnv.env, env: isolatedEnv.env,
}); });
@ -142,13 +142,13 @@ describe('E2E: Piece selection branch coverage', () => {
it('should fail fast with message when --piece is unknown (resolver miss branch)', () => { it('should fail fast with message when --piece is unknown (resolver miss branch)', () => {
const result = runTaskWithPiece({ const result = runTaskWithPiece({
piece: '@nrslib/takt-packages/not-found', piece: '@nrslib/takt-ensembles/not-found',
cwd: testRepo.path, cwd: testRepo.path,
env: isolatedEnv.env, env: isolatedEnv.env,
}); });
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('Piece not found: @nrslib/takt-packages/not-found'); expect(result.stdout).toContain('Piece not found: @nrslib/takt-ensembles/not-found');
expect(result.stdout).toContain('Cancelled'); expect(result.stdout).toContain('Cancelled');
}, 240_000); }, 240_000);

View File

@ -39,9 +39,9 @@ function readYamlFile<T>(path: string): T {
return parseYaml(raw) as T; return parseYaml(raw) as T;
} }
const FIXTURE_REPO = 'nrslib/takt-pack-fixture'; const FIXTURE_REPO = 'nrslib/takt-ensemble-fixture';
const FIXTURE_REPO_SUBDIR = 'nrslib/takt-pack-fixture-subdir'; const FIXTURE_REPO_SUBDIR = 'nrslib/takt-ensemble-fixture-subdir';
const FIXTURE_REPO_FACETS_ONLY = 'nrslib/takt-pack-fixture-facets-only'; const FIXTURE_REPO_FACETS_ONLY = 'nrslib/takt-ensemble-fixture-facets-only';
const MISSING_MANIFEST_REPO = 'nrslib/takt'; const MISSING_MANIFEST_REPO = 'nrslib/takt';
const FIXTURE_REF = 'v1.0.0'; const FIXTURE_REF = 'v1.0.0';
@ -50,7 +50,7 @@ const canUseSubdirRepo = canAccessRepo(FIXTURE_REPO_SUBDIR) && canAccessRepoRef(
const canUseFacetsOnlyRepo = canAccessRepo(FIXTURE_REPO_FACETS_ONLY) && canAccessRepoRef(FIXTURE_REPO_FACETS_ONLY, FIXTURE_REF); const canUseFacetsOnlyRepo = canAccessRepo(FIXTURE_REPO_FACETS_ONLY) && canAccessRepoRef(FIXTURE_REPO_FACETS_ONLY, FIXTURE_REF);
const canUseMissingManifestRepo = canAccessRepo(MISSING_MANIFEST_REPO); const canUseMissingManifestRepo = canAccessRepo(MISSING_MANIFEST_REPO);
describe('E2E: takt ensemble (real GitHub fixtures)', () => { describe('E2E: takt repertoire (real GitHub fixtures)', () => {
let isolatedEnv: IsolatedEnv; let isolatedEnv: IsolatedEnv;
beforeEach(() => { beforeEach(() => {
@ -67,7 +67,7 @@ describe('E2E: takt ensemble (real GitHub fixtures)', () => {
it.skipIf(!canUseFixtureRepo)('should install fixture package from GitHub and create lock file', () => { it.skipIf(!canUseFixtureRepo)('should install fixture package from GitHub and create lock file', () => {
const result = runTakt({ const result = runTakt({
args: ['ensemble', 'add', `github:${FIXTURE_REPO}@${FIXTURE_REF}`], args: ['repertoire', 'add', `github:${FIXTURE_REPO}@${FIXTURE_REF}`],
cwd: process.cwd(), cwd: process.cwd(),
env: isolatedEnv.env, env: isolatedEnv.env,
input: 'y\n', input: 'y\n',
@ -78,14 +78,14 @@ describe('E2E: takt ensemble (real GitHub fixtures)', () => {
expect(result.stdout).toContain(`📦 ${FIXTURE_REPO} @${FIXTURE_REF}`); expect(result.stdout).toContain(`📦 ${FIXTURE_REPO} @${FIXTURE_REF}`);
expect(result.stdout).toContain('インストールしました'); expect(result.stdout).toContain('インストールしました');
const packageDir = join(isolatedEnv.taktDir, 'ensemble', '@nrslib', 'takt-pack-fixture'); const packageDir = join(isolatedEnv.taktDir, 'repertoire', '@nrslib', 'takt-ensemble-fixture');
expect(existsSync(join(packageDir, 'takt-package.yaml'))).toBe(true); expect(existsSync(join(packageDir, 'takt-repertoire.yaml'))).toBe(true);
expect(existsSync(join(packageDir, '.takt-pack-lock.yaml'))).toBe(true); expect(existsSync(join(packageDir, '.takt-repertoire-lock.yaml'))).toBe(true);
expect(existsSync(join(packageDir, 'facets'))).toBe(true); expect(existsSync(join(packageDir, 'facets'))).toBe(true);
expect(existsSync(join(packageDir, 'pieces'))).toBe(true); expect(existsSync(join(packageDir, 'pieces'))).toBe(true);
const lock = readYamlFile<LockFile>(join(packageDir, '.takt-pack-lock.yaml')); const lock = readYamlFile<LockFile>(join(packageDir, '.takt-repertoire-lock.yaml'));
expect(lock.source).toBe('github:nrslib/takt-pack-fixture'); expect(lock.source).toBe('github:nrslib/takt-ensemble-fixture');
expect(lock.ref).toBe(FIXTURE_REF); expect(lock.ref).toBe(FIXTURE_REF);
expect(lock.commit).toBeTypeOf('string'); expect(lock.commit).toBeTypeOf('string');
expect(lock.commit!.length).toBeGreaterThanOrEqual(7); expect(lock.commit!.length).toBeGreaterThanOrEqual(7);
@ -94,7 +94,7 @@ describe('E2E: takt ensemble (real GitHub fixtures)', () => {
it.skipIf(!canUseFixtureRepo)('should list installed package after add', () => { it.skipIf(!canUseFixtureRepo)('should list installed package after add', () => {
const addResult = runTakt({ const addResult = runTakt({
args: ['ensemble', 'add', `github:${FIXTURE_REPO}@${FIXTURE_REF}`], args: ['repertoire', 'add', `github:${FIXTURE_REPO}@${FIXTURE_REF}`],
cwd: process.cwd(), cwd: process.cwd(),
env: isolatedEnv.env, env: isolatedEnv.env,
input: 'y\n', input: 'y\n',
@ -103,19 +103,19 @@ describe('E2E: takt ensemble (real GitHub fixtures)', () => {
expect(addResult.exitCode).toBe(0); expect(addResult.exitCode).toBe(0);
const listResult = runTakt({ const listResult = runTakt({
args: ['ensemble', 'list'], args: ['repertoire', 'list'],
cwd: process.cwd(), cwd: process.cwd(),
env: isolatedEnv.env, env: isolatedEnv.env,
timeout: 120_000, timeout: 120_000,
}); });
expect(listResult.exitCode).toBe(0); expect(listResult.exitCode).toBe(0);
expect(listResult.stdout).toContain('@nrslib/takt-pack-fixture'); expect(listResult.stdout).toContain('@nrslib/takt-ensemble-fixture');
}, 240_000); }, 240_000);
it.skipIf(!canUseFixtureRepo)('should remove installed package with confirmation', () => { it.skipIf(!canUseFixtureRepo)('should remove installed package with confirmation', () => {
const addResult = runTakt({ const addResult = runTakt({
args: ['ensemble', 'add', `github:${FIXTURE_REPO}@${FIXTURE_REF}`], args: ['repertoire', 'add', `github:${FIXTURE_REPO}@${FIXTURE_REF}`],
cwd: process.cwd(), cwd: process.cwd(),
env: isolatedEnv.env, env: isolatedEnv.env,
input: 'y\n', input: 'y\n',
@ -124,7 +124,7 @@ describe('E2E: takt ensemble (real GitHub fixtures)', () => {
expect(addResult.exitCode).toBe(0); expect(addResult.exitCode).toBe(0);
const removeResult = runTakt({ const removeResult = runTakt({
args: ['ensemble', 'remove', '@nrslib/takt-pack-fixture'], args: ['repertoire', 'remove', '@nrslib/takt-ensemble-fixture'],
cwd: process.cwd(), cwd: process.cwd(),
env: isolatedEnv.env, env: isolatedEnv.env,
input: 'y\n', input: 'y\n',
@ -132,13 +132,13 @@ describe('E2E: takt ensemble (real GitHub fixtures)', () => {
}); });
expect(removeResult.exitCode).toBe(0); expect(removeResult.exitCode).toBe(0);
const packageDir = join(isolatedEnv.taktDir, 'ensemble', '@nrslib', 'takt-pack-fixture'); const packageDir = join(isolatedEnv.taktDir, 'repertoire', '@nrslib', 'takt-ensemble-fixture');
expect(existsSync(packageDir)).toBe(false); expect(existsSync(packageDir)).toBe(false);
}, 240_000); }, 240_000);
it.skipIf(!canUseFixtureRepo)('should cancel installation when user answers N', () => { it.skipIf(!canUseFixtureRepo)('should cancel installation when user answers N', () => {
const result = runTakt({ const result = runTakt({
args: ['ensemble', 'add', `github:${FIXTURE_REPO}@${FIXTURE_REF}`], args: ['repertoire', 'add', `github:${FIXTURE_REPO}@${FIXTURE_REF}`],
cwd: process.cwd(), cwd: process.cwd(),
env: isolatedEnv.env, env: isolatedEnv.env,
input: 'n\n', input: 'n\n',
@ -148,13 +148,13 @@ describe('E2E: takt ensemble (real GitHub fixtures)', () => {
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('キャンセルしました'); expect(result.stdout).toContain('キャンセルしました');
const packageDir = join(isolatedEnv.taktDir, 'ensemble', '@nrslib', 'takt-pack-fixture'); const packageDir = join(isolatedEnv.taktDir, 'repertoire', '@nrslib', 'takt-ensemble-fixture');
expect(existsSync(packageDir)).toBe(false); expect(existsSync(packageDir)).toBe(false);
}, 240_000); }, 240_000);
it.skipIf(!canUseSubdirRepo)('should install subdir fixture package', () => { it.skipIf(!canUseSubdirRepo)('should install subdir fixture package', () => {
const result = runTakt({ const result = runTakt({
args: ['ensemble', 'add', `github:${FIXTURE_REPO_SUBDIR}@${FIXTURE_REF}`], args: ['repertoire', 'add', `github:${FIXTURE_REPO_SUBDIR}@${FIXTURE_REF}`],
cwd: process.cwd(), cwd: process.cwd(),
env: isolatedEnv.env, env: isolatedEnv.env,
input: 'y\n', input: 'y\n',
@ -162,15 +162,15 @@ describe('E2E: takt ensemble (real GitHub fixtures)', () => {
}); });
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
const packageDir = join(isolatedEnv.taktDir, 'ensemble', '@nrslib', 'takt-pack-fixture-subdir'); const packageDir = join(isolatedEnv.taktDir, 'repertoire', '@nrslib', 'takt-ensemble-fixture-subdir');
expect(existsSync(join(packageDir, 'takt-package.yaml'))).toBe(true); expect(existsSync(join(packageDir, 'takt-repertoire.yaml'))).toBe(true);
expect(existsSync(join(packageDir, '.takt-pack-lock.yaml'))).toBe(true); expect(existsSync(join(packageDir, '.takt-repertoire-lock.yaml'))).toBe(true);
expect(existsSync(join(packageDir, 'facets')) || existsSync(join(packageDir, 'pieces'))).toBe(true); expect(existsSync(join(packageDir, 'facets')) || existsSync(join(packageDir, 'pieces'))).toBe(true);
}, 240_000); }, 240_000);
it.skipIf(!canUseFacetsOnlyRepo)('should install facets-only fixture package without requiring pieces directory', () => { it.skipIf(!canUseFacetsOnlyRepo)('should install facets-only fixture package without requiring pieces directory', () => {
const result = runTakt({ const result = runTakt({
args: ['ensemble', 'add', `github:${FIXTURE_REPO_FACETS_ONLY}@${FIXTURE_REF}`], args: ['repertoire', 'add', `github:${FIXTURE_REPO_FACETS_ONLY}@${FIXTURE_REF}`],
cwd: process.cwd(), cwd: process.cwd(),
env: isolatedEnv.env, env: isolatedEnv.env,
input: 'y\n', input: 'y\n',
@ -178,14 +178,14 @@ describe('E2E: takt ensemble (real GitHub fixtures)', () => {
}); });
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
const packageDir = join(isolatedEnv.taktDir, 'ensemble', '@nrslib', 'takt-pack-fixture-facets-only'); const packageDir = join(isolatedEnv.taktDir, 'repertoire', '@nrslib', 'takt-ensemble-fixture-facets-only');
expect(existsSync(join(packageDir, 'facets'))).toBe(true); expect(existsSync(join(packageDir, 'facets'))).toBe(true);
expect(existsSync(join(packageDir, 'pieces'))).toBe(false); expect(existsSync(join(packageDir, 'pieces'))).toBe(false);
}, 240_000); }, 240_000);
it.skipIf(!canUseMissingManifestRepo)('should fail when repository has no takt-package.yaml', () => { it.skipIf(!canUseMissingManifestRepo)('should fail when repository has no takt-repertoire.yaml', () => {
const result = runTakt({ const result = runTakt({
args: ['ensemble', 'add', `github:${MISSING_MANIFEST_REPO}`], args: ['repertoire', 'add', `github:${MISSING_MANIFEST_REPO}`],
cwd: process.cwd(), cwd: process.cwd(),
env: isolatedEnv.env, env: isolatedEnv.env,
input: 'y\n', input: 'y\n',
@ -193,6 +193,6 @@ describe('E2E: takt ensemble (real GitHub fixtures)', () => {
}); });
expect(result.exitCode).not.toBe(0); expect(result.exitCode).not.toBe(0);
expect(result.stdout).toContain('takt-package.yaml not found'); expect(result.stdout).toContain('takt-repertoire.yaml not found');
}, 240_000); }, 240_000);
}); });

View File

@ -1,83 +1,83 @@
/** /**
* E2E tests for `takt ensemble` subcommands. * E2E tests for `takt repertoire` subcommands.
* *
* All tests are marked as `it.todo()` because the `takt ensemble` command * All tests are marked as `it.todo()` because the `takt repertoire` command
* is not yet implemented. These serve as the specification skeleton; * is not yet implemented. These serve as the specification skeleton;
* fill in the callbacks when the implementation lands. * fill in the callbacks when the implementation lands.
* *
* GitHub fixture repos used: * GitHub fixture repos used:
* - github:nrslib/takt-pack-fixture (standard: facets/ + pieces/) * - github:nrslib/takt-ensemble-fixture (standard: facets/ + pieces/)
* - github:nrslib/takt-pack-fixture-subdir (path field specified) * - github:nrslib/takt-ensemble-fixture-subdir (path field specified)
* - github:nrslib/takt-pack-fixture-facets-only (facets only, no pieces/) * - github:nrslib/takt-ensemble-fixture-facets-only (facets only, no pieces/)
* *
*/ */
import { describe, it } from 'vitest'; import { describe, it } from 'vitest';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// E2E: takt ensemble add — 正常系 // E2E: takt repertoire add — 正常系
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
describe('E2E: takt ensemble add (正常系)', () => { describe('E2E: takt repertoire add (正常系)', () => {
// E1: 標準パッケージのインポート // E1: 標準パッケージのインポート
// Given: 空の isolatedEnv // Given: 空の isolatedEnv
// When: takt ensemble add github:nrslib/takt-pack-fixture@v1.0.0、y 入力 // When: takt repertoire add github:nrslib/takt-ensemble-fixture@v1.0.0、y 入力
// Then: {taktDir}/ensemble/@nrslib/takt-pack-fixture/ に takt-pack.yaml, // Then: {taktDir}/repertoire/@nrslib/takt-ensemble-fixture/ に takt-repertoire.yaml,
// .takt-pack-lock.yaml, facets/, pieces/ が存在する // .takt-repertoire-lock.yaml, facets/, pieces/ が存在する
it.todo('should install standard package and verify directory structure'); it.todo('should install standard package and verify directory structure');
// E2: lock ファイルのフィールド確認 // E2: lock ファイルのフィールド確認
// Given: E1 完了後 // Given: E1 完了後
// When: .takt-pack-lock.yaml を読む // When: .takt-repertoire-lock.yaml を読む
// Then: source, ref, commit, imported_at フィールドがすべて存在する // Then: source, ref, commit, imported_at フィールドがすべて存在する
it.todo('should generate .takt-pack-lock.yaml with source, ref, commit, imported_at'); it.todo('should generate .takt-repertoire-lock.yaml with source, ref, commit, imported_at');
// E3: サブディレクトリ型パッケージのインポート // E3: サブディレクトリ型パッケージのインポート
// Given: 空の isolatedEnv // Given: 空の isolatedEnv
// When: takt ensemble add github:nrslib/takt-pack-fixture-subdir@v1.0.0、y 入力 // When: takt repertoire add github:nrslib/takt-ensemble-fixture-subdir@v1.0.0、y 入力
// Then: path フィールドで指定されたサブディレクトリ配下のファイルのみコピーされる // Then: path フィールドで指定されたサブディレクトリ配下のファイルのみコピーされる
it.todo('should install subdir-type package and copy only path-specified files'); it.todo('should install subdir-type package and copy only path-specified files');
// E4: ファセットのみパッケージのインポート // E4: ファセットのみパッケージのインポート
// Given: 空の isolatedEnv // Given: 空の isolatedEnv
// When: takt ensemble add github:nrslib/takt-pack-fixture-facets-only@v1.0.0、y 入力 // When: takt repertoire add github:nrslib/takt-ensemble-fixture-facets-only@v1.0.0、y 入力
// Then: facets/ は存在し、pieces/ ディレクトリは存在しない // Then: facets/ は存在し、pieces/ ディレクトリは存在しない
it.todo('should install facets-only package without creating pieces/ directory'); it.todo('should install facets-only package without creating pieces/ directory');
// E4b: コミットSHA指定 // E4b: コミットSHA指定
// Given: 空の isolatedEnv // Given: 空の isolatedEnv
// When: takt ensemble add github:nrslib/takt-pack-fixture@{sha}、y 入力 // When: takt repertoire add github:nrslib/takt-ensemble-fixture@{sha}、y 入力
// Then: .takt-pack-lock.yaml の commit フィールドが指定した SHA と一致する // Then: .takt-repertoire-lock.yaml の commit フィールドが指定した SHA と一致する
it.todo('should populate lock file commit field with the specified commit SHA when installing by SHA'); it.todo('should populate lock file commit field with the specified commit SHA when installing by SHA');
// E5: インストール前サマリー表示 // E5: インストール前サマリー表示
// Given: 空の isolatedEnv // Given: 空の isolatedEnv
// When: takt ensemble add github:nrslib/takt-pack-fixture@v1.0.0、N 入力(確認でキャンセル) // When: takt repertoire add github:nrslib/takt-ensemble-fixture@v1.0.0、N 入力(確認でキャンセル)
// Then: stdout に "📦 nrslib/takt-pack-fixture", "faceted:", "pieces:" が含まれる // Then: stdout に "📦 nrslib/takt-ensemble-fixture", "faceted:", "pieces:" が含まれる
it.todo('should display pre-install summary with package name, faceted count, and pieces list'); it.todo('should display pre-install summary with package name, faceted count, and pieces list');
// E6: 権限警告表示edit: true ピース) // E6: 権限警告表示edit: true ピース)
// Given: edit: true を含むパッケージ // Given: edit: true を含むパッケージ
// When: ensemble add、N 入力 // When: repertoire add、N 入力
// Then: stdout に ⚠ が含まれる // Then: stdout に ⚠ が含まれる
it.todo('should display warning symbol when package contains piece with edit: true'); it.todo('should display warning symbol when package contains piece with edit: true');
// E7: ユーザー確認 N で中断 // E7: ユーザー確認 N で中断
// Given: 空の isolatedEnv // Given: 空の isolatedEnv
// When: ensemble add、N 入力 // When: repertoire add、N 入力
// Then: インストールディレクトリが存在しない。exit code 0 // Then: インストールディレクトリが存在しない。exit code 0
it.todo('should abort installation when user answers N to confirmation prompt'); it.todo('should abort installation when user answers N to confirmation prompt');
}); });
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// E2E: takt ensemble add — 上書きシナリオ // E2E: takt repertoire add — 上書きシナリオ
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
describe('E2E: takt ensemble add (上書きシナリオ)', () => { describe('E2E: takt repertoire add (上書きシナリオ)', () => {
// E8: 既存パッケージの上書き警告表示 // E8: 既存パッケージの上書き警告表示
// Given: 1回目インストール済み // Given: 1回目インストール済み
// When: 2回目 ensemble add // When: 2回目 repertoire add
// Then: stdout に "⚠ パッケージ @nrslib/takt-pack-fixture は既にインストールされています" が含まれる // Then: stdout に "⚠ パッケージ @nrslib/takt-ensemble-fixture は既にインストールされています" が含まれる
it.todo('should display already-installed warning on second add'); it.todo('should display already-installed warning on second add');
// E9: 上書き y で原子的更新 // E9: 上書き y で原子的更新
@ -93,80 +93,80 @@ describe('E2E: takt ensemble add (上書きシナリオ)', () => {
it.todo('should keep existing package when user answers N to overwrite prompt'); it.todo('should keep existing package when user answers N to overwrite prompt');
// E11: 前回異常終了残留物(.tmp/)クリーンアップ // E11: 前回異常終了残留物(.tmp/)クリーンアップ
// Given: {ensembleDir}/@nrslib/takt-pack-fixture.tmp/ が既に存在する状態 // Given: {repertoireDir}/@nrslib/takt-ensemble-fixture.tmp/ が既に存在する状態
// When: ensemble add、y 入力 // When: repertoire add、y 入力
// Then: インストールが正常完了する。exit code 0 // Then: インストールが正常完了する。exit code 0
it.todo('should clean up leftover .tmp/ directory from previous failed installation'); it.todo('should clean up leftover .tmp/ directory from previous failed installation');
// E12: 前回異常終了残留物(.bak/)クリーンアップ // E12: 前回異常終了残留物(.bak/)クリーンアップ
// Given: {ensembleDir}/@nrslib/takt-pack-fixture.bak/ が既に存在する状態 // Given: {repertoireDir}/@nrslib/takt-ensemble-fixture.bak/ が既に存在する状態
// When: ensemble add、y 入力 // When: repertoire add、y 入力
// Then: インストールが正常完了する。exit code 0 // Then: インストールが正常完了する。exit code 0
it.todo('should clean up leftover .bak/ directory from previous failed installation'); it.todo('should clean up leftover .bak/ directory from previous failed installation');
}); });
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// E2E: takt ensemble add — バリデーション・エラー系 // E2E: takt repertoire add — バリデーション・エラー系
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
describe('E2E: takt ensemble add (バリデーション・エラー系)', () => { describe('E2E: takt repertoire add (バリデーション・エラー系)', () => {
// E13: takt-pack.yaml 不在リポジトリ // E13: takt-repertoire.yaml 不在リポジトリ
// Given: takt-pack.yaml のないリポジトリを指定 // Given: takt-repertoire.yaml のないリポジトリを指定
// When: ensemble add // When: repertoire add
// Then: exit code 非0。エラーメッセージ表示 // Then: exit code 非0。エラーメッセージ表示
it.todo('should fail with error when repository has no takt-pack.yaml'); it.todo('should fail with error when repository has no takt-repertoire.yaml');
// E14: path に絶対パス(/foo // E14: path に絶対パス(/foo
// Given: path: /foo の takt-pack.yaml // Given: path: /foo の takt-repertoire.yaml
// When: ensemble add // When: repertoire add
// Then: exit code 非0 // Then: exit code 非0
it.todo('should reject takt-pack.yaml with absolute path in path field (/foo)'); it.todo('should reject takt-repertoire.yaml with absolute path in path field (/foo)');
// E15: path に .. によるリポジトリ外参照 // E15: path に .. によるリポジトリ外参照
// Given: path: ../outside の takt-pack.yaml // Given: path: ../outside の takt-repertoire.yaml
// When: ensemble add // When: repertoire add
// Then: exit code 非0 // Then: exit code 非0
it.todo('should reject takt-pack.yaml with path traversal via ".." segments'); it.todo('should reject takt-repertoire.yaml with path traversal via ".." segments');
// E16: 空パッケージfacets/ も pieces/ もない) // E16: 空パッケージfacets/ も pieces/ もない)
// Given: facets/, pieces/ のどちらもない takt-pack.yaml // Given: facets/, pieces/ のどちらもない takt-repertoire.yaml
// When: ensemble add // When: repertoire add
// Then: exit code 非0 // Then: exit code 非0
it.todo('should reject package with neither facets/ nor pieces/ directory'); it.todo('should reject package with neither facets/ nor pieces/ directory');
// E17: min_version 不正形式1.0、セグメント不足) // E17: min_version 不正形式1.0、セグメント不足)
// Given: takt.min_version: "1.0" // Given: takt.min_version: "1.0"
// When: ensemble add // When: repertoire add
// Then: exit code 非0 // Then: exit code 非0
it.todo('should reject takt-pack.yaml with min_version "1.0" (missing patch segment)'); it.todo('should reject takt-repertoire.yaml with min_version "1.0" (missing patch segment)');
// E18: min_version 不正形式v1.0.0、v プレフィックス) // E18: min_version 不正形式v1.0.0、v プレフィックス)
// Given: takt.min_version: "v1.0.0" // Given: takt.min_version: "v1.0.0"
// When: ensemble add // When: repertoire add
// Then: exit code 非0 // Then: exit code 非0
it.todo('should reject takt-pack.yaml with min_version "v1.0.0" (v prefix)'); it.todo('should reject takt-repertoire.yaml with min_version "v1.0.0" (v prefix)');
// E19: min_version 不正形式1.0.0-alpha、pre-release // E19: min_version 不正形式1.0.0-alpha、pre-release
// Given: takt.min_version: "1.0.0-alpha" // Given: takt.min_version: "1.0.0-alpha"
// When: ensemble add // When: repertoire add
// Then: exit code 非0 // Then: exit code 非0
it.todo('should reject takt-pack.yaml with min_version "1.0.0-alpha" (pre-release suffix)'); it.todo('should reject takt-repertoire.yaml with min_version "1.0.0-alpha" (pre-release suffix)');
// E20: min_version が現在の TAKT より新しい // E20: min_version が現在の TAKT より新しい
// Given: takt.min_version: "999.0.0" // Given: takt.min_version: "999.0.0"
// When: ensemble add // When: repertoire add
// Then: exit code 非0。必要バージョンと現在バージョンが表示される // Then: exit code 非0。必要バージョンと現在バージョンが表示される
it.todo('should fail with version mismatch message when min_version exceeds current takt version'); it.todo('should fail with version mismatch message when min_version exceeds current takt version');
}); });
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// E2E: takt ensemble remove // E2E: takt repertoire remove
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
describe('E2E: takt ensemble remove', () => { describe('E2E: takt repertoire remove', () => {
// E21: 正常削除 y // E21: 正常削除 y
// Given: パッケージインストール済み // Given: パッケージインストール済み
// When: takt ensemble remove @nrslib/takt-pack-fixture、y 入力 // When: takt repertoire remove @nrslib/takt-ensemble-fixture、y 入力
// Then: ディレクトリが削除される。@nrslib/ 配下が空なら @nrslib/ も削除 // Then: ディレクトリが削除される。@nrslib/ 配下が空なら @nrslib/ も削除
it.todo('should remove installed package directory when user answers y'); it.todo('should remove installed package directory when user answers y');
@ -196,26 +196,26 @@ describe('E2E: takt ensemble remove', () => {
}); });
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// E2E: takt ensemble list // E2E: takt repertoire list
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
describe('E2E: takt ensemble list', () => { describe('E2E: takt repertoire list', () => {
// E26: インストール済みパッケージ一覧表示 // E26: インストール済みパッケージ一覧表示
// Given: パッケージ1件インストール済み // Given: パッケージ1件インストール済み
// When: takt ensemble list // When: takt repertoire list
// Then: "📦 インストール済みパッケージ:" と @nrslib/takt-pack-fixture、 // Then: "📦 インストール済みパッケージ:" と @nrslib/takt-ensemble-fixture、
// description、ref、commit 先頭7文字が表示される // description、ref、commit 先頭7文字が表示される
it.todo('should list installed packages with name, description, ref, and abbreviated commit'); it.todo('should list installed packages with name, description, ref, and abbreviated commit');
// E27: 空状態での表示 // E27: 空状態での表示
// Given: ensemble/ が空(パッケージなし) // Given: repertoire/ が空(パッケージなし)
// When: takt ensemble list // When: takt repertoire list
// Then: パッケージなし相当のメッセージ。exit code 0 // Then: パッケージなし相当のメッセージ。exit code 0
it.todo('should display empty-state message when no packages are installed'); it.todo('should display empty-state message when no packages are installed');
// E28: 複数パッケージの一覧 // E28: 複数パッケージの一覧
// Given: 2件以上インストール済み // Given: 2件以上インストール済み
// When: takt ensemble list // When: takt repertoire list
// Then: すべてのパッケージが表示される // Then: すべてのパッケージが表示される
it.todo('should list all installed packages when multiple packages exist'); it.todo('should list all installed packages when multiple packages exist');
}); });

View File

@ -4,7 +4,7 @@
* Covers: * Covers:
* - isScopeRef(): detects @{owner}/{repo}/{facet-name} format * - isScopeRef(): detects @{owner}/{repo}/{facet-name} format
* - parseScopeRef(): parses components from scope reference * - parseScopeRef(): parses components from scope reference
* - resolveScopeRef(): resolves to ~/.takt/ensemble/@{owner}/{repo}/facets/{facet-type}/{facet-name}.md * - resolveScopeRef(): resolves to ~/.takt/repertoire/@{owner}/{repo}/facets/{facet-type}/{facet-name}.md
* - facet-type mapping from field context (personapersonas, policypolicies, etc.) * - facet-type mapping from field context (personapersonas, policypolicies, etc.)
* - Name constraint validation (owner, repo, facet-name patterns) * - Name constraint validation (owner, repo, facet-name patterns)
* - Case normalization (uppercase lowercase) * - Case normalization (uppercase lowercase)
@ -124,89 +124,89 @@ describe('parseScopeRef', () => {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
describe('resolveScopeRef', () => { describe('resolveScopeRef', () => {
let tempEnsembleDir: string; let tempRepertoireDir: string;
beforeEach(() => { beforeEach(() => {
tempEnsembleDir = mkdtempSync(join(tmpdir(), 'takt-ensemble-')); tempRepertoireDir = mkdtempSync(join(tmpdir(), 'takt-repertoire-'));
}); });
afterEach(() => { afterEach(() => {
rmSync(tempEnsembleDir, { recursive: true, force: true }); rmSync(tempRepertoireDir, { recursive: true, force: true });
}); });
it('should resolve persona scope ref to facets/personas/{name}.md', () => { it('should resolve persona scope ref to facets/personas/{name}.md', () => {
// Given: ensemble directory with the package's persona file // Given: repertoire directory with the package's persona file
const facetDir = join(tempEnsembleDir, '@nrslib', 'takt-fullstack', 'facets', 'personas'); const facetDir = join(tempRepertoireDir, '@nrslib', 'takt-fullstack', 'facets', 'personas');
mkdirSync(facetDir, { recursive: true }); mkdirSync(facetDir, { recursive: true });
writeFileSync(join(facetDir, 'expert-coder.md'), 'Expert coder persona'); writeFileSync(join(facetDir, 'expert-coder.md'), 'Expert coder persona');
const scopeRef: ScopeRef = { owner: 'nrslib', repo: 'takt-fullstack', name: 'expert-coder' }; const scopeRef: ScopeRef = { owner: 'nrslib', repo: 'takt-fullstack', name: 'expert-coder' };
// When: scope ref is resolved with facetType 'personas' // When: scope ref is resolved with facetType 'personas'
const result = resolveScopeRef(scopeRef, 'personas', tempEnsembleDir); const result = resolveScopeRef(scopeRef, 'personas', tempRepertoireDir);
// Then: resolved to the correct file path // Then: resolved to the correct file path
expect(result).toBe(join(tempEnsembleDir, '@nrslib', 'takt-fullstack', 'facets', 'personas', 'expert-coder.md')); expect(result).toBe(join(tempRepertoireDir, '@nrslib', 'takt-fullstack', 'facets', 'personas', 'expert-coder.md'));
}); });
it('should resolve policy scope ref to facets/policies/{name}.md', () => { it('should resolve policy scope ref to facets/policies/{name}.md', () => {
// Given: ensemble directory with policy file // Given: repertoire directory with policy file
const facetDir = join(tempEnsembleDir, '@nrslib', 'takt-fullstack', 'facets', 'policies'); const facetDir = join(tempRepertoireDir, '@nrslib', 'takt-fullstack', 'facets', 'policies');
mkdirSync(facetDir, { recursive: true }); mkdirSync(facetDir, { recursive: true });
writeFileSync(join(facetDir, 'owasp-checklist.md'), 'OWASP content'); writeFileSync(join(facetDir, 'owasp-checklist.md'), 'OWASP content');
const scopeRef: ScopeRef = { owner: 'nrslib', repo: 'takt-fullstack', name: 'owasp-checklist' }; const scopeRef: ScopeRef = { owner: 'nrslib', repo: 'takt-fullstack', name: 'owasp-checklist' };
// When: scope ref is resolved with facetType 'policies' // When: scope ref is resolved with facetType 'policies'
const result = resolveScopeRef(scopeRef, 'policies', tempEnsembleDir); const result = resolveScopeRef(scopeRef, 'policies', tempRepertoireDir);
// Then: resolved to correct path // Then: resolved to correct path
expect(result).toBe(join(tempEnsembleDir, '@nrslib', 'takt-fullstack', 'facets', 'policies', 'owasp-checklist.md')); expect(result).toBe(join(tempRepertoireDir, '@nrslib', 'takt-fullstack', 'facets', 'policies', 'owasp-checklist.md'));
}); });
it('should resolve knowledge scope ref to facets/knowledge/{name}.md', () => { it('should resolve knowledge scope ref to facets/knowledge/{name}.md', () => {
// Given: ensemble directory with knowledge file // Given: repertoire directory with knowledge file
const facetDir = join(tempEnsembleDir, '@nrslib', 'takt-security-facets', 'facets', 'knowledge'); const facetDir = join(tempRepertoireDir, '@nrslib', 'takt-security-facets', 'facets', 'knowledge');
mkdirSync(facetDir, { recursive: true }); mkdirSync(facetDir, { recursive: true });
writeFileSync(join(facetDir, 'vulnerability-patterns.md'), 'Vuln patterns'); writeFileSync(join(facetDir, 'vulnerability-patterns.md'), 'Vuln patterns');
const scopeRef: ScopeRef = { owner: 'nrslib', repo: 'takt-security-facets', name: 'vulnerability-patterns' }; const scopeRef: ScopeRef = { owner: 'nrslib', repo: 'takt-security-facets', name: 'vulnerability-patterns' };
// When: scope ref is resolved with facetType 'knowledge' // When: scope ref is resolved with facetType 'knowledge'
const result = resolveScopeRef(scopeRef, 'knowledge', tempEnsembleDir); const result = resolveScopeRef(scopeRef, 'knowledge', tempRepertoireDir);
// Then: resolved to correct path // Then: resolved to correct path
expect(result).toBe(join(tempEnsembleDir, '@nrslib', 'takt-security-facets', 'facets', 'knowledge', 'vulnerability-patterns.md')); expect(result).toBe(join(tempRepertoireDir, '@nrslib', 'takt-security-facets', 'facets', 'knowledge', 'vulnerability-patterns.md'));
}); });
it('should resolve instructions scope ref to facets/instructions/{name}.md', () => { it('should resolve instructions scope ref to facets/instructions/{name}.md', () => {
// Given: instruction file // Given: instruction file
const facetDir = join(tempEnsembleDir, '@acme', 'takt-backend', 'facets', 'instructions'); const facetDir = join(tempRepertoireDir, '@acme', 'takt-backend', 'facets', 'instructions');
mkdirSync(facetDir, { recursive: true }); mkdirSync(facetDir, { recursive: true });
writeFileSync(join(facetDir, 'review-checklist.md'), 'Review steps'); writeFileSync(join(facetDir, 'review-checklist.md'), 'Review steps');
const scopeRef: ScopeRef = { owner: 'acme', repo: 'takt-backend', name: 'review-checklist' }; const scopeRef: ScopeRef = { owner: 'acme', repo: 'takt-backend', name: 'review-checklist' };
// When: scope ref is resolved with facetType 'instructions' // When: scope ref is resolved with facetType 'instructions'
const result = resolveScopeRef(scopeRef, 'instructions', tempEnsembleDir); const result = resolveScopeRef(scopeRef, 'instructions', tempRepertoireDir);
// Then: correct path // Then: correct path
expect(result).toBe(join(tempEnsembleDir, '@acme', 'takt-backend', 'facets', 'instructions', 'review-checklist.md')); expect(result).toBe(join(tempRepertoireDir, '@acme', 'takt-backend', 'facets', 'instructions', 'review-checklist.md'));
}); });
it('should resolve output-contracts scope ref to facets/output-contracts/{name}.md', () => { it('should resolve output-contracts scope ref to facets/output-contracts/{name}.md', () => {
// Given: output contract file // Given: output contract file
const facetDir = join(tempEnsembleDir, '@acme', 'takt-backend', 'facets', 'output-contracts'); const facetDir = join(tempRepertoireDir, '@acme', 'takt-backend', 'facets', 'output-contracts');
mkdirSync(facetDir, { recursive: true }); mkdirSync(facetDir, { recursive: true });
writeFileSync(join(facetDir, 'review-report.md'), 'Report contract'); writeFileSync(join(facetDir, 'review-report.md'), 'Report contract');
const scopeRef: ScopeRef = { owner: 'acme', repo: 'takt-backend', name: 'review-report' }; const scopeRef: ScopeRef = { owner: 'acme', repo: 'takt-backend', name: 'review-report' };
// When: scope ref is resolved with facetType 'output-contracts' // When: scope ref is resolved with facetType 'output-contracts'
const result = resolveScopeRef(scopeRef, 'output-contracts', tempEnsembleDir); const result = resolveScopeRef(scopeRef, 'output-contracts', tempRepertoireDir);
// Then: correct path // Then: correct path
expect(result).toBe(join(tempEnsembleDir, '@acme', 'takt-backend', 'facets', 'output-contracts', 'review-report.md')); expect(result).toBe(join(tempRepertoireDir, '@acme', 'takt-backend', 'facets', 'output-contracts', 'review-report.md'));
}); });
}); });

View File

@ -1,5 +1,5 @@
import { join } from 'node:path'; import { join } from 'node:path';
import type { ScanConfig } from '../../features/ensemble/remove.js'; import type { ScanConfig } from '../../features/repertoire/remove.js';
/** /**
* Build a ScanConfig for tests using tempDir as the root. * Build a ScanConfig for tests using tempDir as the root.

View File

@ -72,7 +72,7 @@ function writeYaml(path: string, content: string): void {
writeFileSync(path, content.trim() + '\n', 'utf-8'); writeFileSync(path, content.trim() + '\n', 'utf-8');
} }
function createPieceMap(entries: { name: string; source: 'builtin' | 'user' | 'project' | 'ensemble' }[]): function createPieceMap(entries: { name: string; source: 'builtin' | 'user' | 'project' | 'repertoire' }[]):
Map<string, PieceWithSource> { Map<string, PieceWithSource> {
const pieces = new Map<string, PieceWithSource>(); const pieces = new Map<string, PieceWithSource>();
for (const entry of entries) { for (const entry of entries) {
@ -442,11 +442,11 @@ describe('buildCategorizedPieces', () => {
expect(paths).toEqual(['Parent / Child']); expect(paths).toEqual(['Parent / Child']);
}); });
it('should append ensemble category for @scope pieces', () => { it('should append repertoire category for @scope pieces', () => {
const allPieces = createPieceMap([ const allPieces = createPieceMap([
{ name: 'default', source: 'builtin' }, { name: 'default', source: 'builtin' },
{ name: '@nrslib/takt-pack/expert', source: 'ensemble' }, { name: '@nrslib/takt-ensemble/expert', source: 'repertoire' },
{ name: '@nrslib/takt-pack/reviewer', source: 'ensemble' }, { name: '@nrslib/takt-ensemble/reviewer', source: 'repertoire' },
]); ]);
const config = { const config = {
pieceCategories: [{ name: 'Main', pieces: ['default'], children: [] }], pieceCategories: [{ name: 'Main', pieces: ['default'], children: [] }],
@ -459,21 +459,21 @@ describe('buildCategorizedPieces', () => {
const categorized = buildCategorizedPieces(allPieces, config, process.cwd()); const categorized = buildCategorizedPieces(allPieces, config, process.cwd());
// ensemble category is appended // repertoire category is appended
const ensembleCat = categorized.categories.find((c) => c.name === 'ensemble'); const repertoireCat = categorized.categories.find((c) => c.name === 'repertoire');
expect(ensembleCat).toBeDefined(); expect(repertoireCat).toBeDefined();
expect(ensembleCat!.children).toHaveLength(1); expect(repertoireCat!.children).toHaveLength(1);
expect(ensembleCat!.children[0]!.name).toBe('@nrslib/takt-pack'); expect(repertoireCat!.children[0]!.name).toBe('@nrslib/takt-ensemble');
expect(ensembleCat!.children[0]!.pieces).toEqual( expect(repertoireCat!.children[0]!.pieces).toEqual(
expect.arrayContaining(['@nrslib/takt-pack/expert', '@nrslib/takt-pack/reviewer']), expect.arrayContaining(['@nrslib/takt-ensemble/expert', '@nrslib/takt-ensemble/reviewer']),
); );
// @scope pieces must not appear in Others // @scope pieces must not appear in Others
const othersCat = categorized.categories.find((c) => c.name === 'Others'); const othersCat = categorized.categories.find((c) => c.name === 'Others');
expect(othersCat?.pieces ?? []).not.toContain('@nrslib/takt-pack/expert'); expect(othersCat?.pieces ?? []).not.toContain('@nrslib/takt-ensemble/expert');
}); });
it('should not append ensemble category when no @scope pieces exist', () => { it('should not append repertoire category when no @scope pieces exist', () => {
const allPieces = createPieceMap([{ name: 'default', source: 'builtin' }]); const allPieces = createPieceMap([{ name: 'default', source: 'builtin' }]);
const config = { const config = {
pieceCategories: [{ name: 'Main', pieces: ['default'], children: [] }], pieceCategories: [{ name: 'Main', pieces: ['default'], children: [] }],
@ -486,7 +486,7 @@ describe('buildCategorizedPieces', () => {
const categorized = buildCategorizedPieces(allPieces, config, process.cwd()); const categorized = buildCategorizedPieces(allPieces, config, process.cwd());
const ensembleCat = categorized.categories.find((c) => c.name === 'ensemble'); const repertoireCat = categorized.categories.find((c) => c.name === 'repertoire');
expect(ensembleCat).toBeUndefined(); expect(repertoireCat).toBeUndefined();
}); });
}); });

View File

@ -189,7 +189,7 @@ movements:
}); });
describe('loadPieceByIdentifier with @scope ref (ensemble)', () => { describe('loadPieceByIdentifier with @scope ref (repertoire)', () => {
let tempDir: string; let tempDir: string;
let configDir: string; let configDir: string;
const originalTaktConfigDir = process.env.TAKT_CONFIG_DIR; const originalTaktConfigDir = process.env.TAKT_CONFIG_DIR;
@ -210,14 +210,14 @@ describe('loadPieceByIdentifier with @scope ref (ensemble)', () => {
rmSync(configDir, { recursive: true, force: true }); rmSync(configDir, { recursive: true, force: true });
}); });
it('should load piece by @scope ref (ensemble)', () => { it('should load piece by @scope ref (repertoire)', () => {
// Given: ensemble package with a piece file // Given: repertoire package with a piece file
const piecesDir = join(configDir, 'ensemble', '@nrslib', 'takt-pack', 'pieces'); const piecesDir = join(configDir, 'repertoire', '@nrslib', 'takt-ensemble', 'pieces');
mkdirSync(piecesDir, { recursive: true }); mkdirSync(piecesDir, { recursive: true });
writeFileSync(join(piecesDir, 'expert.yaml'), SAMPLE_PIECE); writeFileSync(join(piecesDir, 'expert.yaml'), SAMPLE_PIECE);
// When: piece is loaded via @scope ref // When: piece is loaded via @scope ref
const piece = loadPieceByIdentifier('@nrslib/takt-pack/expert', tempDir); const piece = loadPieceByIdentifier('@nrslib/takt-ensemble/expert', tempDir);
// Then: the piece is resolved correctly // Then: the piece is resolved correctly
expect(piece).not.toBeNull(); expect(piece).not.toBeNull();
@ -225,19 +225,19 @@ describe('loadPieceByIdentifier with @scope ref (ensemble)', () => {
}); });
it('should return null for non-existent @scope piece', () => { it('should return null for non-existent @scope piece', () => {
// Given: ensemble dir exists but the requested piece does not // Given: repertoire dir exists but the requested piece does not
const piecesDir = join(configDir, 'ensemble', '@nrslib', 'takt-pack', 'pieces'); const piecesDir = join(configDir, 'repertoire', '@nrslib', 'takt-ensemble', 'pieces');
mkdirSync(piecesDir, { recursive: true }); mkdirSync(piecesDir, { recursive: true });
// When: a non-existent piece is requested // When: a non-existent piece is requested
const piece = loadPieceByIdentifier('@nrslib/takt-pack/no-such-piece', tempDir); const piece = loadPieceByIdentifier('@nrslib/takt-ensemble/no-such-piece', tempDir);
// Then: null is returned // Then: null is returned
expect(piece).toBeNull(); expect(piece).toBeNull();
}); });
}); });
describe('loadAllPiecesWithSources with ensemble pieces', () => { describe('loadAllPiecesWithSources with repertoire pieces', () => {
let tempDir: string; let tempDir: string;
let configDir: string; let configDir: string;
const originalTaktConfigDir = process.env.TAKT_CONFIG_DIR; const originalTaktConfigDir = process.env.TAKT_CONFIG_DIR;
@ -258,28 +258,28 @@ describe('loadAllPiecesWithSources with ensemble pieces', () => {
rmSync(configDir, { recursive: true, force: true }); rmSync(configDir, { recursive: true, force: true });
}); });
it('should include ensemble pieces with @scope qualified names', () => { it('should include repertoire pieces with @scope qualified names', () => {
// Given: ensemble package with a piece file // Given: repertoire package with a piece file
const piecesDir = join(configDir, 'ensemble', '@nrslib', 'takt-pack', 'pieces'); const piecesDir = join(configDir, 'repertoire', '@nrslib', 'takt-ensemble', 'pieces');
mkdirSync(piecesDir, { recursive: true }); mkdirSync(piecesDir, { recursive: true });
writeFileSync(join(piecesDir, 'expert.yaml'), SAMPLE_PIECE); writeFileSync(join(piecesDir, 'expert.yaml'), SAMPLE_PIECE);
// When: all pieces are loaded // When: all pieces are loaded
const pieces = loadAllPiecesWithSources(tempDir); const pieces = loadAllPiecesWithSources(tempDir);
// Then: the ensemble piece is included with 'ensemble' source // Then: the repertoire piece is included with 'repertoire' source
expect(pieces.has('@nrslib/takt-pack/expert')).toBe(true); expect(pieces.has('@nrslib/takt-ensemble/expert')).toBe(true);
expect(pieces.get('@nrslib/takt-pack/expert')!.source).toBe('ensemble'); expect(pieces.get('@nrslib/takt-ensemble/expert')!.source).toBe('repertoire');
}); });
it('should not throw when ensemble dir does not exist', () => { it('should not throw when repertoire dir does not exist', () => {
// Given: no ensemble dir created (configDir/ensemble does not exist) // Given: no repertoire dir created (configDir/repertoire does not exist)
// When: all pieces are loaded // When: all pieces are loaded
const pieces = loadAllPiecesWithSources(tempDir); const pieces = loadAllPiecesWithSources(tempDir);
// Then: no @scope pieces are present and no error thrown // Then: no @scope pieces are present and no error thrown
const ensemblePieces = Array.from(pieces.keys()).filter((k) => k.startsWith('@')); const repertoirePieces = Array.from(pieces.keys()).filter((k) => k.startsWith('@'));
expect(ensemblePieces).toHaveLength(0); expect(repertoirePieces).toHaveLength(0);
}); });
}); });

View File

@ -1,7 +1,7 @@
/** /**
* Unit tests for ensemble atomic installation/update sequence. * Unit tests for repertoire atomic installation/update sequence.
* *
* Target: src/features/ensemble/atomic-update.ts * Target: src/features/repertoire/atomic-update.ts
* *
* Atomic update steps under test: * Atomic update steps under test:
* Step 0: Clean up leftover .tmp/ and .bak/ from previous failed runs * Step 0: Clean up leftover .tmp/ and .bak/ from previous failed runs
@ -23,9 +23,9 @@ import {
cleanupResiduals, cleanupResiduals,
atomicReplace, atomicReplace,
type AtomicReplaceOptions, type AtomicReplaceOptions,
} from '../features/ensemble/atomic-update.js'; } from '../features/repertoire/atomic-update.js';
describe('ensemble atomic install: leftover cleanup (Step 0)', () => { describe('repertoire atomic install: leftover cleanup (Step 0)', () => {
let tempDir: string; let tempDir: string;
beforeEach(() => { beforeEach(() => {
@ -69,7 +69,7 @@ describe('ensemble atomic install: leftover cleanup (Step 0)', () => {
}); });
}); });
describe('ensemble atomic install: failure recovery', () => { describe('repertoire atomic install: failure recovery', () => {
let tempDir: string; let tempDir: string;
beforeEach(() => { beforeEach(() => {

View File

@ -1,7 +1,7 @@
/** /**
* Unit tests for ensemble reference integrity scanner. * Unit tests for repertoire reference integrity scanner.
* *
* Target: src/features/ensemble/remove.ts (findScopeReferences) * Target: src/features/repertoire/remove.ts (findScopeReferences)
* *
* Scanner searches for @scope package references in: * Scanner searches for @scope package references in:
* - {root}/pieces/**\/*.yaml * - {root}/pieces/**\/*.yaml
@ -18,10 +18,10 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'; import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
import { join } from 'node:path'; import { join } from 'node:path';
import { tmpdir } from 'node:os'; import { tmpdir } from 'node:os';
import { findScopeReferences } from '../features/ensemble/remove.js'; import { findScopeReferences } from '../features/repertoire/remove.js';
import { makeScanConfig } from './helpers/ensemble-test-helpers.js'; import { makeScanConfig } from './helpers/repertoire-test-helpers.js';
describe('ensemble reference integrity: detection', () => { describe('repertoire reference integrity: detection', () => {
let tempDir: string; let tempDir: string;
beforeEach(() => { beforeEach(() => {
@ -34,52 +34,52 @@ describe('ensemble reference integrity: detection', () => {
// U29: ~/.takt/pieces/ の @scope 参照を検出 // U29: ~/.takt/pieces/ の @scope 参照を検出
// Given: {root}/pieces/my-review.yaml に // Given: {root}/pieces/my-review.yaml に
// persona: "@nrslib/takt-pack-fixture/expert-coder" を含む // persona: "@nrslib/takt-ensemble-fixture/expert-coder" を含む
// When: findScopeReferences("@nrslib/takt-pack-fixture", config) // When: findScopeReferences("@nrslib/takt-ensemble-fixture", config)
// Then: my-review.yaml が検出される // Then: my-review.yaml が検出される
it('should detect @scope reference in global pieces YAML', () => { it('should detect @scope reference in global pieces YAML', () => {
const piecesDir = join(tempDir, 'pieces'); const piecesDir = join(tempDir, 'pieces');
mkdirSync(piecesDir, { recursive: true }); mkdirSync(piecesDir, { recursive: true });
const pieceFile = join(piecesDir, 'my-review.yaml'); const pieceFile = join(piecesDir, 'my-review.yaml');
writeFileSync(pieceFile, 'persona: "@nrslib/takt-pack-fixture/expert-coder"'); writeFileSync(pieceFile, 'persona: "@nrslib/takt-ensemble-fixture/expert-coder"');
const refs = findScopeReferences('@nrslib/takt-pack-fixture', makeScanConfig(tempDir)); const refs = findScopeReferences('@nrslib/takt-ensemble-fixture', makeScanConfig(tempDir));
expect(refs.some((r) => r.filePath === pieceFile)).toBe(true); expect(refs.some((r) => r.filePath === pieceFile)).toBe(true);
}); });
// U30: {root}/preferences/piece-categories.yaml の @scope 参照を検出 // U30: {root}/preferences/piece-categories.yaml の @scope 参照を検出
// Given: piece-categories.yaml に @nrslib/takt-pack-fixture/expert を含む // Given: piece-categories.yaml に @nrslib/takt-ensemble-fixture/expert を含む
// When: findScopeReferences("@nrslib/takt-pack-fixture", config) // When: findScopeReferences("@nrslib/takt-ensemble-fixture", config)
// Then: piece-categories.yaml が検出される // Then: piece-categories.yaml が検出される
it('should detect @scope reference in global piece-categories.yaml', () => { it('should detect @scope reference in global piece-categories.yaml', () => {
const prefsDir = join(tempDir, 'preferences'); const prefsDir = join(tempDir, 'preferences');
mkdirSync(prefsDir, { recursive: true }); mkdirSync(prefsDir, { recursive: true });
const categoriesFile = join(prefsDir, 'piece-categories.yaml'); const categoriesFile = join(prefsDir, 'piece-categories.yaml');
writeFileSync(categoriesFile, 'categories:\n - "@nrslib/takt-pack-fixture/expert"'); writeFileSync(categoriesFile, 'categories:\n - "@nrslib/takt-ensemble-fixture/expert"');
const refs = findScopeReferences('@nrslib/takt-pack-fixture', makeScanConfig(tempDir)); const refs = findScopeReferences('@nrslib/takt-ensemble-fixture', makeScanConfig(tempDir));
expect(refs.some((r) => r.filePath === categoriesFile)).toBe(true); expect(refs.some((r) => r.filePath === categoriesFile)).toBe(true);
}); });
// U31: {root}/.takt/pieces/ の @scope 参照を検出 // U31: {root}/.takt/pieces/ の @scope 参照を検出
// Given: プロジェクト {root}/.takt/pieces/proj.yaml に @scope 参照 // Given: プロジェクト {root}/.takt/pieces/proj.yaml に @scope 参照
// When: findScopeReferences("@nrslib/takt-pack-fixture", config) // When: findScopeReferences("@nrslib/takt-ensemble-fixture", config)
// Then: proj.yaml が検出される // Then: proj.yaml が検出される
it('should detect @scope reference in project-level pieces YAML', () => { it('should detect @scope reference in project-level pieces YAML', () => {
const projectPiecesDir = join(tempDir, '.takt', 'pieces'); const projectPiecesDir = join(tempDir, '.takt', 'pieces');
mkdirSync(projectPiecesDir, { recursive: true }); mkdirSync(projectPiecesDir, { recursive: true });
const projFile = join(projectPiecesDir, 'proj.yaml'); const projFile = join(projectPiecesDir, 'proj.yaml');
writeFileSync(projFile, 'persona: "@nrslib/takt-pack-fixture/expert-coder"'); writeFileSync(projFile, 'persona: "@nrslib/takt-ensemble-fixture/expert-coder"');
const refs = findScopeReferences('@nrslib/takt-pack-fixture', makeScanConfig(tempDir)); const refs = findScopeReferences('@nrslib/takt-ensemble-fixture', makeScanConfig(tempDir));
expect(refs.some((r) => r.filePath === projFile)).toBe(true); expect(refs.some((r) => r.filePath === projFile)).toBe(true);
}); });
}); });
describe('ensemble reference integrity: non-detection', () => { describe('repertoire reference integrity: non-detection', () => {
let tempDir: string; let tempDir: string;
beforeEach(() => { beforeEach(() => {
@ -92,28 +92,28 @@ describe('ensemble reference integrity: non-detection', () => {
// U32: @scope なし参照は検出しない // U32: @scope なし参照は検出しない
// Given: persona: "coder" のみ(@scope なし) // Given: persona: "coder" のみ(@scope なし)
// When: findScopeReferences("@nrslib/takt-pack-fixture", config) // When: findScopeReferences("@nrslib/takt-ensemble-fixture", config)
// Then: 結果が空配列 // Then: 結果が空配列
it('should not detect plain name references without @scope prefix', () => { it('should not detect plain name references without @scope prefix', () => {
const piecesDir = join(tempDir, 'pieces'); const piecesDir = join(tempDir, 'pieces');
mkdirSync(piecesDir, { recursive: true }); mkdirSync(piecesDir, { recursive: true });
writeFileSync(join(piecesDir, 'plain.yaml'), 'persona: "coder"'); writeFileSync(join(piecesDir, 'plain.yaml'), 'persona: "coder"');
const refs = findScopeReferences('@nrslib/takt-pack-fixture', makeScanConfig(tempDir)); const refs = findScopeReferences('@nrslib/takt-ensemble-fixture', makeScanConfig(tempDir));
expect(refs).toHaveLength(0); expect(refs).toHaveLength(0);
}); });
// U33: 別スコープは検出しない // U33: 別スコープは検出しない
// Given: persona: "@other/package/name" // Given: persona: "@other/package/name"
// When: findScopeReferences("@nrslib/takt-pack-fixture", config) // When: findScopeReferences("@nrslib/takt-ensemble-fixture", config)
// Then: 結果が空配列 // Then: 結果が空配列
it('should not detect references to a different @scope package', () => { it('should not detect references to a different @scope package', () => {
const piecesDir = join(tempDir, 'pieces'); const piecesDir = join(tempDir, 'pieces');
mkdirSync(piecesDir, { recursive: true }); mkdirSync(piecesDir, { recursive: true });
writeFileSync(join(piecesDir, 'other.yaml'), 'persona: "@other/package/name"'); writeFileSync(join(piecesDir, 'other.yaml'), 'persona: "@other/package/name"');
const refs = findScopeReferences('@nrslib/takt-pack-fixture', makeScanConfig(tempDir)); const refs = findScopeReferences('@nrslib/takt-ensemble-fixture', makeScanConfig(tempDir));
expect(refs).toHaveLength(0); expect(refs).toHaveLength(0);
}); });

View File

@ -1,5 +1,5 @@
/** /**
* Unit tests for ensemble @scope resolution and facet resolution chain. * Unit tests for repertoire @scope resolution and facet resolution chain.
* *
* Covers: * Covers:
* A. @scope reference resolution (src/faceted-prompting/scope.ts) * A. @scope reference resolution (src/faceted-prompting/scope.ts)
@ -8,7 +8,7 @@
* *
* @scope resolution rules: * @scope resolution rules:
* "@{owner}/{repo}/{name}" in a facet field * "@{owner}/{repo}/{name}" in a facet field
* {ensembleDir}/@{owner}/{repo}/facets/{type}/{name}.md * {repertoireDir}/@{owner}/{repo}/facets/{type}/{name}.md
* *
* Name constraints: * Name constraints:
* owner: /^[a-z0-9][a-z0-9-]*$/ (lowercase only after normalization) * owner: /^[a-z0-9][a-z0-9-]*$/ (lowercase only after normalization)
@ -16,7 +16,7 @@
* facet/piece name: /^[a-z0-9][a-z0-9-]*$/ * facet/piece name: /^[a-z0-9][a-z0-9-]*$/
* *
* Facet resolution order (package piece): * Facet resolution order (package piece):
* 1. package-local: {ensembleDir}/@{owner}/{repo}/facets/{type}/{facet}.md * 1. package-local: {repertoireDir}/@{owner}/{repo}/facets/{type}/{facet}.md
* 2. project: .takt/facets/{type}/{facet}.md * 2. project: .takt/facets/{type}/{facet}.md
* 3. user: ~/.takt/facets/{type}/{facet}.md * 3. user: ~/.takt/facets/{type}/{facet}.md
* 4. builtin: builtins/{lang}/facets/{type}/{facet}.md * 4. builtin: builtins/{lang}/facets/{type}/{facet}.md
@ -55,45 +55,45 @@ describe('@scope reference resolution', () => {
}); });
// U34: persona @scope 解決 // U34: persona @scope 解決
// Input: "@nrslib/takt-pack-fixture/expert-coder" (personas field) // Input: "@nrslib/takt-ensemble-fixture/expert-coder" (personas field)
// Expect: resolves to {ensembleDir}/@nrslib/takt-pack-fixture/facets/personas/expert-coder.md // Expect: resolves to {repertoireDir}/@nrslib/takt-ensemble-fixture/facets/personas/expert-coder.md
it('should resolve persona @scope reference to ensemble faceted path', () => { it('should resolve persona @scope reference to repertoire faceted path', () => {
const ensembleDir = tempDir; const repertoireDir = tempDir;
const ref = '@nrslib/takt-pack-fixture/expert-coder'; const ref = '@nrslib/takt-ensemble-fixture/expert-coder';
const scopeRef = parseScopeRef(ref); const scopeRef = parseScopeRef(ref);
const resolved = resolveScopeRef(scopeRef, 'personas', ensembleDir); const resolved = resolveScopeRef(scopeRef, 'personas', repertoireDir);
const expected = join(ensembleDir, '@nrslib', 'takt-pack-fixture', 'facets', 'personas', 'expert-coder.md'); const expected = join(repertoireDir, '@nrslib', 'takt-ensemble-fixture', 'facets', 'personas', 'expert-coder.md');
expect(resolved).toBe(expected); expect(resolved).toBe(expected);
}); });
// U35: policy @scope 解決 // U35: policy @scope 解決
// Input: "@nrslib/takt-pack-fixture/strict-coding" (policies field) // Input: "@nrslib/takt-ensemble-fixture/strict-coding" (policies field)
// Expect: resolves to {ensembleDir}/@nrslib/takt-pack-fixture/facets/policies/strict-coding.md // Expect: resolves to {repertoireDir}/@nrslib/takt-ensemble-fixture/facets/policies/strict-coding.md
it('should resolve policy @scope reference to ensemble faceted path', () => { it('should resolve policy @scope reference to repertoire faceted path', () => {
const ensembleDir = tempDir; const repertoireDir = tempDir;
const ref = '@nrslib/takt-pack-fixture/strict-coding'; const ref = '@nrslib/takt-ensemble-fixture/strict-coding';
const scopeRef = parseScopeRef(ref); const scopeRef = parseScopeRef(ref);
const resolved = resolveScopeRef(scopeRef, 'policies', ensembleDir); const resolved = resolveScopeRef(scopeRef, 'policies', repertoireDir);
const expected = join(ensembleDir, '@nrslib', 'takt-pack-fixture', 'facets', 'policies', 'strict-coding.md'); const expected = join(repertoireDir, '@nrslib', 'takt-ensemble-fixture', 'facets', 'policies', 'strict-coding.md');
expect(resolved).toBe(expected); expect(resolved).toBe(expected);
}); });
// U36: 大文字正規化 // U36: 大文字正規化
// Input: "@NrsLib/Takt-Pack-Fixture/expert-coder" // Input: "@NrsLib/Takt-Ensemble-Fixture/expert-coder"
// Expect: owner and repo lowercase-normalized; name kept as-is (must already be lowercase per spec) // Expect: owner and repo lowercase-normalized; name kept as-is (must already be lowercase per spec)
it('should normalize uppercase @scope references to lowercase before resolving', () => { it('should normalize uppercase @scope references to lowercase before resolving', () => {
const ensembleDir = tempDir; const repertoireDir = tempDir;
const ref = '@NrsLib/Takt-Pack-Fixture/expert-coder'; const ref = '@NrsLib/Takt-Ensemble-Fixture/expert-coder';
const scopeRef = parseScopeRef(ref); const scopeRef = parseScopeRef(ref);
// owner and repo are normalized to lowercase // owner and repo are normalized to lowercase
expect(scopeRef.owner).toBe('nrslib'); expect(scopeRef.owner).toBe('nrslib');
expect(scopeRef.repo).toBe('takt-pack-fixture'); expect(scopeRef.repo).toBe('takt-ensemble-fixture');
const resolved = resolveScopeRef(scopeRef, 'personas', ensembleDir); const resolved = resolveScopeRef(scopeRef, 'personas', repertoireDir);
const expected = join(ensembleDir, '@nrslib', 'takt-pack-fixture', 'facets', 'personas', 'expert-coder.md'); const expected = join(repertoireDir, '@nrslib', 'takt-ensemble-fixture', 'facets', 'personas', 'expert-coder.md');
expect(resolved).toBe(expected); expect(resolved).toBe(expected);
}); });
@ -101,13 +101,13 @@ describe('@scope reference resolution', () => {
// Input: "@nonexistent/package/facet" // Input: "@nonexistent/package/facet"
// Expect: resolveFacetPath returns undefined (file not found at resolved path) // Expect: resolveFacetPath returns undefined (file not found at resolved path)
it('should throw error when @scope reference points to non-existent package', () => { it('should throw error when @scope reference points to non-existent package', () => {
const ensembleDir = tempDir; const repertoireDir = tempDir;
const ref = '@nonexistent/package/facet'; const ref = '@nonexistent/package/facet';
// resolveFacetPath returns undefined when the @scope file does not exist // resolveFacetPath returns undefined when the @scope file does not exist
const result = resolveFacetPath(ref, 'personas', { const result = resolveFacetPath(ref, 'personas', {
lang: 'en', lang: 'en',
ensembleDir, repertoireDir,
}); });
expect(result).toBeUndefined(); expect(result).toBeUndefined();
@ -177,9 +177,9 @@ describe('facet resolution chain: package-local layer', () => {
// When: パッケージ内ピースからファセット解決 // When: パッケージ内ピースからファセット解決
// Then: package-local 層のファセットが返る // Then: package-local 層のファセットが返る
it('should prefer package-local facet over project/user/builtin layers', () => { it('should prefer package-local facet over project/user/builtin layers', () => {
const ensembleDir = join(tempDir, 'ensemble'); const repertoireDir = join(tempDir, 'repertoire');
const packagePiecesDir = join(ensembleDir, '@nrslib', 'takt-pack-fixture', 'pieces'); const packagePiecesDir = join(repertoireDir, '@nrslib', 'takt-ensemble-fixture', 'pieces');
const packageFacetDir = join(ensembleDir, '@nrslib', 'takt-pack-fixture', 'facets', 'personas'); const packageFacetDir = join(repertoireDir, '@nrslib', 'takt-ensemble-fixture', 'facets', 'personas');
const projectFacetDir = join(tempDir, 'project', '.takt', 'facets', 'personas'); const projectFacetDir = join(tempDir, 'project', '.takt', 'facets', 'personas');
// Create both package-local and project facet files with the same name // Create both package-local and project facet files with the same name
@ -192,7 +192,7 @@ describe('facet resolution chain: package-local layer', () => {
const candidateDirs = buildCandidateDirsWithPackage('personas', { const candidateDirs = buildCandidateDirsWithPackage('personas', {
lang: 'en', lang: 'en',
pieceDir: packagePiecesDir, pieceDir: packagePiecesDir,
ensembleDir, repertoireDir,
projectDir: join(tempDir, 'project'), projectDir: join(tempDir, 'project'),
}); });
@ -205,8 +205,8 @@ describe('facet resolution chain: package-local layer', () => {
// When: ファセット解決 // When: ファセット解決
// Then: project 層のファセットが返る // Then: project 層のファセットが返る
it('should fall back to project facet when package-local does not have it', () => { it('should fall back to project facet when package-local does not have it', () => {
const ensembleDir = join(tempDir, 'ensemble'); const repertoireDir = join(tempDir, 'repertoire');
const packagePiecesDir = join(ensembleDir, '@nrslib', 'takt-pack-fixture', 'pieces'); const packagePiecesDir = join(repertoireDir, '@nrslib', 'takt-ensemble-fixture', 'pieces');
const projectFacetDir = join(tempDir, 'project', '.takt', 'facets', 'personas'); const projectFacetDir = join(tempDir, 'project', '.takt', 'facets', 'personas');
mkdirSync(packagePiecesDir, { recursive: true }); mkdirSync(packagePiecesDir, { recursive: true });
@ -218,7 +218,7 @@ describe('facet resolution chain: package-local layer', () => {
const resolved = resolveFacetPath('expert-coder', 'personas', { const resolved = resolveFacetPath('expert-coder', 'personas', {
lang: 'en', lang: 'en',
pieceDir: packagePiecesDir, pieceDir: packagePiecesDir,
ensembleDir, repertoireDir,
projectDir: join(tempDir, 'project'), projectDir: join(tempDir, 'project'),
}); });
@ -230,9 +230,9 @@ describe('facet resolution chain: package-local layer', () => {
// When: ファセット解決 // When: ファセット解決
// Then: package-local は無視。project → user → builtin の3層で解決 // Then: package-local は無視。project → user → builtin の3層で解決
it('should not consult package-local layer for non-package pieces', () => { it('should not consult package-local layer for non-package pieces', () => {
const ensembleDir = join(tempDir, 'ensemble'); const repertoireDir = join(tempDir, 'repertoire');
const packageFacetDir = join(ensembleDir, '@nrslib', 'takt-pack-fixture', 'facets', 'personas'); const packageFacetDir = join(repertoireDir, '@nrslib', 'takt-ensemble-fixture', 'facets', 'personas');
// Non-package pieceDir (not under ensembleDir) // Non-package pieceDir (not under repertoireDir)
const globalPiecesDir = join(tempDir, 'global-pieces'); const globalPiecesDir = join(tempDir, 'global-pieces');
mkdirSync(packageFacetDir, { recursive: true }); mkdirSync(packageFacetDir, { recursive: true });
@ -242,7 +242,7 @@ describe('facet resolution chain: package-local layer', () => {
const candidateDirs = buildCandidateDirsWithPackage('personas', { const candidateDirs = buildCandidateDirsWithPackage('personas', {
lang: 'en', lang: 'en',
pieceDir: globalPiecesDir, pieceDir: globalPiecesDir,
ensembleDir, repertoireDir,
}); });
// Package-local dir should NOT be in candidates for non-package pieces // Package-local dir should NOT be in candidates for non-package pieces
@ -252,14 +252,14 @@ describe('facet resolution chain: package-local layer', () => {
describe('package piece detection', () => { describe('package piece detection', () => {
// U46: パッケージ所属は pieceDir パスから判定 // U46: パッケージ所属は pieceDir パスから判定
// Given: pieceDir が {ensembleDir}/@nrslib/repo/pieces/ 配下 // Given: pieceDir が {repertoireDir}/@nrslib/repo/pieces/ 配下
// When: isPackagePiece(pieceDir) 呼び出し // When: isPackagePiece(pieceDir) 呼び出し
// Then: true が返る // Then: true が返る
it('should return true for pieceDir under ensemble/@scope/repo/pieces/', () => { it('should return true for pieceDir under repertoire/@scope/repo/pieces/', () => {
const ensembleDir = '/home/user/.takt/ensemble'; const repertoireDir = '/home/user/.takt/repertoire';
const pieceDir = '/home/user/.takt/ensemble/@nrslib/takt-pack-fixture/pieces'; const pieceDir = '/home/user/.takt/repertoire/@nrslib/takt-ensemble-fixture/pieces';
expect(isPackagePiece(pieceDir, ensembleDir)).toBe(true); expect(isPackagePiece(pieceDir, repertoireDir)).toBe(true);
}); });
// U47: 非パッケージ pieceDir は false // U47: 非パッケージ pieceDir は false
@ -267,9 +267,9 @@ describe('package piece detection', () => {
// When: isPackagePiece(pieceDir) 呼び出し // When: isPackagePiece(pieceDir) 呼び出し
// Then: false が返る // Then: false が返る
it('should return false for pieceDir under global pieces directory', () => { it('should return false for pieceDir under global pieces directory', () => {
const ensembleDir = '/home/user/.takt/ensemble'; const repertoireDir = '/home/user/.takt/repertoire';
const pieceDir = '/home/user/.takt/pieces'; const pieceDir = '/home/user/.takt/pieces';
expect(isPackagePiece(pieceDir, ensembleDir)).toBe(false); expect(isPackagePiece(pieceDir, repertoireDir)).toBe(false);
}); });
}); });

View File

@ -15,7 +15,7 @@ import {
cleanupResiduals, cleanupResiduals,
atomicReplace, atomicReplace,
type AtomicReplaceOptions, type AtomicReplaceOptions,
} from '../../features/ensemble/atomic-update.js'; } from '../../features/repertoire/atomic-update.js';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// cleanupResiduals // cleanupResiduals

View File

@ -27,7 +27,7 @@ import {
MAX_FILE_COUNT, MAX_FILE_COUNT,
ALLOWED_EXTENSIONS, ALLOWED_EXTENSIONS,
ALLOWED_DIRS, ALLOWED_DIRS,
} from '../../features/ensemble/file-filter.js'; } from '../../features/repertoire/file-filter.js';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// isAllowedExtension // isAllowedExtension
@ -39,7 +39,7 @@ describe('isAllowedExtension', () => {
}); });
it('should allow .yaml files', () => { it('should allow .yaml files', () => {
expect(isAllowedExtension('takt-package.yaml')).toBe(true); expect(isAllowedExtension('takt-repertoire.yaml')).toBe(true);
}); });
it('should allow .yml files', () => { it('should allow .yml files', () => {

View File

@ -9,7 +9,7 @@
*/ */
import { describe, it, expect, vi } from 'vitest'; import { describe, it, expect, vi } from 'vitest';
import { resolveRef } from '../../features/ensemble/github-ref-resolver.js'; import { resolveRef } from '../../features/repertoire/github-ref-resolver.js';
describe('resolveRef', () => { describe('resolveRef', () => {
it('should return specRef directly when provided', () => { it('should return specRef directly when provided', () => {

View File

@ -5,7 +5,7 @@
*/ */
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { parseGithubSpec } from '../../features/ensemble/github-spec.js'; import { parseGithubSpec } from '../../features/repertoire/github-spec.js';
describe('parseGithubSpec', () => { describe('parseGithubSpec', () => {
describe('happy path', () => { describe('happy path', () => {

View File

@ -1,10 +1,10 @@
/** /**
* Tests for ensemble list display data retrieval. * Tests for repertoire list display data retrieval.
* *
* Covers: * Covers:
* - readPackageInfo(): reads description from takt-package.yaml and ref/commit from .takt-pack-lock.yaml * - readPackageInfo(): reads description from takt-repertoire.yaml and ref/commit from .takt-repertoire-lock.yaml
* - commit is truncated to first 7 characters for display * - commit is truncated to first 7 characters for display
* - listPackages(): enumerates all installed packages under ensemble/ * - listPackages(): enumerates all installed packages under repertoire/
* - Multiple packages are correctly listed * - Multiple packages are correctly listed
*/ */
@ -15,7 +15,7 @@ import { tmpdir } from 'node:os';
import { import {
readPackageInfo, readPackageInfo,
listPackages, listPackages,
} from '../../features/ensemble/list.js'; } from '../../features/repertoire/list.js';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// readPackageInfo // readPackageInfo
@ -32,16 +32,16 @@ describe('readPackageInfo', () => {
rmSync(tempDir, { recursive: true, force: true }); rmSync(tempDir, { recursive: true, force: true });
}); });
it('should read description from takt-package.yaml', () => { it('should read description from takt-repertoire.yaml', () => {
// Given: a package directory with takt-package.yaml and .takt-pack-lock.yaml // Given: a package directory with takt-repertoire.yaml and .takt-repertoire-lock.yaml
const packageDir = join(tempDir, '@nrslib', 'takt-fullstack'); const packageDir = join(tempDir, '@nrslib', 'takt-fullstack');
mkdirSync(packageDir, { recursive: true }); mkdirSync(packageDir, { recursive: true });
writeFileSync( writeFileSync(
join(packageDir, 'takt-package.yaml'), join(packageDir, 'takt-repertoire.yaml'),
'description: フルスタック開発ワークフロー\n', 'description: フルスタック開発ワークフロー\n',
); );
writeFileSync( writeFileSync(
join(packageDir, '.takt-pack-lock.yaml'), join(packageDir, '.takt-repertoire-lock.yaml'),
`source: github:nrslib/takt-fullstack `source: github:nrslib/takt-fullstack
ref: v1.2.0 ref: v1.2.0
commit: abc1234def5678 commit: abc1234def5678
@ -63,9 +63,9 @@ imported_at: 2026-02-20T12:00:00.000Z
// Given: package with a long commit SHA // Given: package with a long commit SHA
const packageDir = join(tempDir, '@nrslib', 'takt-security-facets'); const packageDir = join(tempDir, '@nrslib', 'takt-security-facets');
mkdirSync(packageDir, { recursive: true }); mkdirSync(packageDir, { recursive: true });
writeFileSync(join(packageDir, 'takt-package.yaml'), 'description: Security facets\n'); writeFileSync(join(packageDir, 'takt-repertoire.yaml'), 'description: Security facets\n');
writeFileSync( writeFileSync(
join(packageDir, '.takt-pack-lock.yaml'), join(packageDir, '.takt-repertoire-lock.yaml'),
`source: github:nrslib/takt-security-facets `source: github:nrslib/takt-security-facets
ref: HEAD ref: HEAD
commit: def5678901234567 commit: def5678901234567
@ -82,12 +82,12 @@ imported_at: 2026-02-20T12:00:00.000Z
}); });
it('should handle package without description field', () => { it('should handle package without description field', () => {
// Given: takt-package.yaml with no description // Given: takt-repertoire.yaml with no description
const packageDir = join(tempDir, '@acme', 'takt-backend'); const packageDir = join(tempDir, '@acme', 'takt-backend');
mkdirSync(packageDir, { recursive: true }); mkdirSync(packageDir, { recursive: true });
writeFileSync(join(packageDir, 'takt-package.yaml'), 'path: takt\n'); writeFileSync(join(packageDir, 'takt-repertoire.yaml'), 'path: takt\n');
writeFileSync( writeFileSync(
join(packageDir, '.takt-pack-lock.yaml'), join(packageDir, '.takt-repertoire-lock.yaml'),
`source: github:acme/takt-backend `source: github:acme/takt-backend
ref: v2.0.0 ref: v2.0.0
commit: 789abcdef0123 commit: 789abcdef0123
@ -107,9 +107,9 @@ imported_at: 2026-01-15T08:30:00.000Z
// Given: package imported from default branch // Given: package imported from default branch
const packageDir = join(tempDir, '@acme', 'no-tag-pkg'); const packageDir = join(tempDir, '@acme', 'no-tag-pkg');
mkdirSync(packageDir, { recursive: true }); mkdirSync(packageDir, { recursive: true });
writeFileSync(join(packageDir, 'takt-package.yaml'), 'description: No tag\n'); writeFileSync(join(packageDir, 'takt-repertoire.yaml'), 'description: No tag\n');
writeFileSync( writeFileSync(
join(packageDir, '.takt-pack-lock.yaml'), join(packageDir, '.takt-repertoire-lock.yaml'),
`source: github:acme/no-tag-pkg `source: github:acme/no-tag-pkg
ref: HEAD ref: HEAD
commit: aabbccddeeff00 commit: aabbccddeeff00
@ -128,8 +128,8 @@ imported_at: 2026-02-01T00:00:00.000Z
// Given: package directory with no lock file // Given: package directory with no lock file
const packageDir = join(tempDir, '@acme', 'no-lock-pkg'); const packageDir = join(tempDir, '@acme', 'no-lock-pkg');
mkdirSync(packageDir, { recursive: true }); mkdirSync(packageDir, { recursive: true });
writeFileSync(join(packageDir, 'takt-package.yaml'), 'description: No lock\n'); writeFileSync(join(packageDir, 'takt-repertoire.yaml'), 'description: No lock\n');
// .takt-pack-lock.yaml intentionally not created // .takt-repertoire-lock.yaml intentionally not created
// When: package info is read // When: package info is read
const info = readPackageInfo(packageDir, '@acme/no-lock-pkg'); const info = readPackageInfo(packageDir, '@acme/no-lock-pkg');
@ -156,18 +156,18 @@ describe('listPackages', () => {
}); });
function createPackage( function createPackage(
ensembleDir: string, repertoireDir: string,
owner: string, owner: string,
repo: string, repo: string,
description: string, description: string,
ref: string, ref: string,
commit: string, commit: string,
): void { ): void {
const packageDir = join(ensembleDir, `@${owner}`, repo); const packageDir = join(repertoireDir, `@${owner}`, repo);
mkdirSync(packageDir, { recursive: true }); mkdirSync(packageDir, { recursive: true });
writeFileSync(join(packageDir, 'takt-package.yaml'), `description: ${description}\n`); writeFileSync(join(packageDir, 'takt-repertoire.yaml'), `description: ${description}\n`);
writeFileSync( writeFileSync(
join(packageDir, '.takt-pack-lock.yaml'), join(packageDir, '.takt-repertoire-lock.yaml'),
`source: github:${owner}/${repo} `source: github:${owner}/${repo}
ref: ${ref} ref: ${ref}
commit: ${commit} commit: ${commit}
@ -176,15 +176,15 @@ imported_at: 2026-02-20T12:00:00.000Z
); );
} }
it('should list all installed packages from ensemble directory', () => { it('should list all installed packages from repertoire directory', () => {
// Given: ensemble directory with 3 packages // Given: repertoire directory with 3 packages
const ensembleDir = join(tempDir, 'ensemble'); const repertoireDir = join(tempDir, 'repertoire');
createPackage(ensembleDir, 'nrslib', 'takt-fullstack', 'Fullstack workflow', 'v1.2.0', 'abc1234def5678'); createPackage(repertoireDir, 'nrslib', 'takt-fullstack', 'Fullstack workflow', 'v1.2.0', 'abc1234def5678');
createPackage(ensembleDir, 'nrslib', 'takt-security-facets', 'Security facets', 'HEAD', 'def5678901234'); createPackage(repertoireDir, 'nrslib', 'takt-security-facets', 'Security facets', 'HEAD', 'def5678901234');
createPackage(ensembleDir, 'acme-corp', 'takt-backend', 'Backend facets', 'v2.0.0', '789abcdef0123'); createPackage(repertoireDir, 'acme-corp', 'takt-backend', 'Backend facets', 'v2.0.0', '789abcdef0123');
// When: packages are listed // When: packages are listed
const packages = listPackages(ensembleDir); const packages = listPackages(repertoireDir);
// Then: all 3 packages are returned // Then: all 3 packages are returned
expect(packages).toHaveLength(3); expect(packages).toHaveLength(3);
@ -194,25 +194,25 @@ imported_at: 2026-02-20T12:00:00.000Z
expect(scopes).toContain('@acme-corp/takt-backend'); expect(scopes).toContain('@acme-corp/takt-backend');
}); });
it('should return empty list when ensemble directory has no packages', () => { it('should return empty list when repertoire directory has no packages', () => {
// Given: empty ensemble directory // Given: empty repertoire directory
const ensembleDir = join(tempDir, 'ensemble'); const repertoireDir = join(tempDir, 'repertoire');
mkdirSync(ensembleDir, { recursive: true }); mkdirSync(repertoireDir, { recursive: true });
// When: packages are listed // When: packages are listed
const packages = listPackages(ensembleDir); const packages = listPackages(repertoireDir);
// Then: empty list // Then: empty list
expect(packages).toHaveLength(0); expect(packages).toHaveLength(0);
}); });
it('should include correct commit (truncated to 7 chars) for each package', () => { it('should include correct commit (truncated to 7 chars) for each package', () => {
// Given: ensemble with one package // Given: repertoire with one package
const ensembleDir = join(tempDir, 'ensemble'); const repertoireDir = join(tempDir, 'repertoire');
createPackage(ensembleDir, 'nrslib', 'takt-fullstack', 'Fullstack', 'v1.2.0', 'abc1234def5678'); createPackage(repertoireDir, 'nrslib', 'takt-fullstack', 'Fullstack', 'v1.2.0', 'abc1234def5678');
// When: packages are listed // When: packages are listed
const packages = listPackages(ensembleDir); const packages = listPackages(repertoireDir);
// Then: commit is 7 chars // Then: commit is 7 chars
const pkg = packages.find((p) => p.scope === '@nrslib/takt-fullstack')!; const pkg = packages.find((p) => p.scope === '@nrslib/takt-fullstack')!;

View File

@ -1,11 +1,11 @@
/** /**
* Tests for .takt-pack-lock.yaml generation and parsing. * Tests for .takt-repertoire-lock.yaml generation and parsing.
* *
* Covers: * Covers:
* - extractCommitSha: parse SHA from tarball directory name {owner}-{repo}-{sha}/ * - extractCommitSha: parse SHA from tarball directory name {owner}-{repo}-{sha}/
* - generateLockFile: produces correct fields (source, ref, commit, imported_at) * - generateLockFile: produces correct fields (source, ref, commit, imported_at)
* - ref defaults to "HEAD" when not specified * - ref defaults to "HEAD" when not specified
* - parseLockFile: reads .takt-pack-lock.yaml content * - parseLockFile: reads .takt-repertoire-lock.yaml content
*/ */
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
@ -13,7 +13,7 @@ import {
extractCommitSha, extractCommitSha,
generateLockFile, generateLockFile,
parseLockFile, parseLockFile,
} from '../../features/ensemble/lock-file.js'; } from '../../features/repertoire/lock-file.js';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// extractCommitSha // extractCommitSha
@ -117,7 +117,7 @@ describe('generateLockFile', () => {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
describe('parseLockFile', () => { describe('parseLockFile', () => {
it('should parse a valid .takt-pack-lock.yaml string', () => { it('should parse a valid .takt-repertoire-lock.yaml string', () => {
// Given: lock file YAML content // Given: lock file YAML content
const yaml = `source: github:nrslib/takt-fullstack const yaml = `source: github:nrslib/takt-fullstack
ref: v1.2.0 ref: v1.2.0

View File

@ -8,7 +8,7 @@
*/ */
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { summarizeFacetsByType, detectEditPieces, formatEditPieceWarnings } from '../../features/ensemble/pack-summary.js'; import { summarizeFacetsByType, detectEditPieces, formatEditPieceWarnings } from '../../features/repertoire/pack-summary.js';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// summarizeFacetsByType // summarizeFacetsByType

View File

@ -2,7 +2,7 @@
* Tests for package-local facet resolution chain. * Tests for package-local facet resolution chain.
* *
* Covers: * Covers:
* - isPackagePiece(): detects if pieceDir is under ~/.takt/ensemble/@owner/repo/pieces/ * - isPackagePiece(): detects if pieceDir is under ~/.takt/repertoire/@owner/repo/pieces/
* - getPackageFromPieceDir(): extracts @owner/repo from pieceDir path * - getPackageFromPieceDir(): extracts @owner/repo from pieceDir path
* - Package pieces use 4-layer chain: package-local project user builtin * - Package pieces use 4-layer chain: package-local project user builtin
* - Non-package pieces use 3-layer chain: project user builtin * - Non-package pieces use 3-layer chain: project user builtin
@ -34,27 +34,27 @@ describe('isPackagePiece', () => {
rmSync(tempDir, { recursive: true, force: true }); rmSync(tempDir, { recursive: true, force: true });
}); });
it('should return true when pieceDir is under ensemble/@owner/repo/pieces/', () => { it('should return true when pieceDir is under repertoire/@owner/repo/pieces/', () => {
// Given: pieceDir under the ensemble directory structure // Given: pieceDir under the repertoire directory structure
const ensembleDir = join(tempDir, 'ensemble'); const repertoireDir = join(tempDir, 'repertoire');
const pieceDir = join(ensembleDir, '@nrslib', 'takt-fullstack', 'pieces'); const pieceDir = join(repertoireDir, '@nrslib', 'takt-fullstack', 'pieces');
// When: checking if it is a package piece // When: checking if it is a package piece
const result = isPackagePiece(pieceDir, ensembleDir); const result = isPackagePiece(pieceDir, repertoireDir);
// Then: it is recognized as a package piece // Then: it is recognized as a package piece
expect(result).toBe(true); expect(result).toBe(true);
}); });
it('should return false when pieceDir is under user global pieces directory', () => { it('should return false when pieceDir is under user global pieces directory', () => {
// Given: pieceDir in ~/.takt/pieces/ (not ensemble) // Given: pieceDir in ~/.takt/pieces/ (not repertoire)
const globalPiecesDir = join(tempDir, 'pieces'); const globalPiecesDir = join(tempDir, 'pieces');
mkdirSync(globalPiecesDir, { recursive: true }); mkdirSync(globalPiecesDir, { recursive: true });
const ensembleDir = join(tempDir, 'ensemble'); const repertoireDir = join(tempDir, 'repertoire');
// When: checking // When: checking
const result = isPackagePiece(globalPiecesDir, ensembleDir); const result = isPackagePiece(globalPiecesDir, repertoireDir);
// Then: not a package piece // Then: not a package piece
expect(result).toBe(false); expect(result).toBe(false);
@ -65,10 +65,10 @@ describe('isPackagePiece', () => {
const projectPiecesDir = join(tempDir, '.takt', 'pieces'); const projectPiecesDir = join(tempDir, '.takt', 'pieces');
mkdirSync(projectPiecesDir, { recursive: true }); mkdirSync(projectPiecesDir, { recursive: true });
const ensembleDir = join(tempDir, 'ensemble'); const repertoireDir = join(tempDir, 'repertoire');
// When: checking // When: checking
const result = isPackagePiece(projectPiecesDir, ensembleDir); const result = isPackagePiece(projectPiecesDir, repertoireDir);
// Then: not a package piece // Then: not a package piece
expect(result).toBe(false); expect(result).toBe(false);
@ -79,10 +79,10 @@ describe('isPackagePiece', () => {
const builtinPiecesDir = join(tempDir, 'builtins', 'ja', 'pieces'); const builtinPiecesDir = join(tempDir, 'builtins', 'ja', 'pieces');
mkdirSync(builtinPiecesDir, { recursive: true }); mkdirSync(builtinPiecesDir, { recursive: true });
const ensembleDir = join(tempDir, 'ensemble'); const repertoireDir = join(tempDir, 'repertoire');
// When: checking // When: checking
const result = isPackagePiece(builtinPiecesDir, ensembleDir); const result = isPackagePiece(builtinPiecesDir, repertoireDir);
// Then: not a package piece // Then: not a package piece
expect(result).toBe(false); expect(result).toBe(false);
@ -104,13 +104,13 @@ describe('getPackageFromPieceDir', () => {
rmSync(tempDir, { recursive: true, force: true }); rmSync(tempDir, { recursive: true, force: true });
}); });
it('should extract owner and repo from ensemble pieceDir', () => { it('should extract owner and repo from repertoire pieceDir', () => {
// Given: pieceDir under ensemble // Given: pieceDir under repertoire
const ensembleDir = join(tempDir, 'ensemble'); const repertoireDir = join(tempDir, 'repertoire');
const pieceDir = join(ensembleDir, '@nrslib', 'takt-fullstack', 'pieces'); const pieceDir = join(repertoireDir, '@nrslib', 'takt-fullstack', 'pieces');
// When: package is extracted // When: package is extracted
const pkg = getPackageFromPieceDir(pieceDir, ensembleDir); const pkg = getPackageFromPieceDir(pieceDir, repertoireDir);
// Then: owner and repo are correct // Then: owner and repo are correct
expect(pkg).not.toBeUndefined(); expect(pkg).not.toBeUndefined();
@ -119,12 +119,12 @@ describe('getPackageFromPieceDir', () => {
}); });
it('should return undefined for non-package pieceDir', () => { it('should return undefined for non-package pieceDir', () => {
// Given: pieceDir not under ensemble // Given: pieceDir not under repertoire
const pieceDir = join(tempDir, 'pieces'); const pieceDir = join(tempDir, 'pieces');
const ensembleDir = join(tempDir, 'ensemble'); const repertoireDir = join(tempDir, 'repertoire');
// When: package is extracted // When: package is extracted
const pkg = getPackageFromPieceDir(pieceDir, ensembleDir); const pkg = getPackageFromPieceDir(pieceDir, repertoireDir);
// Then: undefined (not a package piece) // Then: undefined (not a package piece)
expect(pkg).toBeUndefined(); expect(pkg).toBeUndefined();
@ -148,25 +148,25 @@ describe('buildCandidateDirsWithPackage', () => {
it('should include package-local dir as first candidate for package piece', () => { it('should include package-local dir as first candidate for package piece', () => {
// Given: a package piece context // Given: a package piece context
const ensembleDir = join(tempDir, 'ensemble'); const repertoireDir = join(tempDir, 'repertoire');
const pieceDir = join(ensembleDir, '@nrslib', 'takt-fullstack', 'pieces'); const pieceDir = join(repertoireDir, '@nrslib', 'takt-fullstack', 'pieces');
const projectDir = join(tempDir, 'project'); const projectDir = join(tempDir, 'project');
const context = { projectDir, lang: 'ja' as const, pieceDir, ensembleDir }; const context = { projectDir, lang: 'ja' as const, pieceDir, repertoireDir };
// When: candidate directories are built // When: candidate directories are built
const dirs = buildCandidateDirsWithPackage('personas', context); const dirs = buildCandidateDirsWithPackage('personas', context);
// Then: package-local dir is first // Then: package-local dir is first
const expectedPackageLocal = join(ensembleDir, '@nrslib', 'takt-fullstack', 'facets', 'personas'); const expectedPackageLocal = join(repertoireDir, '@nrslib', 'takt-fullstack', 'facets', 'personas');
expect(dirs[0]).toBe(expectedPackageLocal); expect(dirs[0]).toBe(expectedPackageLocal);
}); });
it('should have 4 candidate dirs for package piece: package-local, project, user, builtin', () => { it('should have 4 candidate dirs for package piece: package-local, project, user, builtin', () => {
// Given: package piece context // Given: package piece context
const ensembleDir = join(tempDir, 'ensemble'); const repertoireDir = join(tempDir, 'repertoire');
const pieceDir = join(ensembleDir, '@nrslib', 'takt-fullstack', 'pieces'); const pieceDir = join(repertoireDir, '@nrslib', 'takt-fullstack', 'pieces');
const projectDir = join(tempDir, 'project'); const projectDir = join(tempDir, 'project');
const context = { projectDir, lang: 'ja' as const, pieceDir, ensembleDir }; const context = { projectDir, lang: 'ja' as const, pieceDir, repertoireDir };
// When: candidate directories are built // When: candidate directories are built
const dirs = buildCandidateDirsWithPackage('personas', context); const dirs = buildCandidateDirsWithPackage('personas', context);
@ -176,14 +176,14 @@ describe('buildCandidateDirsWithPackage', () => {
}); });
it('should have 3 candidate dirs for non-package piece: project, user, builtin', () => { it('should have 3 candidate dirs for non-package piece: project, user, builtin', () => {
// Given: non-package piece context (no ensemble path) // Given: non-package piece context (no repertoire path)
const projectDir = join(tempDir, 'project'); const projectDir = join(tempDir, 'project');
const userPiecesDir = join(tempDir, 'pieces'); const userPiecesDir = join(tempDir, 'pieces');
const context = { const context = {
projectDir, projectDir,
lang: 'ja' as const, lang: 'ja' as const,
pieceDir: userPiecesDir, pieceDir: userPiecesDir,
ensembleDir: join(tempDir, 'ensemble'), repertoireDir: join(tempDir, 'repertoire'),
}; };
// When: candidate directories are built // When: candidate directories are built
@ -195,8 +195,8 @@ describe('buildCandidateDirsWithPackage', () => {
it('should resolve package-local facet before project-level for package piece', () => { it('should resolve package-local facet before project-level for package piece', () => {
// Given: both package-local and project-level facet files exist // Given: both package-local and project-level facet files exist
const ensembleDir = join(tempDir, 'ensemble'); const repertoireDir = join(tempDir, 'repertoire');
const pkgFacetDir = join(ensembleDir, '@nrslib', 'takt-fullstack', 'facets', 'personas'); const pkgFacetDir = join(repertoireDir, '@nrslib', 'takt-fullstack', 'facets', 'personas');
mkdirSync(pkgFacetDir, { recursive: true }); mkdirSync(pkgFacetDir, { recursive: true });
writeFileSync(join(pkgFacetDir, 'expert-coder.md'), 'Package persona'); writeFileSync(join(pkgFacetDir, 'expert-coder.md'), 'Package persona');
@ -205,8 +205,8 @@ describe('buildCandidateDirsWithPackage', () => {
mkdirSync(projectFacetDir, { recursive: true }); mkdirSync(projectFacetDir, { recursive: true });
writeFileSync(join(projectFacetDir, 'expert-coder.md'), 'Project persona'); writeFileSync(join(projectFacetDir, 'expert-coder.md'), 'Project persona');
const pieceDir = join(ensembleDir, '@nrslib', 'takt-fullstack', 'pieces'); const pieceDir = join(repertoireDir, '@nrslib', 'takt-fullstack', 'pieces');
const context = { projectDir, lang: 'ja' as const, pieceDir, ensembleDir }; const context = { projectDir, lang: 'ja' as const, pieceDir, repertoireDir };
// When: candidate directories are built // When: candidate directories are built
const dirs = buildCandidateDirsWithPackage('personas', context); const dirs = buildCandidateDirsWithPackage('personas', context);

View File

@ -1,5 +1,5 @@
/** /**
* Tests for reference integrity check during ensemble remove. * Tests for reference integrity check during repertoire remove.
* *
* Covers: * Covers:
* - shouldRemoveOwnerDir(): returns true when owner dir has no other packages * - shouldRemoveOwnerDir(): returns true when owner dir has no other packages
@ -9,7 +9,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, mkdirSync, rmSync } from 'node:fs'; import { mkdtempSync, mkdirSync, rmSync } from 'node:fs';
import { join } from 'node:path'; import { join } from 'node:path';
import { tmpdir } from 'node:os'; import { tmpdir } from 'node:os';
import { shouldRemoveOwnerDir } from '../../features/ensemble/remove.js'; import { shouldRemoveOwnerDir } from '../../features/repertoire/remove.js';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// shouldRemoveOwnerDir // shouldRemoveOwnerDir

View File

@ -1,5 +1,5 @@
/** /**
* Regression test for ensembleRemoveCommand scan configuration. * Regression test for repertoireRemoveCommand scan configuration.
* *
* Verifies that findScopeReferences is called with exactly the 3 spec-defined * Verifies that findScopeReferences is called with exactly the 3 spec-defined
* scan locations: * scan locations:
@ -20,14 +20,14 @@ vi.mock('node:fs', () => ({
rmSync: vi.fn(), rmSync: vi.fn(),
})); }));
vi.mock('../../features/ensemble/remove.js', () => ({ vi.mock('../../features/repertoire/remove.js', () => ({
findScopeReferences: vi.fn().mockReturnValue([]), findScopeReferences: vi.fn().mockReturnValue([]),
shouldRemoveOwnerDir: vi.fn().mockReturnValue(false), shouldRemoveOwnerDir: vi.fn().mockReturnValue(false),
})); }));
vi.mock('../../infra/config/paths.js', () => ({ vi.mock('../../infra/config/paths.js', () => ({
getEnsembleDir: vi.fn().mockReturnValue('/home/user/.takt/ensemble'), getRepertoireDir: vi.fn().mockReturnValue('/home/user/.takt/repertoire'),
getEnsemblePackageDir: vi.fn().mockReturnValue('/home/user/.takt/ensemble/@owner/repo'), getRepertoirePackageDir: vi.fn().mockReturnValue('/home/user/.takt/repertoire/@owner/repo'),
getGlobalConfigDir: vi.fn().mockReturnValue('/home/user/.takt'), getGlobalConfigDir: vi.fn().mockReturnValue('/home/user/.takt'),
getGlobalPiecesDir: vi.fn().mockReturnValue('/home/user/.takt/pieces'), getGlobalPiecesDir: vi.fn().mockReturnValue('/home/user/.takt/pieces'),
getProjectPiecesDir: vi.fn().mockReturnValue('/project/.takt/pieces'), getProjectPiecesDir: vi.fn().mockReturnValue('/project/.takt/pieces'),
@ -46,21 +46,21 @@ vi.mock('../../shared/ui/index.js', () => ({
// Import after mocks are declared // Import after mocks are declared
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
import { ensembleRemoveCommand } from '../../commands/ensemble/remove.js'; import { repertoireRemoveCommand } from '../../commands/repertoire/remove.js';
import { findScopeReferences } from '../../features/ensemble/remove.js'; import { findScopeReferences } from '../../features/repertoire/remove.js';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Tests // Tests
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
describe('ensembleRemoveCommand — scan configuration', () => { describe('repertoireRemoveCommand — scan configuration', () => {
beforeEach(() => { beforeEach(() => {
vi.mocked(findScopeReferences).mockReturnValue([]); vi.mocked(findScopeReferences).mockReturnValue([]);
}); });
it('should call findScopeReferences with exactly 2 piecesDirs and 1 categoriesFile', async () => { it('should call findScopeReferences with exactly 2 piecesDirs and 1 categoriesFile', async () => {
// When: remove command is invoked (confirm returns false → no deletion) // When: remove command is invoked (confirm returns false → no deletion)
await ensembleRemoveCommand('@owner/repo'); await repertoireRemoveCommand('@owner/repo');
// Then: findScopeReferences is called once // Then: findScopeReferences is called once
expect(findScopeReferences).toHaveBeenCalledOnce(); expect(findScopeReferences).toHaveBeenCalledOnce();
@ -76,7 +76,7 @@ describe('ensembleRemoveCommand — scan configuration', () => {
it('should include global pieces dir in scan', async () => { it('should include global pieces dir in scan', async () => {
// When: remove command is invoked // When: remove command is invoked
await ensembleRemoveCommand('@owner/repo'); await repertoireRemoveCommand('@owner/repo');
const [, scanConfig] = vi.mocked(findScopeReferences).mock.calls[0]!; const [, scanConfig] = vi.mocked(findScopeReferences).mock.calls[0]!;
@ -86,7 +86,7 @@ describe('ensembleRemoveCommand — scan configuration', () => {
it('should include project pieces dir in scan', async () => { it('should include project pieces dir in scan', async () => {
// When: remove command is invoked // When: remove command is invoked
await ensembleRemoveCommand('@owner/repo'); await repertoireRemoveCommand('@owner/repo');
const [, scanConfig] = vi.mocked(findScopeReferences).mock.calls[0]!; const [, scanConfig] = vi.mocked(findScopeReferences).mock.calls[0]!;
@ -96,7 +96,7 @@ describe('ensembleRemoveCommand — scan configuration', () => {
it('should include preferences/piece-categories.yaml in categoriesFiles', async () => { it('should include preferences/piece-categories.yaml in categoriesFiles', async () => {
// When: remove command is invoked // When: remove command is invoked
await ensembleRemoveCommand('@owner/repo'); await repertoireRemoveCommand('@owner/repo');
const [, scanConfig] = vi.mocked(findScopeReferences).mock.calls[0]!; const [, scanConfig] = vi.mocked(findScopeReferences).mock.calls[0]!;
@ -108,7 +108,7 @@ describe('ensembleRemoveCommand — scan configuration', () => {
it('should pass the scope as the first argument to findScopeReferences', async () => { it('should pass the scope as the first argument to findScopeReferences', async () => {
// When: remove command is invoked with a scope // When: remove command is invoked with a scope
await ensembleRemoveCommand('@owner/repo'); await repertoireRemoveCommand('@owner/repo');
const [scope] = vi.mocked(findScopeReferences).mock.calls[0]!; const [scope] = vi.mocked(findScopeReferences).mock.calls[0]!;

View File

@ -2,7 +2,7 @@
* Tests for facet directory path helpers in paths.ts items 4245. * Tests for facet directory path helpers in paths.ts items 4245.
* *
* Verifies the `facets/` segment is present in all facet path results, * Verifies the `facets/` segment is present in all facet path results,
* and that getEnsembleFacetDir constructs the correct full ensemble path. * and that getRepertoireFacetDir constructs the correct full repertoire path.
*/ */
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
@ -10,8 +10,8 @@ import {
getProjectFacetDir, getProjectFacetDir,
getGlobalFacetDir, getGlobalFacetDir,
getBuiltinFacetDir, getBuiltinFacetDir,
getEnsembleFacetDir, getRepertoireFacetDir,
getEnsemblePackageDir, getRepertoirePackageDir,
type FacetType, type FacetType,
} from '../../infra/config/paths.js'; } from '../../infra/config/paths.js';
@ -130,38 +130,38 @@ describe('getBuiltinFacetDir — facets/ prefix', () => {
}); });
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// getEnsembleFacetDir — item 45 (new function) // getRepertoireFacetDir — item 45 (new function)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
describe('getEnsembleFacetDir — new path function', () => { describe('getRepertoireFacetDir — new path function', () => {
it('should return path containing ensemble/@{owner}/{repo}/facets/{type}', () => { it('should return path containing repertoire/@{owner}/{repo}/facets/{type}', () => {
// Given: owner, repo, and facet type // Given: owner, repo, and facet type
// When: path is built // When: path is built
const dir = getEnsembleFacetDir('nrslib', 'takt-fullstack', 'personas'); const dir = getRepertoireFacetDir('nrslib', 'takt-fullstack', 'personas');
// Then: all segments are present // Then: all segments are present
const normalized = dir.replace(/\\/g, '/'); const normalized = dir.replace(/\\/g, '/');
expect(normalized).toContain('ensemble'); expect(normalized).toContain('repertoire');
expect(normalized).toContain('@nrslib'); expect(normalized).toContain('@nrslib');
expect(normalized).toContain('takt-fullstack'); expect(normalized).toContain('takt-fullstack');
expect(normalized).toContain('facets'); expect(normalized).toContain('facets');
expect(normalized).toContain('personas'); expect(normalized).toContain('personas');
}); });
it('should construct path as ~/.takt/ensemble/@{owner}/{repo}/facets/{type}', () => { it('should construct path as ~/.takt/repertoire/@{owner}/{repo}/facets/{type}', () => {
// Given: owner, repo, and facet type // Given: owner, repo, and facet type
// When: path is built // When: path is built
const dir = getEnsembleFacetDir('nrslib', 'takt-fullstack', 'personas'); const dir = getRepertoireFacetDir('nrslib', 'takt-fullstack', 'personas');
// Then: full segment order is ensemble → @nrslib → takt-fullstack → facets → personas // Then: full segment order is repertoire → @nrslib → takt-fullstack → facets → personas
const normalized = dir.replace(/\\/g, '/'); const normalized = dir.replace(/\\/g, '/');
expect(normalized).toMatch(/ensemble\/@nrslib\/takt-fullstack\/facets\/personas/); expect(normalized).toMatch(/repertoire\/@nrslib\/takt-fullstack\/facets\/personas/);
}); });
it('should prepend @ before owner name in the path', () => { it('should prepend @ before owner name in the path', () => {
// Given: owner without @ prefix // Given: owner without @ prefix
// When: path is built // When: path is built
const dir = getEnsembleFacetDir('myowner', 'myrepo', 'policies'); const dir = getRepertoireFacetDir('myowner', 'myrepo', 'policies');
// Then: @ is included before owner in the path // Then: @ is included before owner in the path
const normalized = dir.replace(/\\/g, '/'); const normalized = dir.replace(/\\/g, '/');
@ -172,46 +172,46 @@ describe('getEnsembleFacetDir — new path function', () => {
// Given: all valid facet types // Given: all valid facet types
for (const t of ALL_FACET_TYPES) { for (const t of ALL_FACET_TYPES) {
// When: path is built // When: path is built
const dir = getEnsembleFacetDir('owner', 'repo', t); const dir = getRepertoireFacetDir('owner', 'repo', t);
// Then: path has correct ensemble structure with facet type // Then: path has correct repertoire structure with facet type
const normalized = dir.replace(/\\/g, '/'); const normalized = dir.replace(/\\/g, '/');
expect(normalized).toMatch(new RegExp(`ensemble/@owner/repo/facets/${t}`)); expect(normalized).toMatch(new RegExp(`repertoire/@owner/repo/facets/${t}`));
} }
}); });
}); });
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// getEnsemblePackageDir — item 46 // getRepertoirePackageDir — item 46
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
describe('getEnsemblePackageDir', () => { describe('getRepertoirePackageDir', () => {
it('should return path containing ensemble/@{owner}/{repo}', () => { it('should return path containing repertoire/@{owner}/{repo}', () => {
// Given: owner and repo // Given: owner and repo
// When: path is built // When: path is built
const dir = getEnsemblePackageDir('nrslib', 'takt-fullstack'); const dir = getRepertoirePackageDir('nrslib', 'takt-fullstack');
// Then: all segments are present // Then: all segments are present
const normalized = dir.replace(/\\/g, '/'); const normalized = dir.replace(/\\/g, '/');
expect(normalized).toContain('ensemble'); expect(normalized).toContain('repertoire');
expect(normalized).toContain('@nrslib'); expect(normalized).toContain('@nrslib');
expect(normalized).toContain('takt-fullstack'); expect(normalized).toContain('takt-fullstack');
}); });
it('should construct path as ~/.takt/ensemble/@{owner}/{repo}', () => { it('should construct path as ~/.takt/repertoire/@{owner}/{repo}', () => {
// Given: owner and repo // Given: owner and repo
// When: path is built // When: path is built
const dir = getEnsemblePackageDir('nrslib', 'takt-fullstack'); const dir = getRepertoirePackageDir('nrslib', 'takt-fullstack');
// Then: full segment order is ensemble → @nrslib → takt-fullstack // Then: full segment order is repertoire → @nrslib → takt-fullstack
const normalized = dir.replace(/\\/g, '/'); const normalized = dir.replace(/\\/g, '/');
expect(normalized).toMatch(/ensemble\/@nrslib\/takt-fullstack$/); expect(normalized).toMatch(/repertoire\/@nrslib\/takt-fullstack$/);
}); });
it('should prepend @ before owner name in the path', () => { it('should prepend @ before owner name in the path', () => {
// Given: owner without @ prefix // Given: owner without @ prefix
// When: path is built // When: path is built
const dir = getEnsemblePackageDir('myowner', 'myrepo'); const dir = getRepertoirePackageDir('myowner', 'myrepo');
// Then: @ is included before owner in the path // Then: @ is included before owner in the path
const normalized = dir.replace(/\\/g, '/'); const normalized = dir.replace(/\\/g, '/');

View File

@ -1,5 +1,5 @@
/** /**
* Tests for takt-package.yaml parsing and validation. * Tests for takt-repertoire.yaml parsing and validation.
* *
* Covers: * Covers:
* - Full field parsing (description, path, takt.min_version) * - Full field parsing (description, path, takt.min_version)
@ -14,23 +14,23 @@ import { mkdtempSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from 'node
import { join } from 'node:path'; import { join } from 'node:path';
import { tmpdir } from 'node:os'; import { tmpdir } from 'node:os';
import { import {
parseTaktPackConfig, parseTaktRepertoireConfig,
validateTaktPackPath, validateTaktRepertoirePath,
validateMinVersion, validateMinVersion,
isVersionCompatible, isVersionCompatible,
checkPackageHasContent, checkPackageHasContent,
checkPackageHasContentWithContext, checkPackageHasContentWithContext,
validateRealpathInsideRoot, validateRealpathInsideRoot,
resolvePackConfigPath, resolveRepertoireConfigPath,
} from '../../features/ensemble/takt-pack-config.js'; } from '../../features/repertoire/takt-repertoire-config.js';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// parseTaktPackConfig // parseTaktRepertoireConfig
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
describe('parseTaktPackConfig', () => { describe('parseTaktRepertoireConfig', () => {
it('should parse all fields when present', () => { it('should parse all fields when present', () => {
// Given: a complete takt-package.yaml content // Given: a complete takt-repertoire.yaml content
const yaml = ` const yaml = `
description: My package description: My package
path: takt path: takt
@ -39,7 +39,7 @@ takt:
`.trim(); `.trim();
// When: parsed // When: parsed
const config = parseTaktPackConfig(yaml); const config = parseTaktRepertoireConfig(yaml);
// Then: all fields are populated // Then: all fields are populated
expect(config.description).toBe('My package'); expect(config.description).toBe('My package');
@ -48,11 +48,11 @@ takt:
}); });
it('should default path to "." when omitted', () => { it('should default path to "." when omitted', () => {
// Given: takt-package.yaml with no path field // Given: takt-repertoire.yaml with no path field
const yaml = `description: No path field`; const yaml = `description: No path field`;
// When: parsed // When: parsed
const config = parseTaktPackConfig(yaml); const config = parseTaktRepertoireConfig(yaml);
// Then: path defaults to "." // Then: path defaults to "."
expect(config.path).toBe('.'); expect(config.path).toBe('.');
@ -63,7 +63,7 @@ takt:
const yaml = ''; const yaml = '';
// When: parsed // When: parsed
const config = parseTaktPackConfig(yaml); const config = parseTaktRepertoireConfig(yaml);
// Then: defaults are applied // Then: defaults are applied
expect(config.path).toBe('.'); expect(config.path).toBe('.');
@ -76,7 +76,7 @@ takt:
const yaml = 'description: セキュリティレビュー用ファセット集'; const yaml = 'description: セキュリティレビュー用ファセット集';
// When: parsed // When: parsed
const config = parseTaktPackConfig(yaml); const config = parseTaktRepertoireConfig(yaml);
// Then: description is set, path defaults to "." // Then: description is set, path defaults to "."
expect(config.description).toBe('セキュリティレビュー用ファセット集'); expect(config.description).toBe('セキュリティレビュー用ファセット集');
@ -88,7 +88,7 @@ takt:
const yaml = 'path: pkg/takt'; const yaml = 'path: pkg/takt';
// When: parsed // When: parsed
const config = parseTaktPackConfig(yaml); const config = parseTaktRepertoireConfig(yaml);
// Then: path is preserved as-is // Then: path is preserved as-is
expect(config.path).toBe('pkg/takt'); expect(config.path).toBe('pkg/takt');
@ -96,49 +96,49 @@ takt:
}); });
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// validateTaktPackPath // validateTaktRepertoirePath
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
describe('validateTaktPackPath', () => { describe('validateTaktRepertoirePath', () => {
it('should accept "." (current directory)', () => { it('should accept "." (current directory)', () => {
// Given: default path // Given: default path
// When: validated // When: validated
// Then: no error thrown // Then: no error thrown
expect(() => validateTaktPackPath('.')).not.toThrow(); expect(() => validateTaktRepertoirePath('.')).not.toThrow();
}); });
it('should accept simple relative path "takt"', () => { it('should accept simple relative path "takt"', () => {
expect(() => validateTaktPackPath('takt')).not.toThrow(); expect(() => validateTaktRepertoirePath('takt')).not.toThrow();
}); });
it('should accept nested relative path "pkg/takt"', () => { it('should accept nested relative path "pkg/takt"', () => {
expect(() => validateTaktPackPath('pkg/takt')).not.toThrow(); expect(() => validateTaktRepertoirePath('pkg/takt')).not.toThrow();
}); });
it('should reject absolute path starting with "/"', () => { it('should reject absolute path starting with "/"', () => {
// Given: absolute path // Given: absolute path
// When: validated // When: validated
// Then: throws an error // Then: throws an error
expect(() => validateTaktPackPath('/etc/passwd')).toThrow(); expect(() => validateTaktRepertoirePath('/etc/passwd')).toThrow();
}); });
it('should reject path starting with "~"', () => { it('should reject path starting with "~"', () => {
// Given: home-relative path // Given: home-relative path
expect(() => validateTaktPackPath('~/takt')).toThrow(); expect(() => validateTaktRepertoirePath('~/takt')).toThrow();
}); });
it('should reject path containing ".." segment', () => { it('should reject path containing ".." segment', () => {
// Given: path with directory traversal // Given: path with directory traversal
expect(() => validateTaktPackPath('../outside')).toThrow(); expect(() => validateTaktRepertoirePath('../outside')).toThrow();
}); });
it('should reject path with ".." in middle segment', () => { it('should reject path with ".." in middle segment', () => {
// Given: path with ".." embedded // Given: path with ".." embedded
expect(() => validateTaktPackPath('takt/../etc')).toThrow(); expect(() => validateTaktRepertoirePath('takt/../etc')).toThrow();
}); });
it('should reject "../../etc" (multiple traversal)', () => { it('should reject "../../etc" (multiple traversal)', () => {
expect(() => validateTaktPackPath('../../etc')).toThrow(); expect(() => validateTaktRepertoirePath('../../etc')).toThrow();
}); });
}); });
@ -234,7 +234,7 @@ describe('checkPackageHasContent', () => {
let tempDir: string; let tempDir: string;
beforeEach(() => { beforeEach(() => {
tempDir = mkdtempSync(join(tmpdir(), 'takt-pack-content-')); tempDir = mkdtempSync(join(tmpdir(), 'takt-repertoire-content-'));
}); });
afterEach(() => { afterEach(() => {
@ -249,7 +249,7 @@ describe('checkPackageHasContent', () => {
}); });
it('should include manifest/path/hint details in contextual error', () => { it('should include manifest/path/hint details in contextual error', () => {
const manifestPath = join(tempDir, '.takt', 'takt-package.yaml'); const manifestPath = join(tempDir, '.takt', 'takt-repertoire.yaml');
expect(() => checkPackageHasContentWithContext(tempDir, { expect(() => checkPackageHasContentWithContext(tempDir, {
manifestPath, manifestPath,
configuredPath: '.', configuredPath: '.',
@ -344,10 +344,10 @@ describe('validateRealpathInsideRoot', () => {
}); });
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// resolvePackConfigPath (takt-package.yaml search order) // resolveRepertoireConfigPath (takt-repertoire.yaml search order)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
describe('resolvePackConfigPath', () => { describe('resolveRepertoireConfigPath', () => {
let extractDir: string; let extractDir: string;
beforeEach(() => { beforeEach(() => {
@ -358,47 +358,47 @@ describe('resolvePackConfigPath', () => {
rmSync(extractDir, { recursive: true, force: true }); rmSync(extractDir, { recursive: true, force: true });
}); });
it('should return .takt/takt-package.yaml when only that path exists', () => { it('should return .takt/takt-repertoire.yaml when only that path exists', () => {
// Given: only .takt/takt-package.yaml exists // Given: only .takt/takt-repertoire.yaml exists
const taktDir = join(extractDir, '.takt'); const taktDir = join(extractDir, '.takt');
mkdirSync(taktDir, { recursive: true }); mkdirSync(taktDir, { recursive: true });
writeFileSync(join(taktDir, 'takt-package.yaml'), 'description: dot-takt'); writeFileSync(join(taktDir, 'takt-repertoire.yaml'), 'description: dot-takt');
// When: resolved // When: resolved
const result = resolvePackConfigPath(extractDir); const result = resolveRepertoireConfigPath(extractDir);
// Then: .takt/takt-package.yaml is returned // Then: .takt/takt-repertoire.yaml is returned
expect(result).toBe(join(extractDir, '.takt', 'takt-package.yaml')); expect(result).toBe(join(extractDir, '.takt', 'takt-repertoire.yaml'));
}); });
it('should return root takt-package.yaml when only that path exists', () => { it('should return root takt-repertoire.yaml when only that path exists', () => {
// Given: only root takt-package.yaml exists // Given: only root takt-repertoire.yaml exists
writeFileSync(join(extractDir, 'takt-package.yaml'), 'description: root'); writeFileSync(join(extractDir, 'takt-repertoire.yaml'), 'description: root');
// When: resolved // When: resolved
const result = resolvePackConfigPath(extractDir); const result = resolveRepertoireConfigPath(extractDir);
// Then: root takt-package.yaml is returned // Then: root takt-repertoire.yaml is returned
expect(result).toBe(join(extractDir, 'takt-package.yaml')); expect(result).toBe(join(extractDir, 'takt-repertoire.yaml'));
}); });
it('should prefer .takt/takt-package.yaml when both paths exist', () => { it('should prefer .takt/takt-repertoire.yaml when both paths exist', () => {
// Given: both .takt/takt-package.yaml and root takt-package.yaml exist // Given: both .takt/takt-repertoire.yaml and root takt-repertoire.yaml exist
const taktDir = join(extractDir, '.takt'); const taktDir = join(extractDir, '.takt');
mkdirSync(taktDir, { recursive: true }); mkdirSync(taktDir, { recursive: true });
writeFileSync(join(taktDir, 'takt-package.yaml'), 'description: dot-takt'); writeFileSync(join(taktDir, 'takt-repertoire.yaml'), 'description: dot-takt');
writeFileSync(join(extractDir, 'takt-package.yaml'), 'description: root'); writeFileSync(join(extractDir, 'takt-repertoire.yaml'), 'description: root');
// When: resolved // When: resolved
const result = resolvePackConfigPath(extractDir); const result = resolveRepertoireConfigPath(extractDir);
// Then: .takt/takt-package.yaml takes precedence // Then: .takt/takt-repertoire.yaml takes precedence
expect(result).toBe(join(extractDir, '.takt', 'takt-package.yaml')); expect(result).toBe(join(extractDir, '.takt', 'takt-repertoire.yaml'));
}); });
it('should throw when neither path exists', () => { it('should throw when neither path exists', () => {
// Given: empty extract directory // Given: empty extract directory
// When / Then: throws an error // When / Then: throws an error
expect(() => resolvePackConfigPath(extractDir)).toThrow('takt-package.yaml not found in'); expect(() => resolveRepertoireConfigPath(extractDir)).toThrow('takt-repertoire.yaml not found in');
}); });
}); });

View File

@ -11,7 +11,7 @@
*/ */
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { parseTarVerboseListing } from '../../features/ensemble/tar-parser.js'; import { parseTarVerboseListing } from '../../features/repertoire/tar-parser.js';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Helpers to build realistic tar verbose lines // Helpers to build realistic tar verbose lines

View File

@ -183,12 +183,12 @@ describe('resolveAutoPr default in selectAndExecuteTask', () => {
expect(mockSelectPiece).toHaveBeenCalledWith('/project'); expect(mockSelectPiece).toHaveBeenCalledWith('/project');
}); });
it('should accept ensemble scoped piece override when it exists', async () => { it('should accept repertoire scoped piece override when it exists', async () => {
mockLoadPieceByIdentifier.mockReturnValueOnce({ name: '@nrslib/takt-packages/critical-thinking' } as never); mockLoadPieceByIdentifier.mockReturnValueOnce({ name: '@nrslib/takt-ensembles/critical-thinking' } as never);
const selected = await determinePiece('/project', '@nrslib/takt-packages/critical-thinking'); const selected = await determinePiece('/project', '@nrslib/takt-ensembles/critical-thinking');
expect(selected).toBe('@nrslib/takt-packages/critical-thinking'); expect(selected).toBe('@nrslib/takt-ensembles/critical-thinking');
}); });
it('should fail task record when executeTask throws', async () => { it('should fail task record when executeTask throws', async () => {

View File

@ -1,7 +1,7 @@
/** /**
* Unit tests for takt-package.yaml schema validation. * Unit tests for takt-repertoire.yaml schema validation.
* *
* Target: src/features/ensemble/takt-pack-config.ts * Target: src/features/repertoire/takt-repertoire-config.ts
* *
* Schema rules under test: * Schema rules under test:
* - description: optional * - description: optional
@ -13,46 +13,46 @@
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { import {
parseTaktPackConfig, parseTaktRepertoireConfig,
validateTaktPackPath, validateTaktRepertoirePath,
validateMinVersion, validateMinVersion,
} from '../features/ensemble/takt-pack-config.js'; } from '../features/repertoire/takt-repertoire-config.js';
describe('takt-package.yaml schema: description field', () => { describe('takt-repertoire.yaml schema: description field', () => {
it('should accept schema without description field', () => { it('should accept schema without description field', () => {
const config = parseTaktPackConfig(''); const config = parseTaktRepertoireConfig('');
expect(config.description).toBeUndefined(); expect(config.description).toBeUndefined();
}); });
}); });
describe('takt-package.yaml schema: path field', () => { describe('takt-repertoire.yaml schema: path field', () => {
it('should default path to "." when not specified', () => { it('should default path to "." when not specified', () => {
const config = parseTaktPackConfig(''); const config = parseTaktRepertoireConfig('');
expect(config.path).toBe('.'); expect(config.path).toBe('.');
}); });
it('should reject path starting with "/" (absolute path)', () => { it('should reject path starting with "/" (absolute path)', () => {
expect(() => validateTaktPackPath('/foo')).toThrow(); expect(() => validateTaktRepertoirePath('/foo')).toThrow();
}); });
it('should reject path starting with "~" (tilde-absolute path)', () => { it('should reject path starting with "~" (tilde-absolute path)', () => {
expect(() => validateTaktPackPath('~/foo')).toThrow(); expect(() => validateTaktRepertoirePath('~/foo')).toThrow();
}); });
it('should reject path with ".." segment traversing outside repository', () => { it('should reject path with ".." segment traversing outside repository', () => {
expect(() => validateTaktPackPath('../outside')).toThrow(); expect(() => validateTaktRepertoirePath('../outside')).toThrow();
}); });
it('should reject path with embedded ".." segments leading outside repository', () => { it('should reject path with embedded ".." segments leading outside repository', () => {
expect(() => validateTaktPackPath('sub/../../../outside')).toThrow(); expect(() => validateTaktRepertoirePath('sub/../../../outside')).toThrow();
}); });
it('should accept valid relative path "sub/dir"', () => { it('should accept valid relative path "sub/dir"', () => {
expect(() => validateTaktPackPath('sub/dir')).not.toThrow(); expect(() => validateTaktRepertoirePath('sub/dir')).not.toThrow();
}); });
}); });
describe('takt-package.yaml schema: takt.min_version field', () => { describe('takt-repertoire.yaml schema: takt.min_version field', () => {
it('should accept min_version "0.5.0" (valid semver)', () => { it('should accept min_version "0.5.0" (valid semver)', () => {
expect(() => validateMinVersion('0.5.0')).not.toThrow(); expect(() => validateMinVersion('0.5.0')).not.toThrow();
}); });

View File

@ -15,9 +15,9 @@ import { showCatalog } from '../../features/catalog/index.js';
import { computeReviewMetrics, formatReviewMetrics, parseSinceDuration, purgeOldEvents } from '../../features/analytics/index.js'; import { computeReviewMetrics, formatReviewMetrics, parseSinceDuration, purgeOldEvents } from '../../features/analytics/index.js';
import { program, resolvedCwd } from './program.js'; import { program, resolvedCwd } from './program.js';
import { resolveAgentOverrides } from './helpers.js'; import { resolveAgentOverrides } from './helpers.js';
import { ensembleAddCommand } from '../../commands/ensemble/add.js'; import { repertoireAddCommand } from '../../commands/repertoire/add.js';
import { ensembleRemoveCommand } from '../../commands/ensemble/remove.js'; import { repertoireRemoveCommand } from '../../commands/repertoire/remove.js';
import { ensembleListCommand } from '../../commands/ensemble/list.js'; import { repertoireListCommand } from '../../commands/repertoire/list.js';
program program
.command('run') .command('run')
@ -177,29 +177,29 @@ program
} }
}); });
const ensemble = program const repertoire = program
.command('ensemble') .command('repertoire')
.description('Manage ensemble packages'); .description('Manage repertoire packages');
ensemble repertoire
.command('add') .command('add')
.description('Install an ensemble package from GitHub') .description('Install a repertoire package from GitHub')
.argument('<spec>', 'Package spec (e.g. github:{owner}/{repo}@{ref})') .argument('<spec>', 'Package spec (e.g. github:{owner}/{repo}@{ref})')
.action(async (spec: string) => { .action(async (spec: string) => {
await ensembleAddCommand(spec); await repertoireAddCommand(spec);
}); });
ensemble repertoire
.command('remove') .command('remove')
.description('Remove an installed ensemble package') .description('Remove an installed repertoire package')
.argument('<scope>', 'Package scope (e.g. @{owner}/{repo})') .argument('<scope>', 'Package scope (e.g. @{owner}/{repo})')
.action(async (scope: string) => { .action(async (scope: string) => {
await ensembleRemoveCommand(scope); await repertoireRemoveCommand(scope);
}); });
ensemble repertoire
.command('list') .command('list')
.description('List installed ensemble packages') .description('List installed repertoire packages')
.action(async () => { .action(async () => {
await ensembleListCommand(); await repertoireListCommand();
}); });

View File

@ -1,9 +1,9 @@
/** /**
* takt ensemble add install an ensemble package from GitHub. * takt repertoire add install a repertoire package from GitHub.
* *
* Usage: * Usage:
* takt ensemble add github:{owner}/{repo}@{ref} * takt repertoire add github:{owner}/{repo}@{ref}
* takt ensemble add github:{owner}/{repo} (uses default branch) * takt repertoire add github:{owner}/{repo} (uses default branch)
*/ */
import { mkdirSync, copyFileSync, existsSync, readFileSync, writeFileSync, rmSync } from 'node:fs'; import { mkdirSync, copyFileSync, existsSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
@ -12,24 +12,24 @@ import { tmpdir } from 'node:os';
import { execFileSync } from 'node:child_process'; import { execFileSync } from 'node:child_process';
import { createRequire } from 'node:module'; import { createRequire } from 'node:module';
import { stringify as stringifyYaml } from 'yaml'; import { stringify as stringifyYaml } from 'yaml';
import { getEnsemblePackageDir } from '../../infra/config/paths.js'; import { getRepertoirePackageDir } from '../../infra/config/paths.js';
import { parseGithubSpec } from '../../features/ensemble/github-spec.js'; import { parseGithubSpec } from '../../features/repertoire/github-spec.js';
import { import {
parseTaktPackConfig, parseTaktRepertoireConfig,
validateTaktPackPath, validateTaktRepertoirePath,
validateMinVersion, validateMinVersion,
isVersionCompatible, isVersionCompatible,
checkPackageHasContentWithContext, checkPackageHasContentWithContext,
validateRealpathInsideRoot, validateRealpathInsideRoot,
resolvePackConfigPath, resolveRepertoireConfigPath,
} from '../../features/ensemble/takt-pack-config.js'; } from '../../features/repertoire/takt-repertoire-config.js';
import { collectCopyTargets } from '../../features/ensemble/file-filter.js'; import { collectCopyTargets } from '../../features/repertoire/file-filter.js';
import { parseTarVerboseListing } from '../../features/ensemble/tar-parser.js'; import { parseTarVerboseListing } from '../../features/repertoire/tar-parser.js';
import { resolveRef } from '../../features/ensemble/github-ref-resolver.js'; import { resolveRef } from '../../features/repertoire/github-ref-resolver.js';
import { atomicReplace, cleanupResiduals } from '../../features/ensemble/atomic-update.js'; import { atomicReplace, cleanupResiduals } from '../../features/repertoire/atomic-update.js';
import { generateLockFile, extractCommitSha } from '../../features/ensemble/lock-file.js'; import { generateLockFile, extractCommitSha } from '../../features/repertoire/lock-file.js';
import { TAKT_PACKAGE_MANIFEST_FILENAME } from '../../features/ensemble/constants.js'; import { TAKT_REPERTOIRE_MANIFEST_FILENAME, TAKT_REPERTOIRE_LOCK_FILENAME } from '../../features/repertoire/constants.js';
import { summarizeFacetsByType, detectEditPieces, formatEditPieceWarnings } from '../../features/ensemble/pack-summary.js'; import { summarizeFacetsByType, detectEditPieces, formatEditPieceWarnings } from '../../features/repertoire/pack-summary.js';
import { confirm } from '../../shared/prompt/index.js'; import { confirm } from '../../shared/prompt/index.js';
import { info, success } from '../../shared/ui/index.js'; import { info, success } from '../../shared/ui/index.js';
import { createLogger, getErrorMessage } from '../../shared/utils/index.js'; import { createLogger, getErrorMessage } from '../../shared/utils/index.js';
@ -37,9 +37,9 @@ import { createLogger, getErrorMessage } from '../../shared/utils/index.js';
const require = createRequire(import.meta.url); const require = createRequire(import.meta.url);
const { version: TAKT_VERSION } = require('../../../package.json') as { version: string }; const { version: TAKT_VERSION } = require('../../../package.json') as { version: string };
const log = createLogger('ensemble-add'); const log = createLogger('repertoire-add');
export async function ensembleAddCommand(spec: string): Promise<void> { export async function repertoireAddCommand(spec: string): Promise<void> {
const { owner, repo, ref: specRef } = parseGithubSpec(spec); const { owner, repo, ref: specRef } = parseGithubSpec(spec);
try { try {
@ -93,11 +93,11 @@ export async function ensembleAddCommand(spec: string): Promise<void> {
); );
} }
const packConfigPath = resolvePackConfigPath(tmpExtractDir); const packConfigPath = resolveRepertoireConfigPath(tmpExtractDir);
const packConfigYaml = readFileSync(packConfigPath, 'utf-8'); const packConfigYaml = readFileSync(packConfigPath, 'utf-8');
const config = parseTaktPackConfig(packConfigYaml); const config = parseTaktRepertoireConfig(packConfigYaml);
validateTaktPackPath(config.path); validateTaktRepertoirePath(config.path);
if (config.takt?.min_version) { if (config.takt?.min_version) {
validateMinVersion(config.takt.min_version); validateMinVersion(config.takt.min_version);
@ -157,7 +157,7 @@ export async function ensembleAddCommand(spec: string): Promise<void> {
return; return;
} }
const packageDir = getEnsemblePackageDir(owner, repo); const packageDir = getRepertoirePackageDir(owner, repo);
if (existsSync(packageDir)) { if (existsSync(packageDir)) {
const overwrite = await confirm( const overwrite = await confirm(
@ -180,7 +180,7 @@ export async function ensembleAddCommand(spec: string): Promise<void> {
mkdirSync(dirname(destFile), { recursive: true }); mkdirSync(dirname(destFile), { recursive: true });
copyFileSync(target.absolutePath, destFile); copyFileSync(target.absolutePath, destFile);
} }
copyFileSync(packConfigPath, join(packageDir, TAKT_PACKAGE_MANIFEST_FILENAME)); copyFileSync(packConfigPath, join(packageDir, TAKT_REPERTOIRE_MANIFEST_FILENAME));
const lock = generateLockFile({ const lock = generateLockFile({
source: `github:${owner}/${repo}`, source: `github:${owner}/${repo}`,
@ -188,7 +188,7 @@ export async function ensembleAddCommand(spec: string): Promise<void> {
commitSha, commitSha,
importedAt: new Date(), importedAt: new Date(),
}); });
writeFileSync(join(packageDir, '.takt-pack-lock.yaml'), stringifyYaml(lock)); writeFileSync(join(packageDir, TAKT_REPERTOIRE_LOCK_FILENAME), stringifyYaml(lock));
}, },
}); });

View File

@ -1,13 +1,13 @@
/** /**
* takt ensemble list list installed ensemble packages. * takt repertoire list list installed repertoire packages.
*/ */
import { getEnsembleDir } from '../../infra/config/paths.js'; import { getRepertoireDir } from '../../infra/config/paths.js';
import { listPackages } from '../../features/ensemble/list.js'; import { listPackages } from '../../features/repertoire/list.js';
import { info } from '../../shared/ui/index.js'; import { info } from '../../shared/ui/index.js';
export async function ensembleListCommand(): Promise<void> { export async function repertoireListCommand(): Promise<void> {
const packages = listPackages(getEnsembleDir()); const packages = listPackages(getRepertoireDir());
if (packages.length === 0) { if (packages.length === 0) {
info('インストール済みパッケージはありません'); info('インストール済みパッケージはありません');

View File

@ -1,15 +1,15 @@
/** /**
* takt ensemble remove remove an installed ensemble package. * takt repertoire remove remove an installed repertoire package.
*/ */
import { rmSync, existsSync } from 'node:fs'; import { rmSync, existsSync } from 'node:fs';
import { join } from 'node:path'; import { join } from 'node:path';
import { getEnsembleDir, getEnsemblePackageDir, getGlobalConfigDir, getGlobalPiecesDir, getProjectPiecesDir } from '../../infra/config/paths.js'; import { getRepertoireDir, getRepertoirePackageDir, getGlobalConfigDir, getGlobalPiecesDir, getProjectPiecesDir } from '../../infra/config/paths.js';
import { findScopeReferences, shouldRemoveOwnerDir } from '../../features/ensemble/remove.js'; import { findScopeReferences, shouldRemoveOwnerDir } from '../../features/repertoire/remove.js';
import { confirm } from '../../shared/prompt/index.js'; import { confirm } from '../../shared/prompt/index.js';
import { info, success } from '../../shared/ui/index.js'; import { info, success } from '../../shared/ui/index.js';
export async function ensembleRemoveCommand(scope: string): Promise<void> { export async function repertoireRemoveCommand(scope: string): Promise<void> {
if (!scope.startsWith('@')) { if (!scope.startsWith('@')) {
throw new Error(`Invalid scope: "${scope}". Expected @{owner}/{repo}`); throw new Error(`Invalid scope: "${scope}". Expected @{owner}/{repo}`);
} }
@ -21,8 +21,8 @@ export async function ensembleRemoveCommand(scope: string): Promise<void> {
const owner = withoutAt.slice(0, slashIdx); const owner = withoutAt.slice(0, slashIdx);
const repo = withoutAt.slice(slashIdx + 1); const repo = withoutAt.slice(slashIdx + 1);
const ensembleDir = getEnsembleDir(); const repertoireDir = getRepertoireDir();
const packageDir = getEnsemblePackageDir(owner, repo); const packageDir = getRepertoirePackageDir(owner, repo);
if (!existsSync(packageDir)) { if (!existsSync(packageDir)) {
throw new Error(`Package not found: ${scope}`); throw new Error(`Package not found: ${scope}`);
@ -47,7 +47,7 @@ export async function ensembleRemoveCommand(scope: string): Promise<void> {
rmSync(packageDir, { recursive: true, force: true }); rmSync(packageDir, { recursive: true, force: true });
const ownerDir = join(ensembleDir, `@${owner}`); const ownerDir = join(repertoireDir, `@${owner}`);
if (shouldRemoveOwnerDir(ownerDir, repo)) { if (shouldRemoveOwnerDir(ownerDir, repo)) {
rmSync(ownerDir, { recursive: true, force: true }); rmSync(ownerDir, { recursive: true, force: true });
} }

View File

@ -1,10 +1,10 @@
/** /**
* @scope reference resolution utilities for TAKT ensemble packages. * @scope reference resolution utilities for TAKT repertoire packages.
* *
* Provides: * Provides:
* - isScopeRef(): detect @{owner}/{repo}/{facet-name} format * - isScopeRef(): detect @{owner}/{repo}/{facet-name} format
* - parseScopeRef(): parse and normalize components * - parseScopeRef(): parse and normalize components
* - resolveScopeRef(): build file path in ensemble directory * - resolveScopeRef(): build file path in repertoire directory
* - validateScopeOwner/Repo/FacetName(): name constraint validation * - validateScopeOwner/Repo/FacetName(): name constraint validation
*/ */
@ -51,22 +51,22 @@ export function parseScopeRef(ref: string): ScopeRef {
} }
/** /**
* Resolve a scope reference to a file path in the ensemble directory. * Resolve a scope reference to a file path in the repertoire directory.
* *
* Path: {ensembleDir}/@{owner}/{repo}/facets/{facetType}/{name}.md * Path: {repertoireDir}/@{owner}/{repo}/facets/{facetType}/{name}.md
* *
* @param scopeRef - parsed scope reference * @param scopeRef - parsed scope reference
* @param facetType - e.g. "personas", "policies", "knowledge" * @param facetType - e.g. "personas", "policies", "knowledge"
* @param ensembleDir - root ensemble directory (e.g. ~/.takt/ensemble) * @param repertoireDir - root repertoire directory (e.g. ~/.takt/repertoire)
* @returns Absolute path to the facet file. * @returns Absolute path to the facet file.
*/ */
export function resolveScopeRef( export function resolveScopeRef(
scopeRef: ScopeRef, scopeRef: ScopeRef,
facetType: string, facetType: string,
ensembleDir: string, repertoireDir: string,
): string { ): string {
return join( return join(
ensembleDir, repertoireDir,
`@${scopeRef.owner}`, `@${scopeRef.owner}`,
scopeRef.repo, scopeRef.repo,
'facets', 'facets',

View File

@ -1,6 +0,0 @@
/**
* Shared constants for ensemble package manifest handling.
*/
/** Manifest filename inside a package repository and installed package directory. */
export const TAKT_PACKAGE_MANIFEST_FILENAME = 'takt-package.yaml';

View File

@ -0,0 +1,12 @@
/**
* Shared constants for repertoire package manifest handling.
*/
/** Directory name for the repertoire packages dir (~/.takt/repertoire). */
export const REPERTOIRE_DIR_NAME = 'repertoire';
/** Manifest filename inside a package repository and installed package directory. */
export const TAKT_REPERTOIRE_MANIFEST_FILENAME = 'takt-repertoire.yaml';
/** Lock file filename inside an installed package directory. */
export const TAKT_REPERTOIRE_LOCK_FILENAME = '.takt-repertoire-lock.yaml';

View File

@ -1,5 +1,5 @@
/** /**
* File filtering for ensemble package copy operations. * File filtering for repertoire package copy operations.
* *
* Security constraints: * Security constraints:
* - Only .md, .yaml, .yml files are copied * - Only .md, .yaml, .yml files are copied
@ -13,9 +13,9 @@ import { lstatSync, readdirSync, type Stats } from 'node:fs';
import { join, extname, relative } from 'node:path'; import { join, extname, relative } from 'node:path';
import { createLogger } from '../../shared/utils/debug.js'; import { createLogger } from '../../shared/utils/debug.js';
const log = createLogger('ensemble-file-filter'); const log = createLogger('repertoire-file-filter');
/** Allowed file extensions for ensemble package files. */ /** Allowed file extensions for repertoire package files. */
export const ALLOWED_EXTENSIONS = ['.md', '.yaml', '.yml'] as const; export const ALLOWED_EXTENSIONS = ['.md', '.yaml', '.yml'] as const;
/** Top-level directories that are copied from a package. */ /** Top-level directories that are copied from a package. */
@ -107,7 +107,7 @@ function collectFromDir(
* Symbolic links are skipped. Files over MAX_FILE_SIZE are skipped. * Symbolic links are skipped. Files over MAX_FILE_SIZE are skipped.
* Throws if total file count exceeds MAX_FILE_COUNT. * Throws if total file count exceeds MAX_FILE_COUNT.
* *
* @param packageRoot - absolute path to the package root (respects takt-package.yaml path) * @param packageRoot - absolute path to the package root (respects takt-repertoire.yaml path)
*/ */
export function collectCopyTargets(packageRoot: string): CopyTarget[] { export function collectCopyTargets(packageRoot: string): CopyTarget[] {
const targets: CopyTarget[] = []; const targets: CopyTarget[] = [];

View File

@ -1,5 +1,5 @@
/** /**
* GitHub ref resolver for ensemble add command. * GitHub ref resolver for repertoire add command.
* *
* Resolves the ref for a GitHub package installation. * Resolves the ref for a GitHub package installation.
* When the spec omits @{ref}, queries the GitHub API for the default branch. * When the spec omits @{ref}, queries the GitHub API for the default branch.

View File

@ -1,5 +1,5 @@
/** /**
* GitHub package spec parser for ensemble add command. * GitHub package spec parser for repertoire add command.
* *
* Parses "github:{owner}/{repo}@{ref}" format into structured components. * Parses "github:{owner}/{repo}@{ref}" format into structured components.
* The @{ref} part is optional; when omitted, ref is undefined and the caller * The @{ref} part is optional; when omitted, ref is undefined and the caller

View File

@ -1,18 +1,18 @@
/** /**
* Ensemble package listing. * Repertoire package listing.
* *
* Scans the ensemble directory for installed packages and reads their * Scans the repertoire directory for installed packages and reads their
* metadata (description, ref, truncated commit SHA) for display. * metadata (description, ref, truncated commit SHA) for display.
*/ */
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'; import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
import { join } from 'node:path'; import { join } from 'node:path';
import { parseTaktPackConfig } from './takt-pack-config.js'; import { parseTaktRepertoireConfig } from './takt-repertoire-config.js';
import { parseLockFile } from './lock-file.js'; import { parseLockFile } from './lock-file.js';
import { TAKT_PACKAGE_MANIFEST_FILENAME } from './constants.js'; import { TAKT_REPERTOIRE_MANIFEST_FILENAME, TAKT_REPERTOIRE_LOCK_FILENAME } from './constants.js';
import { createLogger, getErrorMessage } from '../../shared/utils/index.js'; import { createLogger, getErrorMessage } from '../../shared/utils/index.js';
const log = createLogger('ensemble-list'); const log = createLogger('repertoire-list');
export interface PackageInfo { export interface PackageInfo {
/** e.g. "@nrslib/takt-fullstack" */ /** e.g. "@nrslib/takt-fullstack" */
@ -30,13 +30,13 @@ export interface PackageInfo {
* @param scope - e.g. "@nrslib/takt-fullstack" * @param scope - e.g. "@nrslib/takt-fullstack"
*/ */
export function readPackageInfo(packageDir: string, scope: string): PackageInfo { export function readPackageInfo(packageDir: string, scope: string): PackageInfo {
const packConfigPath = join(packageDir, TAKT_PACKAGE_MANIFEST_FILENAME); const packConfigPath = join(packageDir, TAKT_REPERTOIRE_MANIFEST_FILENAME);
const lockPath = join(packageDir, '.takt-pack-lock.yaml'); const lockPath = join(packageDir, TAKT_REPERTOIRE_LOCK_FILENAME);
const configYaml = existsSync(packConfigPath) const configYaml = existsSync(packConfigPath)
? readFileSync(packConfigPath, 'utf-8') ? readFileSync(packConfigPath, 'utf-8')
: ''; : '';
const config = parseTaktPackConfig(configYaml); const config = parseTaktRepertoireConfig(configYaml);
const lockYaml = existsSync(lockPath) const lockYaml = existsSync(lockPath)
? readFileSync(lockPath, 'utf-8') ? readFileSync(lockPath, 'utf-8')
@ -52,25 +52,25 @@ export function readPackageInfo(packageDir: string, scope: string): PackageInfo
} }
/** /**
* List all installed packages under the ensemble directory. * List all installed packages under the repertoire directory.
* *
* Directory structure: * Directory structure:
* ensembleDir/ * repertoireDir/
* @{owner}/ * @{owner}/
* {repo}/ * {repo}/
* takt-package.yaml * takt-repertoire.yaml
* .takt-pack-lock.yaml * .takt-repertoire-lock.yaml
* *
* @param ensembleDir - absolute path to the ensemble root (~/.takt/ensemble) * @param repertoireDir - absolute path to the repertoire root (~/.takt/repertoire)
*/ */
export function listPackages(ensembleDir: string): PackageInfo[] { export function listPackages(repertoireDir: string): PackageInfo[] {
if (!existsSync(ensembleDir)) return []; if (!existsSync(repertoireDir)) return [];
const packages: PackageInfo[] = []; const packages: PackageInfo[] = [];
for (const ownerEntry of readdirSync(ensembleDir)) { for (const ownerEntry of readdirSync(repertoireDir)) {
if (!ownerEntry.startsWith('@')) continue; if (!ownerEntry.startsWith('@')) continue;
const ownerDir = join(ensembleDir, ownerEntry); const ownerDir = join(repertoireDir, ownerEntry);
try { if (!statSync(ownerDir).isDirectory()) continue; } catch (e) { log.debug(`stat failed for ${ownerDir}: ${getErrorMessage(e)}`); continue; } try { if (!statSync(ownerDir).isDirectory()) continue; } catch (e) { log.debug(`stat failed for ${ownerDir}: ${getErrorMessage(e)}`); continue; }
for (const repoEntry of readdirSync(ownerDir)) { for (const repoEntry of readdirSync(ownerDir)) {

View File

@ -1,7 +1,7 @@
/** /**
* Lock file generation and parsing for ensemble packages. * Lock file generation and parsing for repertoire packages.
* *
* The .takt-pack-lock.yaml records the installation provenance: * The .takt-repertoire-lock.yaml records the installation provenance:
* source: github:{owner}/{repo} * source: github:{owner}/{repo}
* ref: tag or branch (defaults to "HEAD") * ref: tag or branch (defaults to "HEAD")
* commit: full SHA from tarball directory name * commit: full SHA from tarball directory name
@ -59,7 +59,7 @@ export function generateLockFile(params: GenerateLockFileParams): PackageLock {
} }
/** /**
* Parse .takt-pack-lock.yaml content into a PackageLock object. * Parse .takt-repertoire-lock.yaml content into a PackageLock object.
* Returns empty-valued lock when yaml is empty (lock file missing). * Returns empty-valued lock when yaml is empty (lock file missing).
*/ */
export function parseLockFile(yaml: string): PackageLock { export function parseLockFile(yaml: string): PackageLock {

View File

@ -1,5 +1,5 @@
/** /**
* Ensemble package removal helpers. * Repertoire package removal helpers.
* *
* Provides: * Provides:
* - findScopeReferences: scan YAML files for @scope references (for pre-removal warning) * - findScopeReferences: scan YAML files for @scope references (for pre-removal warning)
@ -10,7 +10,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
import { join } from 'node:path'; import { join } from 'node:path';
import { createLogger } from '../../shared/utils/debug.js'; import { createLogger } from '../../shared/utils/debug.js';
const log = createLogger('ensemble-remove'); const log = createLogger('repertoire-remove');
export interface ScopeReference { export interface ScopeReference {
/** Absolute path to the file containing the @scope reference. */ /** Absolute path to the file containing the @scope reference. */

View File

@ -1,5 +1,5 @@
/** /**
* takt-package.yaml parsing and validation. * takt-repertoire.yaml parsing and validation.
* *
* Handles: * Handles:
* - YAML parsing with default values * - YAML parsing with default values
@ -13,9 +13,9 @@
import { existsSync, realpathSync } from 'node:fs'; import { existsSync, realpathSync } from 'node:fs';
import { join } from 'node:path'; import { join } from 'node:path';
import { parse as parseYaml } from 'yaml'; import { parse as parseYaml } from 'yaml';
import { TAKT_PACKAGE_MANIFEST_FILENAME } from './constants.js'; import { TAKT_REPERTOIRE_MANIFEST_FILENAME } from './constants.js';
export interface TaktPackConfig { export interface TaktRepertoireConfig {
description?: string; description?: string;
path: string; path: string;
takt?: { takt?: {
@ -31,10 +31,10 @@ interface PackageContentCheckContext {
const SEMVER_PATTERN = /^\d+\.\d+\.\d+$/; const SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
/** /**
* Parse takt-package.yaml content string into a TaktPackConfig. * Parse takt-repertoire.yaml content string into a TaktRepertoireConfig.
* Applies default path "." when not specified. * Applies default path "." when not specified.
*/ */
export function parseTaktPackConfig(yaml: string): TaktPackConfig { export function parseTaktRepertoireConfig(yaml: string): TaktRepertoireConfig {
const raw = (yaml.trim() ? parseYaml(yaml) : {}) as Record<string, unknown> | null; const raw = (yaml.trim() ? parseYaml(yaml) : {}) as Record<string, unknown> | null;
const data = raw ?? {}; const data = raw ?? {};
@ -56,16 +56,16 @@ export function parseTaktPackConfig(yaml: string): TaktPackConfig {
* *
* Throws on validation failure. * Throws on validation failure.
*/ */
export function validateTaktPackPath(path: string): void { export function validateTaktRepertoirePath(path: string): void {
if (path.startsWith('/')) { if (path.startsWith('/')) {
throw new Error(`${TAKT_PACKAGE_MANIFEST_FILENAME}: path must not be absolute, got "${path}"`); throw new Error(`${TAKT_REPERTOIRE_MANIFEST_FILENAME}: path must not be absolute, got "${path}"`);
} }
if (path.startsWith('~')) { if (path.startsWith('~')) {
throw new Error(`${TAKT_PACKAGE_MANIFEST_FILENAME}: path must not start with "~", got "${path}"`); throw new Error(`${TAKT_REPERTOIRE_MANIFEST_FILENAME}: path must not start with "~", got "${path}"`);
} }
const segments = path.split('/'); const segments = path.split('/');
if (segments.includes('..')) { if (segments.includes('..')) {
throw new Error(`${TAKT_PACKAGE_MANIFEST_FILENAME}: path must not contain ".." segments, got "${path}"`); throw new Error(`${TAKT_REPERTOIRE_MANIFEST_FILENAME}: path must not contain ".." segments, got "${path}"`);
} }
} }
@ -78,7 +78,7 @@ export function validateTaktPackPath(path: string): void {
export function validateMinVersion(version: string): void { export function validateMinVersion(version: string): void {
if (!SEMVER_PATTERN.test(version)) { if (!SEMVER_PATTERN.test(version)) {
throw new Error( throw new Error(
`${TAKT_PACKAGE_MANIFEST_FILENAME}: takt.min_version must match X.Y.Z (no "v" prefix, no pre-release), got "${version}"`, `${TAKT_REPERTOIRE_MANIFEST_FILENAME}: takt.min_version must match X.Y.Z (no "v" prefix, no pre-release), got "${version}"`,
); );
} }
} }
@ -137,7 +137,7 @@ export function checkPackageHasContentWithContext(
const configuredPath = context.configuredPath ?? '.'; const configuredPath = context.configuredPath ?? '.';
const manifestPath = context.manifestPath ?? '(unknown)'; const manifestPath = context.manifestPath ?? '(unknown)';
const hint = configuredPath === '.' const hint = configuredPath === '.'
? `hint: If your package content is under ".takt/", set "path: .takt" in ${TAKT_PACKAGE_MANIFEST_FILENAME}.` ? `hint: If your package content is under ".takt/", set "path: .takt" in ${TAKT_REPERTOIRE_MANIFEST_FILENAME}.`
: `hint: Verify "path: ${configuredPath}" points to a directory containing facets/ or pieces/.`; : `hint: Verify "path: ${configuredPath}" points to a directory containing facets/ or pieces/.`;
throw new Error( throw new Error(
@ -154,24 +154,24 @@ export function checkPackageHasContentWithContext(
} }
/** /**
* Resolve the path to takt-package.yaml within an extracted tarball directory. * Resolve the path to takt-repertoire.yaml within an extracted tarball directory.
* *
* Search order (first found wins): * Search order (first found wins):
* 1. {extractDir}/.takt/takt-package.yaml * 1. {extractDir}/.takt/takt-repertoire.yaml
* 2. {extractDir}/takt-package.yaml * 2. {extractDir}/takt-repertoire.yaml
* *
* @param extractDir - root of the extracted tarball * @param extractDir - root of the extracted tarball
* @throws if neither candidate exists * @throws if neither candidate exists
*/ */
export function resolvePackConfigPath(extractDir: string): string { export function resolveRepertoireConfigPath(extractDir: string): string {
const taktDirPath = join(extractDir, '.takt', TAKT_PACKAGE_MANIFEST_FILENAME); const taktDirPath = join(extractDir, '.takt', TAKT_REPERTOIRE_MANIFEST_FILENAME);
if (existsSync(taktDirPath)) return taktDirPath; if (existsSync(taktDirPath)) return taktDirPath;
const rootPath = join(extractDir, TAKT_PACKAGE_MANIFEST_FILENAME); const rootPath = join(extractDir, TAKT_REPERTOIRE_MANIFEST_FILENAME);
if (existsSync(rootPath)) return rootPath; if (existsSync(rootPath)) return rootPath;
throw new Error( throw new Error(
`${TAKT_PACKAGE_MANIFEST_FILENAME} not found in "${extractDir}": checked .takt/${TAKT_PACKAGE_MANIFEST_FILENAME} and ${TAKT_PACKAGE_MANIFEST_FILENAME}`, `${TAKT_REPERTOIRE_MANIFEST_FILENAME} not found in "${extractDir}": checked .takt/${TAKT_REPERTOIRE_MANIFEST_FILENAME} and ${TAKT_REPERTOIRE_MANIFEST_FILENAME}`,
); );
} }

View File

@ -16,7 +16,7 @@ import {
getBuiltinPiecesDir, getBuiltinPiecesDir,
getGlobalFacetDir, getGlobalFacetDir,
getProjectFacetDir, getProjectFacetDir,
getEnsembleDir, getRepertoireDir,
isPathSafe, isPathSafe,
} from '../paths.js'; } from '../paths.js';
import { resolveConfigValue } from '../resolveConfigValue.js'; import { resolveConfigValue } from '../resolveConfigValue.js';
@ -31,7 +31,7 @@ function getAllowedPromptBases(cwd: string): string[] {
getBuiltinPiecesDir(lang), getBuiltinPiecesDir(lang),
getGlobalFacetDir('personas'), getGlobalFacetDir('personas'),
getProjectFacetDir(cwd, 'personas'), getProjectFacetDir(cwd, 'personas'),
getEnsembleDir(), getRepertoireDir(),
]; ];
} }

View File

@ -326,11 +326,11 @@ function buildCategoryTree(
} }
/** /**
* Append an "ensemble" category containing all @scope pieces. * Append a "repertoire" category containing all @scope pieces.
* Creates one subcategory per @owner/repo package. * Creates one subcategory per @owner/repo package.
* Marks ensemble piece names as categorized (prevents them from appearing in "Others"). * Marks repertoire piece names as categorized (prevents them from appearing in "Others").
*/ */
function appendEnsembleCategory( function appendRepertoireCategory(
categories: PieceCategoryNode[], categories: PieceCategoryNode[],
allPieces: Map<string, PieceWithSource>, allPieces: Map<string, PieceWithSource>,
categorized: Set<string>, categorized: Set<string>,
@ -352,11 +352,11 @@ function appendEnsembleCategory(
categorized.add(pieceName); categorized.add(pieceName);
} }
if (packagePieces.size === 0) return categories; if (packagePieces.size === 0) return categories;
const ensembleChildren: PieceCategoryNode[] = []; const repertoireChildren: PieceCategoryNode[] = [];
for (const [packageKey, pieces] of packagePieces.entries()) { for (const [packageKey, pieces] of packagePieces.entries()) {
ensembleChildren.push({ name: packageKey, pieces, children: [] }); repertoireChildren.push({ name: packageKey, pieces, children: [] });
} }
return [...categories, { name: 'ensemble', pieces: [], children: ensembleChildren }]; return [...categories, { name: 'repertoire', pieces: [], children: repertoireChildren }];
} }
function appendOthersCategory( function appendOthersCategory(
@ -415,7 +415,7 @@ export function buildCategorizedPieces(
const categorized = new Set<string>(); const categorized = new Set<string>();
const categories = buildCategoryTree(config.pieceCategories, allPieces, categorized); const categories = buildCategoryTree(config.pieceCategories, allPieces, categorized);
const categoriesWithEnsemble = appendEnsembleCategory(categories, allPieces, categorized); const categoriesWithEnsemble = appendRepertoireCategory(categories, allPieces, categorized);
const finalCategories = config.showOthersCategory const finalCategories = config.showOthersCategory
? appendOthersCategory(categoriesWithEnsemble, allPieces, categorized, config.othersCategoryName) ? appendOthersCategory(categoriesWithEnsemble, allPieces, categorized, config.othersCategoryName)

View File

@ -12,7 +12,7 @@ import type { z } from 'zod';
import { PieceConfigRawSchema, PieceMovementRawSchema } from '../../../core/models/index.js'; import { PieceConfigRawSchema, PieceMovementRawSchema } from '../../../core/models/index.js';
import type { PieceConfig, PieceMovement, PieceRule, OutputContractEntry, OutputContractItem, LoopMonitorConfig, LoopMonitorJudge, ArpeggioMovementConfig, ArpeggioMergeMovementConfig, TeamLeaderConfig } from '../../../core/models/index.js'; import type { PieceConfig, PieceMovement, PieceRule, OutputContractEntry, OutputContractItem, LoopMonitorConfig, LoopMonitorJudge, ArpeggioMovementConfig, ArpeggioMergeMovementConfig, TeamLeaderConfig } from '../../../core/models/index.js';
import { resolvePieceConfigValue } from '../resolvePieceConfigValue.js'; import { resolvePieceConfigValue } from '../resolvePieceConfigValue.js';
import { getEnsembleDir } from '../paths.js'; import { getRepertoireDir } from '../paths.js';
import { import {
type PieceSections, type PieceSections,
type FacetResolutionContext, type FacetResolutionContext,
@ -443,7 +443,7 @@ export function loadPieceFromFile(filePath: string, projectDir: string): PieceCo
lang: resolvePieceConfigValue(projectDir, 'language'), lang: resolvePieceConfigValue(projectDir, 'language'),
projectDir, projectDir,
pieceDir, pieceDir,
ensembleDir: getEnsembleDir(), repertoireDir: getRepertoireDir(),
}; };
return normalizePieceConfig(raw, pieceDir, context); return normalizePieceConfig(raw, pieceDir, context);

View File

@ -9,7 +9,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
import { join, resolve, isAbsolute } from 'node:path'; import { join, resolve, isAbsolute } from 'node:path';
import { homedir } from 'node:os'; import { homedir } from 'node:os';
import type { PieceConfig, PieceMovement, InteractiveMode } from '../../../core/models/index.js'; import type { PieceConfig, PieceMovement, InteractiveMode } from '../../../core/models/index.js';
import { getGlobalPiecesDir, getBuiltinPiecesDir, getProjectConfigDir, getEnsembleDir } from '../paths.js'; import { getGlobalPiecesDir, getBuiltinPiecesDir, getProjectConfigDir, getRepertoireDir } from '../paths.js';
import { isScopeRef, parseScopeRef } from '../../../faceted-prompting/index.js'; import { isScopeRef, parseScopeRef } from '../../../faceted-prompting/index.js';
import { resolvePieceConfigValues } from '../resolvePieceConfigValue.js'; import { resolvePieceConfigValues } from '../resolvePieceConfigValue.js';
import { createLogger, getErrorMessage } from '../../../shared/utils/index.js'; import { createLogger, getErrorMessage } from '../../../shared/utils/index.js';
@ -17,7 +17,7 @@ import { loadPieceFromFile } from './pieceParser.js';
const log = createLogger('piece-resolver'); const log = createLogger('piece-resolver');
export type PieceSource = 'builtin' | 'user' | 'project' | 'ensemble'; export type PieceSource = 'builtin' | 'user' | 'project' | 'repertoire';
export interface PieceWithSource { export interface PieceWithSource {
config: PieceConfig; config: PieceConfig;
@ -144,7 +144,7 @@ export function loadPieceByIdentifier(
projectCwd: string, projectCwd: string,
): PieceConfig | null { ): PieceConfig | null {
if (isScopeRef(identifier)) { if (isScopeRef(identifier)) {
return loadEnsemblePieceByRef(identifier, projectCwd); return loadRepertoirePieceByRef(identifier, projectCwd);
} }
if (isPiecePath(identifier)) { if (isPiecePath(identifier)) {
return loadPieceFromPath(identifier, projectCwd, projectCwd); return loadPieceFromPath(identifier, projectCwd, projectCwd);
@ -376,14 +376,14 @@ function* iteratePieceDir(
} }
/** /**
* Iterate piece YAML files in all ensemble packages. * Iterate piece YAML files in all repertoire packages.
* Qualified name format: @{owner}/{repo}/{piece-name} * Qualified name format: @{owner}/{repo}/{piece-name}
*/ */
function* iterateEnsemblePieces(ensembleDir: string): Generator<PieceDirEntry> { function* iterateRepertoirePieces(repertoireDir: string): Generator<PieceDirEntry> {
if (!existsSync(ensembleDir)) return; if (!existsSync(repertoireDir)) return;
for (const ownerEntry of readdirSync(ensembleDir)) { for (const ownerEntry of readdirSync(repertoireDir)) {
if (!ownerEntry.startsWith('@')) continue; if (!ownerEntry.startsWith('@')) continue;
const ownerPath = join(ensembleDir, ownerEntry); const ownerPath = join(repertoireDir, ownerEntry);
try { if (!statSync(ownerPath).isDirectory()) continue; } catch (e) { log.debug(`stat failed for owner dir ${ownerPath}: ${getErrorMessage(e)}`); continue; } try { if (!statSync(ownerPath).isDirectory()) continue; } catch (e) { log.debug(`stat failed for owner dir ${ownerPath}: ${getErrorMessage(e)}`); continue; }
const owner = ownerEntry.slice(1); const owner = ownerEntry.slice(1);
for (const repoEntry of readdirSync(ownerPath)) { for (const repoEntry of readdirSync(ownerPath)) {
@ -396,7 +396,7 @@ function* iterateEnsemblePieces(ensembleDir: string): Generator<PieceDirEntry> {
const piecePath = join(piecesDir, pieceFile); const piecePath = join(piecesDir, pieceFile);
try { if (!statSync(piecePath).isFile()) continue; } catch (e) { log.debug(`stat failed for piece file ${piecePath}: ${getErrorMessage(e)}`); continue; } try { if (!statSync(piecePath).isFile()) continue; } catch (e) { log.debug(`stat failed for piece file ${piecePath}: ${getErrorMessage(e)}`); continue; }
const pieceName = pieceFile.replace(/\.ya?ml$/, ''); const pieceName = pieceFile.replace(/\.ya?ml$/, '');
yield { name: `@${owner}/${repoEntry}/${pieceName}`, path: piecePath, source: 'ensemble' }; yield { name: `@${owner}/${repoEntry}/${pieceName}`, path: piecePath, source: 'repertoire' };
} }
} }
} }
@ -404,12 +404,12 @@ function* iterateEnsemblePieces(ensembleDir: string): Generator<PieceDirEntry> {
/** /**
* Load a piece by @scope reference (@{owner}/{repo}/{piece-name}). * Load a piece by @scope reference (@{owner}/{repo}/{piece-name}).
* Resolves to ~/.takt/ensemble/@{owner}/{repo}/pieces/{piece-name}.yaml * Resolves to ~/.takt/repertoire/@{owner}/{repo}/pieces/{piece-name}.yaml
*/ */
function loadEnsemblePieceByRef(identifier: string, projectCwd: string): PieceConfig | null { function loadRepertoirePieceByRef(identifier: string, projectCwd: string): PieceConfig | null {
const scopeRef = parseScopeRef(identifier); const scopeRef = parseScopeRef(identifier);
const ensembleDir = getEnsembleDir(); const repertoireDir = getRepertoireDir();
const piecesDir = join(ensembleDir, `@${scopeRef.owner}`, scopeRef.repo, 'pieces'); const piecesDir = join(repertoireDir, `@${scopeRef.owner}`, scopeRef.repo, 'pieces');
const filePath = resolvePieceFile(piecesDir, scopeRef.name); const filePath = resolvePieceFile(piecesDir, scopeRef.name);
if (!filePath) return null; if (!filePath) return null;
return loadPieceFromFile(filePath, projectCwd); return loadPieceFromFile(filePath, projectCwd);
@ -450,12 +450,12 @@ export function loadAllPiecesWithSources(cwd: string): Map<string, PieceWithSour
} }
} }
const ensembleDir = getEnsembleDir(); const repertoireDir = getRepertoireDir();
for (const entry of iterateEnsemblePieces(ensembleDir)) { for (const entry of iterateRepertoirePieces(repertoireDir)) {
try { try {
pieces.set(entry.name, { config: loadPieceFromFile(entry.path, cwd), source: entry.source }); pieces.set(entry.name, { config: loadPieceFromFile(entry.path, cwd), source: entry.source });
} catch (err) { } catch (err) {
log.debug('Skipping invalid ensemble piece file', { path: entry.path, error: getErrorMessage(err) }); log.debug('Skipping invalid repertoire piece file', { path: entry.path, error: getErrorMessage(err) });
} }
} }

View File

@ -11,7 +11,7 @@ import { existsSync, readFileSync } from 'node:fs';
import { resolve } from 'node:path'; import { resolve } from 'node:path';
import type { Language } from '../../../core/models/index.js'; import type { Language } from '../../../core/models/index.js';
import type { FacetType } from '../paths.js'; import type { FacetType } from '../paths.js';
import { getProjectFacetDir, getGlobalFacetDir, getBuiltinFacetDir, getEnsembleFacetDir } from '../paths.js'; import { getProjectFacetDir, getGlobalFacetDir, getBuiltinFacetDir, getRepertoireFacetDir } from '../paths.js';
import { import {
resolveFacetPath as resolveFacetPathGeneric, resolveFacetPath as resolveFacetPathGeneric,
@ -38,39 +38,39 @@ export interface FacetResolutionContext {
lang: Language; lang: Language;
/** pieceDir of the piece being parsed — used for package-local layer detection. */ /** pieceDir of the piece being parsed — used for package-local layer detection. */
pieceDir?: string; pieceDir?: string;
/** ensemble directory root — used together with pieceDir to detect package pieces. */ /** repertoire directory root — used together with pieceDir to detect package pieces. */
ensembleDir?: string; repertoireDir?: string;
} }
/** /**
* Determine whether a piece is inside an ensemble package. * Determine whether a piece is inside a repertoire package.
* *
* @param pieceDir - absolute path to the piece directory * @param pieceDir - absolute path to the piece directory
* @param ensembleDir - absolute path to the ensemble root (~/.takt/ensemble) * @param repertoireDir - absolute path to the repertoire root (~/.takt/repertoire)
*/ */
export function isPackagePiece(pieceDir: string, ensembleDir: string): boolean { export function isPackagePiece(pieceDir: string, repertoireDir: string): boolean {
const resolvedPiece = resolve(pieceDir); const resolvedPiece = resolve(pieceDir);
const resolvedEnsemble = resolve(ensembleDir); const resolvedRepertoire = resolve(repertoireDir);
return resolvedPiece.startsWith(resolvedEnsemble + '/'); return resolvedPiece.startsWith(resolvedRepertoire + '/');
} }
/** /**
* Extract { owner, repo } from a package piece directory path. * Extract { owner, repo } from a package piece directory path.
* *
* Directory structure: {ensembleDir}/@{owner}/{repo}/pieces/ * Directory structure: {repertoireDir}/@{owner}/{repo}/pieces/
* *
* @returns { owner, repo } if pieceDir is a package piece, undefined otherwise. * @returns { owner, repo } if pieceDir is a package piece, undefined otherwise.
*/ */
export function getPackageFromPieceDir( export function getPackageFromPieceDir(
pieceDir: string, pieceDir: string,
ensembleDir: string, repertoireDir: string,
): { owner: string; repo: string } | undefined { ): { owner: string; repo: string } | undefined {
if (!isPackagePiece(pieceDir, ensembleDir)) { if (!isPackagePiece(pieceDir, repertoireDir)) {
return undefined; return undefined;
} }
const resolvedEnsemble = resolve(ensembleDir); const resolvedRepertoire = resolve(repertoireDir);
const resolvedPiece = resolve(pieceDir); const resolvedPiece = resolve(pieceDir);
const relative = resolvedPiece.slice(resolvedEnsemble.length + 1); const relative = resolvedPiece.slice(resolvedRepertoire.length + 1);
const parts = relative.split('/'); const parts = relative.split('/');
if (parts.length < 2) return undefined; if (parts.length < 2) return undefined;
const ownerWithAt = parts[0]!; const ownerWithAt = parts[0]!;
@ -84,7 +84,7 @@ export function getPackageFromPieceDir(
* Build candidate directories with optional package-local layer (4-layer for package pieces). * Build candidate directories with optional package-local layer (4-layer for package pieces).
* *
* Resolution order for package pieces: * Resolution order for package pieces:
* 1. package-local: {ensembleDir}/@{owner}/{repo}/facets/{type} * 1. package-local: {repertoireDir}/@{owner}/{repo}/facets/{type}
* 2. project: {projectDir}/.takt/facets/{type} * 2. project: {projectDir}/.takt/facets/{type}
* 3. user: ~/.takt/facets/{type} * 3. user: ~/.takt/facets/{type}
* 4. builtin: builtins/{lang}/facets/{type} * 4. builtin: builtins/{lang}/facets/{type}
@ -97,10 +97,10 @@ export function buildCandidateDirsWithPackage(
): string[] { ): string[] {
const dirs: string[] = []; const dirs: string[] = [];
if (context.pieceDir && context.ensembleDir) { if (context.pieceDir && context.repertoireDir) {
const pkg = getPackageFromPieceDir(context.pieceDir, context.ensembleDir); const pkg = getPackageFromPieceDir(context.pieceDir, context.repertoireDir);
if (pkg) { if (pkg) {
dirs.push(getEnsembleFacetDir(pkg.owner, pkg.repo, facetType, context.ensembleDir)); dirs.push(getRepertoireFacetDir(pkg.owner, pkg.repo, facetType, context.repertoireDir));
} }
} }
@ -116,7 +116,7 @@ export function buildCandidateDirsWithPackage(
/** /**
* Resolve a facet name to its file path via 4-layer lookup (package-local project user builtin). * Resolve a facet name to its file path via 4-layer lookup (package-local project user builtin).
* *
* Handles @{owner}/{repo}/{facet-name} scope references directly when ensembleDir is provided. * Handles @{owner}/{repo}/{facet-name} scope references directly when repertoireDir is provided.
* *
* @returns Absolute file path if found, undefined otherwise. * @returns Absolute file path if found, undefined otherwise.
*/ */
@ -125,9 +125,9 @@ export function resolveFacetPath(
facetType: FacetType, facetType: FacetType,
context: FacetResolutionContext, context: FacetResolutionContext,
): string | undefined { ): string | undefined {
if (isScopeRef(name) && context.ensembleDir) { if (isScopeRef(name) && context.repertoireDir) {
const scopeRef = parseScopeRef(name); const scopeRef = parseScopeRef(name);
const filePath = resolveScopeRef(scopeRef, facetType, context.ensembleDir); const filePath = resolveScopeRef(scopeRef, facetType, context.repertoireDir);
return existsSync(filePath) ? filePath : undefined; return existsSync(filePath) ? filePath : undefined;
} }
return resolveFacetPathGeneric(name, buildCandidateDirsWithPackage(facetType, context)); return resolveFacetPathGeneric(name, buildCandidateDirsWithPackage(facetType, context));
@ -136,7 +136,7 @@ export function resolveFacetPath(
/** /**
* Resolve a facet name to its file content via 4-layer lookup. * Resolve a facet name to its file content via 4-layer lookup.
* *
* Handles @{owner}/{repo}/{facet-name} scope references when ensembleDir is provided. * Handles @{owner}/{repo}/{facet-name} scope references when repertoireDir is provided.
* *
* @returns File content if found, undefined otherwise. * @returns File content if found, undefined otherwise.
*/ */
@ -165,9 +165,9 @@ export function resolveRefToContent(
facetType?: FacetType, facetType?: FacetType,
context?: FacetResolutionContext, context?: FacetResolutionContext,
): string | undefined { ): string | undefined {
if (facetType && context && isScopeRef(ref) && context.ensembleDir) { if (facetType && context && isScopeRef(ref) && context.repertoireDir) {
const scopeRef = parseScopeRef(ref); const scopeRef = parseScopeRef(ref);
const filePath = resolveScopeRef(scopeRef, facetType, context.ensembleDir); const filePath = resolveScopeRef(scopeRef, facetType, context.repertoireDir);
return existsSync(filePath) ? readFileSync(filePath, 'utf-8') : undefined; return existsSync(filePath) ? readFileSync(filePath, 'utf-8') : undefined;
} }
const candidateDirs = facetType && context const candidateDirs = facetType && context
@ -201,9 +201,9 @@ export function resolvePersona(
pieceDir: string, pieceDir: string,
context?: FacetResolutionContext, context?: FacetResolutionContext,
): { personaSpec?: string; personaPath?: string } { ): { personaSpec?: string; personaPath?: string } {
if (rawPersona && isScopeRef(rawPersona) && context?.ensembleDir) { if (rawPersona && isScopeRef(rawPersona) && context?.repertoireDir) {
const scopeRef = parseScopeRef(rawPersona); const scopeRef = parseScopeRef(rawPersona);
const personaPath = resolveScopeRef(scopeRef, 'personas', context.ensembleDir); const personaPath = resolveScopeRef(scopeRef, 'personas', context.repertoireDir);
return { personaSpec: rawPersona, personaPath: existsSync(personaPath) ? personaPath : undefined }; return { personaSpec: rawPersona, personaPath: existsSync(personaPath) ? personaPath : undefined };
} }
const candidateDirs = context const candidateDirs = context

View File

@ -12,6 +12,7 @@ import type { Language } from '../../core/models/index.js';
import { getLanguageResourcesDir } from '../resources/index.js'; import { getLanguageResourcesDir } from '../resources/index.js';
import type { FacetKind } from '../../faceted-prompting/index.js'; import type { FacetKind } from '../../faceted-prompting/index.js';
import { REPERTOIRE_DIR_NAME } from '../../features/repertoire/constants.js';
/** Facet types used in layer resolution */ /** Facet types used in layer resolution */
export type { FacetKind as FacetType } from '../../faceted-prompting/index.js'; export type { FacetKind as FacetType } from '../../faceted-prompting/index.js';
@ -105,25 +106,25 @@ export function getBuiltinFacetDir(lang: Language, facetType: FacetType): string
return join(getLanguageResourcesDir(lang), 'facets', facetType); return join(getLanguageResourcesDir(lang), 'facets', facetType);
} }
/** Get ensemble directory (~/.takt/ensemble/) */ /** Get repertoire directory (~/.takt/repertoire/) */
export function getEnsembleDir(): string { export function getRepertoireDir(): string {
return join(getGlobalConfigDir(), 'ensemble'); return join(getGlobalConfigDir(), REPERTOIRE_DIR_NAME);
} }
/** Get ensemble package directory (~/.takt/ensemble/@{owner}/{repo}/) */ /** Get repertoire package directory (~/.takt/repertoire/@{owner}/{repo}/) */
export function getEnsemblePackageDir(owner: string, repo: string): string { export function getRepertoirePackageDir(owner: string, repo: string): string {
return join(getEnsembleDir(), `@${owner}`, repo); return join(getRepertoireDir(), `@${owner}`, repo);
} }
/** /**
* Get ensemble facet directory. * Get repertoire facet directory.
* *
* Defaults to the global ensemble dir when ensembleDir is not specified. * Defaults to the global repertoire dir when repertoireDir is not specified.
* Pass ensembleDir explicitly when resolving facets within a custom ensemble root * Pass repertoireDir explicitly when resolving facets within a custom repertoire root
* (e.g. the package-local resolution layer). * (e.g. the package-local resolution layer).
*/ */
export function getEnsembleFacetDir(owner: string, repo: string, facetType: FacetType, ensembleDir?: string): string { export function getRepertoireFacetDir(owner: string, repo: string, facetType: FacetType, repertoireDir?: string): string {
const base = ensembleDir ?? getEnsembleDir(); const base = repertoireDir ?? getRepertoireDir();
return join(base, `@${owner}`, repo, 'facets', facetType); return join(base, `@${owner}`, repo, 'facets', facetType);
} }

View File

@ -97,7 +97,7 @@ export async function confirm(message: string, defaultYes = true): Promise<boole
const { useTty, forceTouchTty } = resolveTtyPolicy(); const { useTty, forceTouchTty } = resolveTtyPolicy();
assertTtyIfForced(forceTouchTty); assertTtyIfForced(forceTouchTty);
if (!useTty) { if (!useTty) {
// Support piped stdin (e.g. echo "y" | takt ensemble add ...) // Support piped stdin (e.g. echo "y" | takt repertoire add ...)
if (!process.stdin.isTTY && process.stdin.readable && !process.stdin.destroyed) { if (!process.stdin.isTTY && process.stdin.readable && !process.stdin.destroyed) {
return readConfirmFromPipe(defaultYes); return readConfirmFromPipe(defaultYes);
} }