refactor: rename ensemble to repertoire across codebase
This commit is contained in:
parent
a59ad1d808
commit
c630d78806
@ -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
|
||||||
|
|||||||
@ -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` を追加(ワーカープールのログノイズ抑制のための入力待ち状態共有)
|
||||||
|
|||||||
@ -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)) | バージョン履歴 |
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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/
|
||||||
@ -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/
|
||||||
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
});
|
});
|
||||||
@ -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');
|
||||||
});
|
});
|
||||||
@ -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 (persona→personas, policy→policies, etc.)
|
* - facet-type mapping from field context (persona→personas, policy→policies, 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'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
||||||
@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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(() => {
|
||||||
@ -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);
|
||||||
});
|
});
|
||||||
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -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
|
||||||
@ -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', () => {
|
||||||
@ -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', () => {
|
||||||
@ -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', () => {
|
||||||
@ -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')!;
|
||||||
@ -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
|
||||||
@ -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
|
||||||
@ -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);
|
||||||
@ -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
|
||||||
@ -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]!;
|
||||||
|
|
||||||
@ -2,7 +2,7 @@
|
|||||||
* Tests for facet directory path helpers in paths.ts — items 42–45.
|
* Tests for facet directory path helpers in paths.ts — items 42–45.
|
||||||
*
|
*
|
||||||
* 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, '/');
|
||||||
@ -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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -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
|
||||||
@ -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 () => {
|
||||||
|
|||||||
@ -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();
|
||||||
});
|
});
|
||||||
@ -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();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -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('インストール済みパッケージはありません');
|
||||||
@ -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 });
|
||||||
}
|
}
|
||||||
@ -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',
|
||||||
|
|||||||
@ -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';
|
|
||||||
12
src/features/repertoire/constants.ts
Normal file
12
src/features/repertoire/constants.ts
Normal 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';
|
||||||
@ -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[] = [];
|
||||||
@ -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.
|
||||||
@ -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
|
||||||
@ -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)) {
|
||||||
@ -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 {
|
||||||
@ -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. */
|
||||||
@ -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}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user