@nahisaho/shikigami 2.0.5 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,24 @@ All notable changes to SHIKIGAMI will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.0.6] - 2026-05-18
9
+
10
+ ### Changed
11
+
12
+ - **Coworkプラグインビルダーをゼロから再構築**
13
+ - 公式ドキュメント「プラグインをゼロからビルドする」に完全準拠
14
+ - ASKILL検証コード(ASKILL-M001〜P008)によるバリデーション
15
+ - コンパニオンファイルパス検証(隠しファイル、パストラバーサル、Windows予約名、安全文字チェック)
16
+ - Cowork用SKILL.md自動リライト(cowork.category, cowork.icon, metadata追加)
17
+ - パス参照の自動更新(統合後のreferences/パスに変換)
18
+ - Additional Resources セクション自動生成
19
+ - セキュリティ改善: execSync → execFileSync(シェルインジェクション防止)
20
+ - SKILL.md本文サイズ警告(3000語超で警告)
21
+ - 統合ファイルの5MB超過時チャンキング対応
22
+ - マニフェスト検証(GUID形式、重複folder、スキル数上限)
23
+ - クロスプラットフォーム互換性表示(GitHub Copilot CLI / VS Code / Claude Code / Gemini CLI)
24
+ - 参照: https://learn.microsoft.com/microsoft-365/copilot/cowork/cowork-plugin-development
25
+
8
26
  ## [2.0.5] - 2026-05-18
9
27
 
10
28
  ### Changed
@@ -16,6 +34,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
16
34
  - manifest.json自動生成、アイコン生成、スキル検証機能
17
35
  - Companionファイル20制限への自動統合(63ファイル→14ファイル等)
18
36
  - 参照: https://learn.microsoft.com/microsoft-365/copilot/cowork/cowork-plugin-development
37
+ - **README.md全面刷新**
38
+ - インストールコマンド修正(`@nahisaho/shikigami`)
39
+ - 全19プロンプトをカテゴリ別に一覧化
40
+ - Agent Skills: npmパッケージ含有4スキルに正確化
41
+ - 50+コンサルティングフレームワーク一覧を追加
42
+ - アーキテクチャ図をv2.0.5に更新
43
+ - CLIコマンド一覧を追加
19
44
 
20
45
  ### Removed
21
46
 
package/README.md CHANGED
@@ -1,90 +1,66 @@
1
1
  # SHIKIGAMI
2
2
 
3
- **GitHub Copilot Agent Skills for Deep Research & McKinsey Consulting**
3
+ **GitHub Copilot Agent Skills for Deep Research & Consulting**
4
4
 
5
- マッキンゼーのコンサルティング手法(仮説駆動、MECE、ピラミッド原則、7Sフレームワーク等)を活用した深層リサーチ自動化Agent Skillsパッケージです。GitHub Copilot CLI と連携し、Copilot CLI の組み込み機能を活用しつつ、SHIKIGAMI独自のリサーチ・コンサルティング機能を提供します。
5
+ 50+のコンサルティングフレームワーク(SWOT、PEST、5Forces、BCG Matrix、ピラミッド原則等)を活用した深層リサーチ自動化Agent Skillsパッケージです。GitHub Copilot CLI と連携し、Copilot CLI の組み込み機能を活用しつつ、SHIKIGAMI独自のリサーチ・コンサルティング機能を提供します。
6
6
 
7
7
  ## 🎯 できること
8
8
 
9
- - **対話的目的探索**: ユーザーの表層的な依頼から「真の目的」を探索
10
- - **Deep Research**: WebResearcherパラダイムに基づく反復的深層リサーチ
11
- - **マッキンゼー・フレームワーク分析**: 仮説駆動、イシューツリー、7S、ピラミッド原則等を適用
12
- - **高品質レポート生成**: ハルシネーション防止、ソース追跡、引用管理
9
+ - **対話的目的探索**: ユーザーの表層的な依頼から「真の目的」を5 Whys/JTBDで探索
10
+ - **Deep Research**: Think→Report→Actionサイクルに基づく反復的深層リサーチ
11
+ - **コンサルティング・フレームワーク分析**: 50+フレームワーク(SWOT、PEST、5Forces、BCG Matrix、7S、ピラミッド原則等)を適用
12
+ - **高品質レポート生成**: ハルシネーション防止、ソース追跡、引用管理、Qiita記事対応
13
13
  - **マルチポップ探索**: リサーチ中の分岐点を自動検出し、複数パスを同時探索
14
14
  - **ベクトル検索**: 過去のリサーチ資産をセマンティック検索で再利用
15
- - **品質検証ループ**: Build→Type→Lint→Test→Security→Diff の6フェーズ検証
15
+ - **Copilot Cowork連携**: M365 Copilot Coworkプラグインとしてエクスポート可能
16
16
 
17
17
  ## 📦 インストール
18
18
 
19
19
  ```bash
20
- npm install @nahisaho/shikigami-mck
20
+ npm install @nahisaho/shikigami
21
+ npx shikigami init
21
22
  ```
22
23
 
23
- または、リポジトリをクローン:
24
+ セットアップ後:
24
25
 
25
26
  ```bash
26
- git clone https://github.com/nahisaho/SHIKIGAMI.git
27
+ cd mcp-server && npm install && npm run build
27
28
  ```
28
29
 
29
30
  ## 🏗️ アーキテクチャ
30
31
 
31
- SHIKIGAMI v2.0.0 は GitHub Copilot CLI の組み込み機能を最大限活用する設計に移行しました。
32
-
33
32
  ```
34
33
  ┌─────────────────────────────────────────────────┐
35
34
  │ GitHub Copilot CLI │
36
35
  │ web_search / web_fetch / store_memory / grep │
37
36
  │ explore / research / rubber-duck agents │
38
- │ /session / /context / /compact │
39
37
  ├─────────────────────────────────────────────────┤
40
- │ SHIKIGAMI v2.0.0
38
+ │ SHIKIGAMI v2.0.6
41
39
  │ ┌──────────────┐ ┌───────────────────────┐ │
42
40
  │ │ Agent Skills │ │ MCP Server (10 tools) │ │
43
- │ │ (15 skills) │ │ embed / similarity │ │
41
+ │ │ (4 skills) │ │ embed / similarity │ │
44
42
  │ │ │ │ semantic_search │ │
45
- │ │ Research │ │ branch_detect/eval/ │ │
46
- │ │ Consulting │ │ select │ │
47
- │ │ Quality │ │ set/get_project │ │
48
- │ │ MUSUBIX SDD │ │ save_prompt/research │ │
49
- └──────────────┘ └───────────────────────┘ │
43
+ │ │ Planner │ │ branch_detect/eval/ │ │
44
+ │ │ Deep Research │ │ select │ │
45
+ │ │ Framework │ │ set/get_project │ │
46
+ │ │ Writing │ │ save_prompt/research │ │
47
+ ├──────────────┤ └───────────────────────┘ │
48
+ │ │ 19 Prompts │ │
49
+ │ └──────────────┘ │
50
+ ├─────────────────────────────────────────────────┤
51
+ │ Copilot Cowork Plugin (.zip) │
52
+ │ npx shikigami cowork で M365 プラグイン生成 │
50
53
  └─────────────────────────────────────────────────┘
51
54
  ```
52
55
 
53
- ## 🔧 Agent Skills(15スキル)
54
-
55
- ### リサーチ・コンサルティング
56
+ ## 🔧 Agent Skills(4スキル)
56
57
 
57
58
  | スキル | 説明 |
58
59
  |--------|------|
59
60
  | `shikigami-planner` | 対話的目的探索・5 Whys・JTBD・リサーチ計画立案 |
60
61
  | `shikigami-deep-research` | Think→Report→Actionサイクルの反復的深層リサーチ |
61
- | `shikigami-consulting-framework` | マッキンゼー・フレームワーク分析(仮説駆動・7S・ピラミッド原則等) |
62
- | `shikigami-writing` | レポート生成・ハルシネーション検出・引用管理 |
63
-
64
- ### 品質・検証
65
-
66
- | スキル | 説明 |
67
- |--------|------|
68
- | `build-fix` | ビルドエラーを分類・反復修正(TypeScript/ESLint/依存関係) |
69
- | `checkpoint` | セーフポイントの作成・復元・比較(Git統合) |
70
- | `e2e-runner` | Playwrightを使用したE2Eテスト生成・実行 |
71
- | `eval-harness` | pass@kメトリクスでAIコード生成品質を評価 |
72
- | `verification-loop` | Build→Type→Lint→Test→Security→Diff の6フェーズ検証 |
73
-
74
- ### MUSUBIX SDD
75
-
76
- | スキル | 説明 |
77
- |--------|------|
78
- | `musubix-sdd-workflow` | 10憲法条項に従った仕様駆動開発ガイド |
79
- | `musubix-domain-inference` | 自動ドメイン検出・コンポーネント推論 |
80
- | `musubix-code-generation` | 設計仕様からのコード生成 |
81
- | `musubix-test-generation` | TDD/BDDによるテスト生成 |
82
- | `musubix-adr-generation` | Architecture Decision Records作成 |
83
- | `musubix-best-practices` | 学習済み17ベストプラクティスガイド |
84
- | `musubix-c4-design` | C4モデル設計ドキュメント作成 |
85
- | `musubix-technical-writing` | 技術ドキュメント作成 |
86
- | `musubix-ears-validation` | EARS形式要件の検証・作成 |
87
- | `musubix-traceability` | 要件・設計・コード・テスト間のトレーサビリティ管理 |
62
+ | `shikigami-consulting-framework` | 50+コンサルティングフレームワーク分析 |
63
+ | `shikigami-writing` | レポート生成・ハルシネーション検出・引用管理・Qiita対応 |
88
64
 
89
65
  ## 🔌 MCP Server(10ツール)
90
66
 
@@ -100,112 +76,72 @@ SHIKIGAMI v2.0.0 は GitHub Copilot CLI の組み込み機能を最大限活用
100
76
  | `branch_evaluate` | 分岐パスのスコアリング・評価 |
101
77
  | `branch_select` | 最適な探索パスの選択 |
102
78
 
103
- ## 📝 Prompts
79
+ ## 📝 Prompts(19プロンプト)
80
+
81
+ ### コア
104
82
 
105
83
  | プロンプト | 説明 |
106
84
  |-----------|------|
85
+ | `shikigami-full-research` | 統合リサーチ(Planner→Research→Framework→Writing) |
107
86
  | `shikigami-purpose-discovery` | 目的探索を開始 |
108
87
  | `shikigami-deep-research` | Deep Researchを実行 |
109
- | `shikigami-framework-analysis` | マッキンゼーフレームワーク分析を実行 |
110
- | `shikigami-report-writing` | ピラミッド原則に基づくレポートを生成 |
111
- | `shikigami-full-research` | 統合リサーチを実行 |
112
-
113
- ## 📚 マッキンゼー・フレームワーク一覧
114
-
115
- ### 問題解決・仮説思考
116
- | フレームワーク | 説明 |
117
- |--------------|------|
118
- | **仮説駆動アプローチ** | 仮説を立て、検証し、結論を導く問題解決手法 |
119
- | **イシューツリー** | 問題をMECEに分解し、解決の優先順位を明確化 |
120
- | **ピラミッド原則** | 結論先行で論理的にコミュニケーション(Barbara Minto) |
121
- | **MECE** | 漏れなくダブりなく整理 |
122
-
123
- ### 組織・戦略
124
- | フレームワーク | 説明 |
125
- |--------------|------|
126
- | **マッキンゼーの7S** | 組織の7要素(Strategy, Structure, Systems, Shared Values, Style, Staff, Skills)の整合性分析 |
127
- | **3つの地平線** | 短期(H1)・中期(H2)・長期(H3)の成長を同時管理 |
128
- | **成長のグラニュラリティ** | 成長を詳細に分解して真の成長源を特定 |
129
-
130
- ### 業績改善
131
- | フレームワーク | 説明 |
132
- |--------------|------|
133
- | **間接費価値分析(OVA)** | 間接部門のコストと価値を分析し最適化 |
134
- | **プロフィットプール分析** | バリューチェーン全体の利益分布を可視化 |
135
- | **SCP分析** | 産業構造→企業行動→業績の因果関係を分析 |
136
-
137
- ## 🔄 マッキンゼー式ワークフロー
88
+ | `shikigami-framework-analysis` | フレームワーク分析を実行 |
89
+ | `shikigami-report-writing` | レポートを生成 |
138
90
 
139
- ```mermaid
140
- flowchart TB
141
- subgraph Phase1["Phase 1: 問題定義"]
142
- P1A[クライアントの真の課題特定] --> P1B[成功の定義]
143
- P1B --> P1C[スコープと制約の確認]
144
- end
145
-
146
- subgraph Phase2["Phase 2: 仮説構築"]
147
- P2A[初期仮説の設定] --> P2B[支持論拠の整理]
148
- P2B --> P2C[反証可能な形式で表現]
149
- end
150
-
151
- subgraph Phase3["Phase 3: イシュー分解"]
152
- P3A[イシューツリー作成] --> P3B[優先順位付け]
153
- P3B --> P3C[分析計画策定]
154
- end
155
-
156
- subgraph Phase4["Phase 4: データ収集・分析"]
157
- P4A[定量・定性データ収集] --> P4B[80/20分析]
158
- P4B --> P4C[So What?で洞察抽出]
159
- end
160
-
161
- subgraph Phase5["Phase 5: 統合・提言"]
162
- P5A[ピラミッド原則で構造化] --> P5B[アクションプラン策定]
163
- P5B --> P5C[インパクト評価]
164
- end
165
-
166
- Phase1 --> Phase2
167
- Phase2 --> Phase3
168
- Phase3 --> Phase4
169
- Phase4 --> Phase5
170
- ```
91
+ ### 分析・評価
171
92
 
172
- ## 🚀 使用例
93
+ | プロンプト | 説明 |
94
+ |-----------|------|
95
+ | `shikigami-swot-generation` | SWOT分析を生成 |
96
+ | `shikigami-matching-analysis` | マッチング分析(適合性評価) |
97
+ | `shikigami-financial-analysis` | 財務分析 |
98
+ | `shikigami-comparison-table` | 比較表の生成 |
99
+ | `shikigami-trend-visualization` | トレンド可視化 |
173
100
 
174
- ### 例1: 成長戦略の検討
101
+ ### 品質・検証
175
102
 
176
- ```
177
- ユーザー: 「新規事業の方向性を検討したい」
103
+ | プロンプト | 説明 |
104
+ |-----------|------|
105
+ | `shikigami-source-critique` | ソース信頼性の批評的評価 |
106
+ | `shikigami-risk-sensitivity` | リスク感度分析 |
107
+ | `shikigami-stakeholder-impact` | ステークホルダー影響分析 |
108
+ | `shikigami-consistency-check` | 整合性チェック |
109
+ | `shikigami-phase-gate` | フェーズゲート(品質関門)レビュー |
110
+ | `shikigami-citation-formatter` | 引用フォーマット整形 |
111
+ | `shikigami-executive-sync` | エグゼクティブ向け要約 |
178
112
 
179
- SHIKIGAMI:
180
- 1. [Planner] 「成長の目標は何ですか?」→ 真の目的を探索
181
- 2. [Deep Research] 市場情報を仮説駆動で収集(web_search / web_fetch 活用)
182
- 3. [Framework] 3つの地平線、成長のグラニュラリティを適用
183
- 4. [Writing] ピラミッド原則で構造化した提言レポートを生成
184
- ```
113
+ ### 出力
185
114
 
186
- ### 例2: 組織変革の診断
115
+ | プロンプト | 説明 |
116
+ |-----------|------|
117
+ | `shikigami-qiita-article` | Qiita記事形式で出力 |
118
+ | `shikigami-manifest` | リサーチマニフェスト生成 |
187
119
 
188
- ```
189
- ユーザー: 「組織の問題点を分析して」
120
+ ## 📚 コンサルティング・フレームワーク一覧(50+)
190
121
 
191
- SHIKIGAMI:
192
- 1. [Planner] 変革の目的・成功基準を確認
193
- 2. [Deep Research] 組織情報を収集
194
- 3. [Framework] マッキンゼーの7Sで整合性を分析
195
- 4. [Writing] イシューツリーで課題を構造化したレポートを生成
196
- ```
122
+ ### 戦略分析
123
+ SWOT、PEST、5Forces、3C、BCG Matrix、GE Matrix、Ansoff Matrix、Blue Ocean、VRIO、Value Chain
197
124
 
198
- ### 例3: コスト削減プロジェクト
125
+ ### マーケティング
126
+ 4P、4C、STP、AIDMA/AISAS、Customer Journey、Persona、Positioning Map
199
127
 
200
- ```
201
- ユーザー: 「間接費を削減したい」
128
+ ### 問題解決
129
+ MECE、5 Whys、Issue Tree、Logic Tree、Fishbone、As-Is/To-Be
202
130
 
203
- SHIKIGAMI:
204
- 1. [Planner] 削減目標・制約を確認
205
- 2. [Deep Research] 間接部門の機能・コストを調査
206
- 3. [Framework] OVA(間接費価値分析)を適用
207
- 4. [Writing] 優先順位付きの削減提言レポートを生成
208
- ```
131
+ ### 組織・プロセス
132
+ 7S、RACI、PDCA、OODA、ECRS
133
+
134
+ ### イノベーション
135
+ Business Model Canvas、Lean Canvas、Design Thinking、SCAMPER、TAM/SAM/SOM、AARRR、Value Proposition Canvas
136
+
137
+ ### 意思決定
138
+ Decision Matrix、Cost-Benefit、Risk Matrix、Pros-Cons
139
+
140
+ ### 思考フレームワーク
141
+ Pyramid Principle、So What/Why So、PREP
142
+
143
+ ### PwC固有
144
+ BXT、Strategy&、Fit for Growth、Digital Maturity、ESG Integration、Three Lines of Defense、TIMM
209
145
 
210
146
  ## 🤝 Microsoft 365 Copilot Cowork 連携
211
147
 
@@ -226,6 +162,47 @@ npx shikigami cowork --out ./dist
226
162
 
227
163
  **参照**: [Cowork Plugin Development](https://learn.microsoft.com/microsoft-365/copilot/cowork/cowork-plugin-development)
228
164
 
165
+ ## 🚀 CLI コマンド
166
+
167
+ ```bash
168
+ npx shikigami init # SHIKIGAMIファイルを初期化
169
+ npx shikigami upgrade # 最新版にアップグレード
170
+ npx shikigami cowork # Coworkプラグインをビルド
171
+ npx shikigami new [name] # 新規リサーチプロジェクト作成
172
+ npx shikigami find-related [keyword] # 関連プロジェクト検索
173
+ npx shikigami inherit [options] # 過去レポートからナレッジ継承
174
+ ```
175
+
176
+ ## 🔄 リサーチワークフロー
177
+
178
+ ```mermaid
179
+ flowchart TB
180
+ subgraph Phase1["Phase 1: 目的探索"]
181
+ P1A[ユーザーの依頼] --> P1B[5 Whys / JTBD で真の目的を発見]
182
+ P1B --> P1C[リサーチ計画策定]
183
+ end
184
+
185
+ subgraph Phase2["Phase 2: Deep Research"]
186
+ P2A[Think: 仮説構築] --> P2B[Report: 情報収集・検証]
187
+ P2B --> P2C[Action: 次のリサーチ方向決定]
188
+ P2C --> P2A
189
+ end
190
+
191
+ subgraph Phase3["Phase 3: フレームワーク分析"]
192
+ P3A[適切なフレームワーク選定] --> P3B[50+フレームワークから分析実行]
193
+ P3B --> P3C[洞察の抽出・統合]
194
+ end
195
+
196
+ subgraph Phase4["Phase 4: レポート生成"]
197
+ P4A[ハルシネーション検証] --> P4B[引用・ソース管理]
198
+ P4B --> P4C[構造化レポート出力]
199
+ end
200
+
201
+ Phase1 --> Phase2
202
+ Phase2 --> Phase3
203
+ Phase3 --> Phase4
204
+ ```
205
+
229
206
  ## 📄 ライセンス
230
207
 
231
208
  MIT License
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nahisaho/shikigami-pwc-mcp-server",
3
- "version": "2.0.5",
3
+ "version": "2.0.6",
4
4
  "description": "SHIKIGAMI-PwC MCP Server - Deep Research tools with PwC Consulting Frameworks",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nahisaho/shikigami",
3
- "version": "2.0.5",
3
+ "version": "2.0.6",
4
4
  "description": "GitHub Copilot Agent Skills for Deep Research & Consulting - AI-Powered Research Assistant with 50+ Consulting Frameworks",
5
5
  "keywords": [
6
6
  "github-copilot",
@@ -3,7 +3,7 @@
3
3
  * SHIKIGAMI Cowork Plugin Builder
4
4
  *
5
5
  * Builds a Microsoft 365 Copilot Cowork plugin package (.zip)
6
- * from SHIKIGAMI's Agent Skills.
6
+ * from SHIKIGAMI's Agent Skills following the official "Build from scratch" guide.
7
7
  *
8
8
  * Usage: npx shikigami cowork [options]
9
9
  * --out <dir> Output directory (default: ./dist)
@@ -11,11 +11,14 @@
11
11
  * --help Show help
12
12
  *
13
13
  * Reference: https://learn.microsoft.com/microsoft-365/copilot/cowork/cowork-plugin-development
14
+ *
15
+ * Cross-platform: The same SKILL.md files work in GitHub Copilot CLI,
16
+ * VS Code Copilot, Claude Code, Gemini CLI, and Copilot Cowork.
14
17
  */
15
18
 
16
19
  const fs = require('fs');
17
20
  const path = require('path');
18
- const { execSync } = require('child_process');
21
+ const { execFileSync } = require('child_process');
19
22
 
20
23
  // ── Constants ──────────────────────────────────────────────────────────────
21
24
 
@@ -23,16 +26,32 @@ const MAX_COMPANIONS_PER_SKILL = 20;
23
26
  const MAX_COMPANION_SIZE = 5 * 1024 * 1024; // 5 MB
24
27
  const MAX_COMPANION_TOTAL = 10 * 1024 * 1024; // 10 MB per skill
25
28
  const MAX_SKILLS = 20;
29
+ const MAX_FOLDER_PATH = 256;
30
+ const RECOMMENDED_BODY_WORDS = 3000;
26
31
 
27
32
  const PACKAGE_NAME = 'com.nahisaho.shikigami';
28
- const APP_ID = 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d'; // Stable GUID
33
+ const APP_ID = 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d';
29
34
  const ACCENT_COLOR = '#6B46C1';
30
35
 
31
- // ── Minimal PNG generators (no dependencies) ──────────────────────────────
36
+ const WINDOWS_RESERVED = new Set([
37
+ 'CON', 'PRN', 'AUX', 'NUL',
38
+ 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
39
+ 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9',
40
+ ]);
41
+
42
+ const SAFE_FILENAME_RE = /^[a-zA-Z0-9\-_. !]+$/;
43
+
44
+ // Cowork-specific frontmatter defaults per skill
45
+ const COWORK_DEFAULTS = {
46
+ 'shikigami-planner': { category: 'Research', icon: 'Lightbulb' },
47
+ 'shikigami-deep-research': { category: 'Research', icon: 'Search' },
48
+ 'shikigami-consulting-framework': { category: 'Analysis', icon: 'Briefcase' },
49
+ 'shikigami-writing': { category: 'Writing', icon: 'DocumentBulletList' },
50
+ };
51
+
52
+ // ── Minimal PNG generator (no dependencies) ───────────────────────────────
32
53
 
33
54
  function createMinimalPNG(width, height, r, g, b) {
34
- // Creates a minimal valid PNG with a solid color
35
- // PNG signature
36
55
  const signature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
37
56
 
38
57
  function crc32(buf) {
@@ -53,26 +72,20 @@ function createMinimalPNG(width, height, r, g, b) {
53
72
  const len = Buffer.alloc(4);
54
73
  len.writeUInt32BE(data.length);
55
74
  const typeAndData = Buffer.concat([Buffer.from(type), data]);
56
- const crc = Buffer.alloc(4);
57
- crc.writeUInt32BE(crc32(typeAndData));
58
- return Buffer.concat([len, typeAndData, crc]);
75
+ const crcBuf = Buffer.alloc(4);
76
+ crcBuf.writeUInt32BE(crc32(typeAndData));
77
+ return Buffer.concat([len, typeAndData, crcBuf]);
59
78
  }
60
79
 
61
- // IHDR
62
80
  const ihdr = Buffer.alloc(13);
63
81
  ihdr.writeUInt32BE(width, 0);
64
82
  ihdr.writeUInt32BE(height, 4);
65
- ihdr[8] = 8; // bit depth
66
- ihdr[9] = 2; // color type RGB
67
- ihdr[10] = 0; // compression
68
- ihdr[11] = 0; // filter
69
- ihdr[12] = 0; // interlace
70
-
71
- // IDAT - raw image data
72
- const rowSize = 1 + width * 3; // filter byte + RGB per pixel
83
+ ihdr[8] = 8; ihdr[9] = 2; ihdr[10] = 0; ihdr[11] = 0; ihdr[12] = 0;
84
+
85
+ const rowSize = 1 + width * 3;
73
86
  const rawData = Buffer.alloc(rowSize * height);
74
87
  for (let y = 0; y < height; y++) {
75
- rawData[y * rowSize] = 0; // no filter
88
+ rawData[y * rowSize] = 0;
76
89
  for (let x = 0; x < width; x++) {
77
90
  const offset = y * rowSize + 1 + x * 3;
78
91
  rawData[offset] = r;
@@ -84,18 +97,15 @@ function createMinimalPNG(width, height, r, g, b) {
84
97
  const zlib = require('zlib');
85
98
  const compressed = zlib.deflateSync(rawData);
86
99
 
87
- // IEND
88
- const iend = Buffer.alloc(0);
89
-
90
100
  return Buffer.concat([
91
101
  signature,
92
102
  makeChunk('IHDR', ihdr),
93
103
  makeChunk('IDAT', compressed),
94
- makeChunk('IEND', iend),
104
+ makeChunk('IEND', Buffer.alloc(0)),
95
105
  ]);
96
106
  }
97
107
 
98
- // ── SKILL.md Parser ────────────────────────────────────────────────────────
108
+ // ── YAML Frontmatter Parser ───────────────────────────────────────────────
99
109
 
100
110
  function parseFrontmatter(content) {
101
111
  const match = content.match(/^---\n([\s\S]*?)\n---/);
@@ -103,58 +113,123 @@ function parseFrontmatter(content) {
103
113
 
104
114
  const yaml = match[1];
105
115
  const result = {};
106
-
107
- // Simple YAML parser for frontmatter fields
108
116
  let currentKey = null;
109
- let blockValue = [];
117
+ let blockLines = [];
110
118
  let inBlock = false;
119
+ let currentIndent = 0;
120
+ let mapKey = null;
121
+ let mapObj = null;
122
+
123
+ function flushBlock() {
124
+ if (inBlock && currentKey) {
125
+ result[currentKey] = blockLines.join('\n').trim();
126
+ inBlock = false;
127
+ blockLines = [];
128
+ }
129
+ if (mapKey && mapObj) {
130
+ result[mapKey] = mapObj;
131
+ mapKey = null;
132
+ mapObj = null;
133
+ }
134
+ }
111
135
 
112
136
  for (const line of yaml.split('\n')) {
137
+ // Handle block scalar continuation
113
138
  if (inBlock) {
114
139
  if (line.match(/^\s{2,}/) || line.trim() === '') {
115
- blockValue.push(line.replace(/^\s{2}/, ''));
140
+ blockLines.push(line.replace(/^\s{2}/, ''));
116
141
  continue;
117
142
  } else {
118
- result[currentKey] = blockValue.join('\n').trim();
119
- inBlock = false;
143
+ flushBlock();
144
+ }
145
+ }
146
+
147
+ // Handle map continuation (metadata:, cowork: style)
148
+ if (mapKey && line.match(/^\s{2,}\w/)) {
149
+ const kvMatch = line.trim().match(/^(\w[\w.-]*)\s*:\s*(.*)/);
150
+ if (kvMatch) {
151
+ mapObj[kvMatch[1]] = kvMatch[2].trim().replace(/^["']|["']$/g, '');
152
+ continue;
120
153
  }
154
+ } else if (mapKey) {
155
+ flushBlock();
121
156
  }
122
157
 
158
+ // Top-level key: value
123
159
  const kvMatch = line.match(/^(\w[\w.-]*)\s*:\s*(.*)/);
124
160
  if (kvMatch) {
125
161
  currentKey = kvMatch[1];
126
162
  const value = kvMatch[2].trim();
127
163
  if (value === '|' || value === '>') {
128
164
  inBlock = true;
129
- blockValue = [];
165
+ blockLines = [];
166
+ } else if (value === '') {
167
+ // Could be a map start (metadata:, cowork:)
168
+ mapKey = currentKey;
169
+ mapObj = {};
130
170
  } else {
131
171
  result[currentKey] = value.replace(/^["']|["']$/g, '');
132
172
  }
133
173
  }
134
174
  }
135
175
 
136
- if (inBlock && currentKey) {
137
- result[currentKey] = blockValue.join('\n').trim();
176
+ flushBlock();
177
+ return result;
178
+ }
179
+
180
+ function extractBody(content) {
181
+ const match = content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)/);
182
+ return match ? match[1].trim() : content;
183
+ }
184
+
185
+ // ── Companion File Path Validation ────────────────────────────────────────
186
+
187
+ function validateCompanionPath(relPath) {
188
+ const errors = [];
189
+ const parts = relPath.split('/');
190
+
191
+ // No absolute paths
192
+ if (path.isAbsolute(relPath)) {
193
+ errors.push(`Absolute path not allowed: ${relPath}`);
194
+ }
195
+ // No path traversal
196
+ if (parts.includes('..')) {
197
+ errors.push(`Path traversal (..) not allowed: ${relPath}`);
198
+ }
199
+ // No backslashes or null bytes
200
+ if (relPath.includes('\\') || relPath.includes('\0')) {
201
+ errors.push(`Backslash or null byte in path: ${relPath}`);
202
+ }
203
+
204
+ for (const part of parts) {
205
+ // No hidden files
206
+ if (part.startsWith('.')) {
207
+ errors.push(`Hidden file not allowed: ${relPath}`);
208
+ }
209
+ // No Windows reserved names
210
+ const baseName = part.replace(/\.[^.]*$/, '').toUpperCase();
211
+ if (WINDOWS_RESERVED.has(baseName)) {
212
+ errors.push(`Windows reserved name: ${part} in ${relPath}`);
213
+ }
214
+ // Safe characters only
215
+ if (!SAFE_FILENAME_RE.test(part)) {
216
+ errors.push(`Unsafe characters in filename: ${part} in ${relPath}`);
217
+ }
138
218
  }
139
219
 
140
- return result;
220
+ return errors;
141
221
  }
142
222
 
143
- // ── Companion File Consolidation ───────────────────────────────────────────
223
+ // ── Companion File Consolidation ──────────────────────────────────────────
144
224
 
145
- function consolidateCompanions(skillDir, skillName) {
146
- /**
147
- * If a skill has more than MAX_COMPANIONS_PER_SKILL companion files,
148
- * consolidate them into category-level reference files.
149
- * Returns an array of { relativePath, content } for the consolidated files.
150
- */
151
- const files = [];
225
+ function discoverCompanions(skillDir) {
152
226
  const allFiles = [];
153
227
 
154
228
  function walkDir(dir, prefix) {
155
229
  if (!fs.existsSync(dir)) return;
156
230
  const entries = fs.readdirSync(dir, { withFileTypes: true });
157
231
  for (const entry of entries) {
232
+ if (entry.name.startsWith('.')) continue; // skip hidden
158
233
  const fullPath = path.join(dir, entry.name);
159
234
  const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
160
235
  if (entry.isDirectory()) {
@@ -166,46 +241,63 @@ function consolidateCompanions(skillDir, skillName) {
166
241
  }
167
242
 
168
243
  walkDir(skillDir, '');
244
+ return allFiles;
245
+ }
169
246
 
247
+ function consolidateCompanions(allFiles, skillName) {
170
248
  if (allFiles.length <= MAX_COMPANIONS_PER_SKILL) {
171
- // No consolidation needed - return original files
172
249
  return allFiles.map(f => ({
173
250
  relativePath: f.relPath,
174
251
  content: fs.readFileSync(f.fullPath),
175
252
  }));
176
253
  }
177
254
 
255
+ const files = [];
256
+
178
257
  // Group by top-level directory
179
- const groups = {};
258
+ const topGroups = {};
180
259
  for (const f of allFiles) {
181
- const parts = f.relPath.split('/');
182
- const topDir = parts.length > 1 ? parts[0] : '_root';
183
- const subDir = parts.length > 2 ? parts.slice(0, 2).join('/') : topDir;
184
- if (!groups[subDir]) groups[subDir] = [];
185
- groups[subDir].push(f);
260
+ const topDir = f.relPath.split('/')[0] || '_root';
261
+ if (!topGroups[topDir]) topGroups[topDir] = [];
262
+ topGroups[topDir].push(f);
186
263
  }
187
264
 
188
- // If groups are still too many, merge into top-level categories
189
- if (Object.keys(groups).length > MAX_COMPANIONS_PER_SKILL) {
190
- const topGroups = {};
191
- for (const f of allFiles) {
192
- const topDir = f.relPath.split('/')[0] || '_root';
193
- if (!topGroups[topDir]) topGroups[topDir] = [];
194
- topGroups[topDir].push(f);
265
+ for (const [dir, dirFiles] of Object.entries(topGroups)) {
266
+ if (dir === '_root') {
267
+ for (const f of dirFiles) {
268
+ files.push({
269
+ relativePath: f.relPath,
270
+ content: fs.readFileSync(f.fullPath),
271
+ });
272
+ }
273
+ continue;
195
274
  }
196
275
 
197
- for (const [dir, dirFiles] of Object.entries(topGroups)) {
198
- if (dir === '_root') {
199
- for (const f of dirFiles) {
200
- files.push({
201
- relativePath: f.relPath,
202
- content: fs.readFileSync(f.fullPath),
203
- });
276
+ // Check if sub-directory grouping keeps within limits
277
+ const subGroups = {};
278
+ for (const f of dirFiles) {
279
+ const parts = f.relPath.split('/');
280
+ const subKey = parts.length > 1 ? parts[1] : '_direct';
281
+ if (!subGroups[subKey]) subGroups[subKey] = [];
282
+ subGroups[subKey].push(f);
283
+ }
284
+
285
+ // If sub-grouping fits, use it; otherwise consolidate entire directory
286
+ if (Object.keys(subGroups).length <= MAX_COMPANIONS_PER_SKILL - files.length) {
287
+ for (const [sub, subFiles] of Object.entries(subGroups)) {
288
+ let consolidated = `# ${dir}/${sub}\n\nConsolidated reference for ${skillName}.\n\n`;
289
+ for (const f of subFiles) {
290
+ const content = fs.readFileSync(f.fullPath, 'utf-8');
291
+ const basename = path.basename(f.name, path.extname(f.name));
292
+ consolidated += `---\n\n## ${basename}\n\n${content}\n\n`;
204
293
  }
205
- continue;
294
+ files.push({
295
+ relativePath: `references/${dir}-${sub}.md`,
296
+ content: Buffer.from(consolidated, 'utf-8'),
297
+ });
206
298
  }
207
-
208
- // Consolidate all files under this dir into one reference file
299
+ } else {
300
+ // Consolidate entire top-level directory into one file
209
301
  let consolidated = `# ${dir}\n\nConsolidated reference for ${skillName}.\n\n`;
210
302
  for (const f of dirFiles) {
211
303
  const content = fs.readFileSync(f.fullPath, 'utf-8');
@@ -213,52 +305,119 @@ function consolidateCompanions(skillDir, skillName) {
213
305
  consolidated += `---\n\n## ${basename}\n\n${content}\n\n`;
214
306
  }
215
307
 
216
- files.push({
217
- relativePath: `references/${dir}.md`,
218
- content: Buffer.from(consolidated, 'utf-8'),
219
- });
220
- }
221
- } else {
222
- // Consolidate sub-directories into category files
223
- for (const [subDir, subFiles] of Object.entries(groups)) {
224
- if (subDir === '_root' || subFiles.length <= 1) {
225
- for (const f of subFiles) {
308
+ const consolidatedBuf = Buffer.from(consolidated, 'utf-8');
309
+
310
+ // Chunk if exceeds 5MB
311
+ if (consolidatedBuf.length > MAX_COMPANION_SIZE) {
312
+ const lines = consolidated.split('\n');
313
+ let chunk = [];
314
+ let chunkSize = 0;
315
+ let chunkIdx = 1;
316
+ for (const line of lines) {
317
+ const lineSize = Buffer.byteLength(line + '\n', 'utf-8');
318
+ if (chunkSize + lineSize > MAX_COMPANION_SIZE * 0.9 && chunk.length > 0) {
319
+ files.push({
320
+ relativePath: `references/${dir}-${String(chunkIdx).padStart(2, '0')}.md`,
321
+ content: Buffer.from(chunk.join('\n'), 'utf-8'),
322
+ });
323
+ chunk = [];
324
+ chunkSize = 0;
325
+ chunkIdx++;
326
+ }
327
+ chunk.push(line);
328
+ chunkSize += lineSize;
329
+ }
330
+ if (chunk.length > 0) {
226
331
  files.push({
227
- relativePath: f.relPath,
228
- content: fs.readFileSync(f.fullPath),
332
+ relativePath: `references/${dir}-${String(chunkIdx).padStart(2, '0')}.md`,
333
+ content: Buffer.from(chunk.join('\n'), 'utf-8'),
229
334
  });
230
335
  }
231
- continue;
336
+ } else {
337
+ files.push({
338
+ relativePath: `references/${dir}.md`,
339
+ content: consolidatedBuf,
340
+ });
232
341
  }
342
+ }
343
+ }
233
344
 
234
- let consolidated = `# ${subDir}\n\n`;
235
- for (const f of subFiles) {
236
- const content = fs.readFileSync(f.fullPath, 'utf-8');
237
- const basename = path.basename(f.name, path.extname(f.name));
238
- consolidated += `---\n\n## ${basename}\n\n${content}\n\n`;
345
+ return files;
346
+ }
347
+
348
+ // ── SKILL.md Rewriter for Cowork ──────────────────────────────────────────
349
+
350
+ function rewriteSkillMdForCowork(content, originalCompanions, packagedCompanions, skillName) {
351
+ /**
352
+ * Creates a Cowork-compatible version of SKILL.md:
353
+ * 1. Adds cowork.category and cowork.icon to frontmatter if missing
354
+ * 2. Adds metadata.author and metadata.version if missing
355
+ * 3. Updates file path references to match consolidated paths
356
+ * 4. Adds "Additional Resources" section listing companion files
357
+ */
358
+ let result = content;
359
+
360
+ // Update frontmatter with Cowork fields
361
+ const fmMatch = result.match(/^(---\n)([\s\S]*?)(\n---)/);
362
+ if (fmMatch) {
363
+ let fm = fmMatch[2];
364
+ const defaults = COWORK_DEFAULTS[skillName] || {};
365
+
366
+ if (!fm.includes('cowork.category') && defaults.category) {
367
+ fm += `\ncowork.category: ${defaults.category}`;
368
+ }
369
+ if (!fm.includes('cowork.icon') && defaults.icon) {
370
+ fm += `\ncowork.icon: ${defaults.icon}`;
371
+ }
372
+ if (!fm.includes('metadata:')) {
373
+ fm += `\nmetadata:\n author: nahisaho\n version: "2.0"`;
374
+ }
375
+
376
+ result = `${fmMatch[1]}${fm}${fmMatch[3]}${result.slice(fmMatch[0].length)}`;
377
+ }
378
+
379
+ // Build path mapping: original -> consolidated
380
+ if (originalCompanions.length > MAX_COMPANIONS_PER_SKILL) {
381
+ const pathMap = {};
382
+ for (const orig of originalCompanions) {
383
+ const topDir = orig.relPath.split('/')[0];
384
+ const consolidated = packagedCompanions.find(
385
+ p => p.relativePath.startsWith(`references/${topDir}`)
386
+ );
387
+ if (consolidated) {
388
+ pathMap[orig.relPath] = consolidated.relativePath;
239
389
  }
390
+ }
240
391
 
241
- files.push({
242
- relativePath: `references/${subDir.replace(/\//g, '-')}.md`,
243
- content: Buffer.from(consolidated, 'utf-8'),
244
- });
392
+ // Replace file path references in body
393
+ for (const [origPath, newPath] of Object.entries(pathMap)) {
394
+ result = result.replace(new RegExp(escapeRegex(origPath), 'g'), newPath);
245
395
  }
246
396
  }
247
397
 
248
- return files;
398
+ // Add Additional Resources section if companions exist and not already present
399
+ if (packagedCompanions.length > 0 && !result.includes('## Additional Resources')) {
400
+ let resources = '\n\n## Additional Resources\n\n';
401
+ for (const c of packagedCompanions) {
402
+ const name = path.basename(c.relativePath, path.extname(c.relativePath));
403
+ resources += `- **\`${c.relativePath}\`** — ${name}\n`;
404
+ }
405
+ result += resources;
406
+ }
407
+
408
+ return result;
409
+ }
410
+
411
+ function escapeRegex(str) {
412
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
249
413
  }
250
414
 
251
- // ── ZIP Builder (uses system zip or tar) ───────────────────────────────────
415
+ // ── ZIP Builder ───────────────────────────────────────────────────────────
252
416
 
253
417
  function createZip(outputPath, files) {
254
- /**
255
- * Creates a zip file from an array of { path, content } entries.
256
- * Uses a temp directory and system zip command.
257
- */
258
418
  const tmpDir = fs.mkdtempSync(path.join(require('os').tmpdir(), 'shikigami-cowork-'));
259
419
 
260
420
  try {
261
- // Write all files to temp directory
262
421
  for (const file of files) {
263
422
  const filePath = path.join(tmpDir, file.path);
264
423
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
@@ -269,19 +428,18 @@ function createZip(outputPath, files) {
269
428
  }
270
429
  }
271
430
 
272
- // Create zip
273
431
  const absOutput = path.resolve(outputPath);
274
432
  try {
275
- execSync(`cd "${tmpDir}" && zip -r "${absOutput}" .`, { stdio: 'pipe' });
433
+ execFileSync('zip', ['-r', absOutput, '.'], { cwd: tmpDir, stdio: 'pipe' });
276
434
  } catch {
277
- // Fallback: try PowerShell (Windows)
435
+ // Fallback: PowerShell (Windows)
278
436
  try {
279
- execSync(
280
- `powershell -Command "Compress-Archive -Path '${tmpDir}${path.sep}*' -DestinationPath '${absOutput}' -Force"`,
281
- { stdio: 'pipe' }
282
- );
437
+ execFileSync('powershell', [
438
+ '-Command',
439
+ `Compress-Archive -Path '${tmpDir}${path.sep}*' -DestinationPath '${absOutput}' -Force`
440
+ ], { stdio: 'pipe' });
283
441
  } catch {
284
- console.error('❌ zip command not found. Install zip or use PowerShell.');
442
+ console.error('❌ zip command not found. Install zip or use PowerShell on Windows.');
285
443
  process.exit(1);
286
444
  }
287
445
  }
@@ -307,14 +465,15 @@ function generateManifest(skills, version) {
307
465
  },
308
466
  name: {
309
467
  short: 'SHIKIGAMI',
310
- full: 'SHIKIGAMI - Deep Research & Consulting Agent Skills for Copilot Cowork',
468
+ full: 'SHIKIGAMI - Deep Research & Consulting Agent Skills',
311
469
  },
312
470
  description: {
313
471
  short: 'AI-powered deep research and consulting framework analysis',
314
- full: 'SHIKIGAMI provides 4 specialized Agent Skills for Copilot Cowork: '
472
+ full: 'SHIKIGAMI provides 4 specialized Agent Skills: '
315
473
  + 'deep research with hallucination prevention, 50+ consulting frameworks '
316
474
  + '(SWOT, PEST, 5Forces, BCG Matrix, etc.), structured report writing, '
317
- + 'and project planning with 5 Whys/JTBD purpose discovery.',
475
+ + 'and project planning with 5 Whys/JTBD purpose discovery. '
476
+ + 'Cross-platform compatible with GitHub Copilot CLI, VS Code, Claude Code, and Gemini CLI.',
318
477
  },
319
478
  icons: {
320
479
  color: 'color.png',
@@ -325,7 +484,44 @@ function generateManifest(skills, version) {
325
484
  };
326
485
  }
327
486
 
328
- // ── Validation ─────────────────────────────────────────────────────────────
487
+ // ── Validation (ASKILL codes from official docs) ──────────────────────────
488
+
489
+ function validateManifest(manifest) {
490
+ const errors = [];
491
+
492
+ // ASKILL-M001: folder required in each agentSkills entry
493
+ for (const skill of manifest.agentSkills || []) {
494
+ if (!skill.folder) {
495
+ errors.push('[ASKILL-M001] `folder` is required for each agentSkills entry');
496
+ }
497
+ }
498
+
499
+ // ASKILL-M002: max 20 skills
500
+ if ((manifest.agentSkills || []).length > MAX_SKILLS) {
501
+ errors.push(`[ASKILL-M002] Max ${MAX_SKILLS} agentSkills entries (found ${manifest.agentSkills.length})`);
502
+ }
503
+
504
+ // ASKILL-M003: folder path max 256 chars
505
+ for (const skill of manifest.agentSkills || []) {
506
+ if (skill.folder && skill.folder.length > MAX_FOLDER_PATH) {
507
+ errors.push(`[ASKILL-M003] Folder path exceeds ${MAX_FOLDER_PATH} chars: ${skill.folder}`);
508
+ }
509
+ }
510
+
511
+ // ASKILL-P008: no duplicate folder values
512
+ const folders = (manifest.agentSkills || []).map(s => s.folder);
513
+ const dupes = folders.filter((f, i) => folders.indexOf(f) !== i);
514
+ if (dupes.length > 0) {
515
+ errors.push(`[ASKILL-P008] Duplicate folder values: ${[...new Set(dupes)].join(', ')}`);
516
+ }
517
+
518
+ // GUID format
519
+ if (!manifest.id || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(manifest.id)) {
520
+ errors.push('[Manifest] Invalid GUID format for id');
521
+ }
522
+
523
+ return errors;
524
+ }
329
525
 
330
526
  function validateSkill(skillDir, skillName) {
331
527
  const errors = [];
@@ -333,57 +529,87 @@ function validateSkill(skillDir, skillName) {
333
529
 
334
530
  const skillMdPath = path.join(skillDir, 'SKILL.md');
335
531
  if (!fs.existsSync(skillMdPath)) {
336
- errors.push(`SKILL.md not found in ${skillName}`);
532
+ // ASKILL-P002
533
+ errors.push(`[ASKILL-P002] SKILL.md not found in ${skillName}`);
337
534
  return { errors, warnings };
338
535
  }
339
536
 
340
537
  const content = fs.readFileSync(skillMdPath, 'utf-8');
341
538
  const frontmatter = parseFrontmatter(content);
342
539
 
540
+ // ASKILL-P003: valid YAML frontmatter
343
541
  if (!frontmatter) {
344
- errors.push(`${skillName}: No YAML frontmatter found`);
542
+ errors.push(`[ASKILL-P003] No valid YAML frontmatter in ${skillName}/SKILL.md`);
345
543
  return { errors, warnings };
346
544
  }
347
545
 
348
- // Validate name
546
+ // ASKILL-P004: name field required
349
547
  if (!frontmatter.name) {
350
- errors.push(`${skillName}: Missing 'name' field in frontmatter`);
548
+ errors.push(`[ASKILL-P004] Missing 'name' field in ${skillName}/SKILL.md frontmatter`);
351
549
  } else {
550
+ // ASKILL-P006: name matches folder name
352
551
  if (frontmatter.name !== skillName) {
353
- errors.push(`${skillName}: Frontmatter name '${frontmatter.name}' doesn't match folder '${skillName}'`);
552
+ errors.push(`[ASKILL-P006] Frontmatter name '${frontmatter.name}' doesn't match folder '${skillName}'`);
354
553
  }
554
+ // ASKILL-P007: name is kebab-case, 1-64 chars
355
555
  if (frontmatter.name.length > 64) {
356
- errors.push(`${skillName}: Name exceeds 64 characters`);
556
+ errors.push(`[ASKILL-P007] Name exceeds 64 characters: ${frontmatter.name}`);
357
557
  }
358
558
  if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(frontmatter.name)) {
359
- errors.push(`${skillName}: Name must be kebab-case`);
559
+ errors.push(`[ASKILL-P007] Name must be kebab-case: ${frontmatter.name}`);
360
560
  }
361
561
  }
362
562
 
363
- // Validate description
563
+ // ASKILL-P005: description field required, 1-1024 chars
364
564
  if (!frontmatter.description) {
365
- errors.push(`${skillName}: Missing 'description' field in frontmatter`);
565
+ errors.push(`[ASKILL-P005] Missing 'description' field in ${skillName}/SKILL.md frontmatter`);
366
566
  } else if (frontmatter.description.length > 1024) {
367
- warnings.push(`${skillName}: Description exceeds 1024 characters (${frontmatter.description.length})`);
567
+ errors.push(`[ASKILL-P005] Description exceeds 1024 characters (${frontmatter.description.length}) in ${skillName}`);
568
+ }
569
+
570
+ // Body size warning
571
+ const body = extractBody(content);
572
+ const wordCount = body.split(/\s+/).filter(w => w.length > 0).length;
573
+ if (wordCount > RECOMMENDED_BODY_WORDS) {
574
+ warnings.push(`${skillName}: SKILL.md body is ~${wordCount} words (recommended <${RECOMMENDED_BODY_WORDS}). Consider moving details to references/`);
575
+ }
576
+
577
+ // Discover and validate companion files
578
+ const originalCompanions = discoverCompanions(skillDir);
579
+
580
+ // Validate companion file paths
581
+ for (const f of originalCompanions) {
582
+ const pathErrors = validateCompanionPath(f.relPath);
583
+ for (const e of pathErrors) {
584
+ errors.push(`[Companion] ${skillName}: ${e}`);
585
+ }
368
586
  }
369
587
 
370
- // Count companion files
371
- const companions = consolidateCompanions(skillDir, skillName);
372
- if (companions.length > MAX_COMPANIONS_PER_SKILL) {
373
- errors.push(`${skillName}: ${companions.length} companion files after consolidation (max ${MAX_COMPANIONS_PER_SKILL})`);
588
+ // Consolidate companions for packaging
589
+ const packagedCompanions = consolidateCompanions(originalCompanions, skillName);
590
+
591
+ // Validate consolidated results
592
+ if (packagedCompanions.length > MAX_COMPANIONS_PER_SKILL) {
593
+ errors.push(`[Companion] ${skillName}: ${packagedCompanions.length} files after consolidation (max ${MAX_COMPANIONS_PER_SKILL})`);
374
594
  }
375
595
 
376
- const totalSize = companions.reduce((sum, f) => sum + f.content.length, 0);
596
+ const totalSize = packagedCompanions.reduce((sum, f) => sum + f.content.length, 0);
377
597
  if (totalSize > MAX_COMPANION_TOTAL) {
378
- errors.push(`${skillName}: Total companion size ${(totalSize / 1024 / 1024).toFixed(1)}MB exceeds 10MB limit`);
598
+ errors.push(`[Companion] ${skillName}: Total size ${(totalSize / 1024 / 1024).toFixed(1)}MB exceeds 10MB limit`);
379
599
  }
380
600
 
381
- const oversized = companions.filter(f => f.content.length > MAX_COMPANION_SIZE);
382
- for (const f of oversized) {
383
- errors.push(`${skillName}: ${f.relativePath} exceeds 5MB limit`);
601
+ for (const f of packagedCompanions) {
602
+ if (f.content.length > MAX_COMPANION_SIZE) {
603
+ errors.push(`[Companion] ${skillName}: ${f.relativePath} exceeds 5MB limit`);
604
+ }
605
+ // Validate packaged paths too
606
+ const pathErrors = validateCompanionPath(f.relativePath);
607
+ for (const e of pathErrors) {
608
+ errors.push(`[Companion] ${skillName}: ${e}`);
609
+ }
384
610
  }
385
611
 
386
- return { errors, warnings, companions, frontmatter };
612
+ return { errors, warnings, originalCompanions, packagedCompanions, frontmatter, content };
387
613
  }
388
614
 
389
615
  // ── Main ───────────────────────────────────────────────────────────────────
@@ -402,6 +628,7 @@ function showHelp() {
402
628
  🎭 SHIKIGAMI Cowork Plugin Builder
403
629
 
404
630
  Builds a Microsoft 365 Copilot Cowork plugin package (.zip)
631
+ following the official "Build a plugin from scratch" guide.
405
632
 
406
633
  Usage: npx shikigami cowork [options]
407
634
 
@@ -410,8 +637,17 @@ Options:
410
637
  --check Validation only (dry-run)
411
638
  -h, --help Show this help
412
639
 
413
- Output:
414
- Creates shikigami-cowork-plugin.zip ready for M365 Admin Center upload.
640
+ Steps (per official docs):
641
+ 1. Discover & validate SKILL.md files (frontmatter, naming, paths)
642
+ 2. Consolidate companion files (max 20 per skill)
643
+ 3. Rewrite SKILL.md for Cowork (add cowork.category, cowork.icon, metadata)
644
+ 4. Generate manifest.json (M365 Unified App Manifest devPreview)
645
+ 5. Generate icons (color.png 192×192, outline.png 32×32)
646
+ 6. Create .zip package
647
+
648
+ Cross-platform:
649
+ Same SKILL.md files work in GitHub Copilot CLI, VS Code Copilot,
650
+ Claude Code, Gemini CLI, Cursor, JetBrains Junie, and Copilot Cowork.
415
651
 
416
652
  Reference:
417
653
  https://learn.microsoft.com/microsoft-365/copilot/cowork/cowork-plugin-development
@@ -431,15 +667,13 @@ function build() {
431
667
  console.log('====================================');
432
668
  console.log('');
433
669
 
434
- // Locate skills directory
670
+ // ── Step 0: Locate skills directory ──
435
671
  const packageDir = path.resolve(__dirname, '..');
436
672
  let skillsRoot = path.join(packageDir, 'github-assets', 'skills');
437
673
 
438
- // If running from installed package, check node_modules path
439
674
  if (!fs.existsSync(skillsRoot)) {
440
675
  skillsRoot = path.join(packageDir, '.github', 'skills');
441
676
  }
442
- // Also check project's .github/skills (after init)
443
677
  if (!fs.existsSync(skillsRoot)) {
444
678
  skillsRoot = path.join(process.cwd(), '.github', 'skills');
445
679
  }
@@ -450,14 +684,17 @@ function build() {
450
684
  process.exit(1);
451
685
  }
452
686
 
453
- // Read package version
454
687
  let version = '1.0.0';
455
688
  try {
456
689
  const pkg = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf-8'));
457
690
  version = pkg.version || version;
458
691
  } catch { /* ignore */ }
459
692
 
460
- // Discover skills
693
+ // ── Step 1: Discover & validate skills ──
694
+ console.log('Step 1: Discovering and validating skills...');
695
+ console.log(`📂 Skills root: ${skillsRoot}`);
696
+ console.log('');
697
+
461
698
  const skillDirs = fs.readdirSync(skillsRoot, { withFileTypes: true })
462
699
  .filter(d => d.isDirectory() && d.name.startsWith('shikigami-'))
463
700
  .map(d => d.name);
@@ -467,11 +704,6 @@ function build() {
467
704
  process.exit(1);
468
705
  }
469
706
 
470
- console.log(`📂 Skills root: ${skillsRoot}`);
471
- console.log(`📦 Found ${skillDirs.length} skills`);
472
- console.log('');
473
-
474
- // Validate all skills
475
707
  let hasErrors = false;
476
708
  const validatedSkills = [];
477
709
 
@@ -486,13 +718,19 @@ function build() {
486
718
  for (const warn of result.warnings) console.log(` ⚠️ ${warn}`);
487
719
 
488
720
  if (result.errors.length === 0) {
489
- const companionCount = result.companions ? result.companions.length : 0;
490
- console.log(` ✅ ${skillName} (${companionCount} companion files)`);
721
+ const origCount = result.originalCompanions ? result.originalCompanions.length : 0;
722
+ const pkgCount = result.packagedCompanions ? result.packagedCompanions.length : 0;
723
+ const consolidatedNote = origCount > MAX_COMPANIONS_PER_SKILL
724
+ ? ` (consolidated ${origCount} → ${pkgCount})`
725
+ : '';
726
+ console.log(` ✅ ${skillName} (${pkgCount} companion files${consolidatedNote})`);
491
727
  validatedSkills.push({
492
728
  name: skillName,
493
729
  dir: skillDir,
494
- companions: result.companions,
730
+ originalCompanions: result.originalCompanions,
731
+ packagedCompanions: result.packagedCompanions,
495
732
  frontmatter: result.frontmatter,
733
+ content: result.content,
496
734
  });
497
735
  }
498
736
  }
@@ -505,63 +743,89 @@ function build() {
505
743
  }
506
744
 
507
745
  if (validatedSkills.length > MAX_SKILLS) {
508
- console.error(`❌ Too many skills: ${validatedSkills.length} (max ${MAX_SKILLS})`);
746
+ console.error(`❌ [ASKILL-M002] Too many skills: ${validatedSkills.length} (max ${MAX_SKILLS})`);
747
+ process.exit(1);
748
+ }
749
+
750
+ // ── Manifest validation ──
751
+ const manifest = generateManifest(validatedSkills, version);
752
+ const manifestErrors = validateManifest(manifest);
753
+ if (manifestErrors.length > 0) {
754
+ hasErrors = true;
755
+ for (const err of manifestErrors) console.log(` ❌ ${err}`);
756
+ }
757
+
758
+ if (hasErrors) {
759
+ console.error('❌ Manifest validation failed.');
509
760
  process.exit(1);
510
761
  }
511
762
 
512
763
  if (check) {
513
764
  console.log('✅ Validation passed (dry-run mode)');
514
765
  console.log(` ${validatedSkills.length} skills ready for packaging`);
766
+ console.log('');
767
+ console.log('Cross-platform compatibility:');
768
+ console.log(' ✅ GitHub Copilot CLI');
769
+ console.log(' ✅ VS Code Copilot');
770
+ console.log(' ✅ Microsoft 365 Copilot Cowork');
771
+ console.log(' ✅ Claude Code / Gemini CLI / Cursor');
515
772
  process.exit(0);
516
773
  }
517
774
 
518
- // Build package
519
- console.log('📦 Building Cowork plugin package...');
775
+ // ── Step 2-5: Build package ──
776
+ console.log('Step 2: Building Cowork plugin package...');
520
777
 
521
778
  const zipFiles = [];
522
779
 
523
- // 1. Generate manifest.json
524
- const manifest = generateManifest(validatedSkills, version);
780
+ // Step 4: manifest.json
525
781
  zipFiles.push({
526
782
  path: 'manifest.json',
527
783
  content: JSON.stringify(manifest, null, 2),
528
784
  });
529
785
  console.log(' ✅ manifest.json');
530
786
 
531
- // 2. Generate icons
532
- const colorPng = createMinimalPNG(192, 192, 107, 70, 193); // Purple (#6B46C1)
787
+ // Step 5: Icons
788
+ const colorPng = createMinimalPNG(192, 192, 107, 70, 193);
533
789
  const outlinePng = createMinimalPNG(32, 32, 107, 70, 193);
534
790
  zipFiles.push({ path: 'color.png', content: colorPng });
535
791
  zipFiles.push({ path: 'outline.png', content: outlinePng });
536
792
  console.log(' ✅ color.png (192×192)');
537
793
  console.log(' ✅ outline.png (32×32)');
538
794
 
539
- // 3. Copy skills with consolidation
795
+ // Steps 1-2: Skills with Cowork-specific SKILL.md
540
796
  for (const skill of validatedSkills) {
541
- // Copy SKILL.md
542
- const skillMd = fs.readFileSync(path.join(skill.dir, 'SKILL.md'));
797
+ // Rewrite SKILL.md for Cowork (add metadata, category, icon, update paths)
798
+ const rewrittenSkillMd = rewriteSkillMdForCowork(
799
+ skill.content,
800
+ skill.originalCompanions,
801
+ skill.packagedCompanions,
802
+ skill.name
803
+ );
804
+
543
805
  zipFiles.push({
544
806
  path: `skills/${skill.name}/SKILL.md`,
545
- content: skillMd,
807
+ content: rewrittenSkillMd,
546
808
  });
547
809
 
548
810
  // Copy companion files (already consolidated)
549
- for (const companion of skill.companions) {
811
+ for (const companion of skill.packagedCompanions) {
550
812
  zipFiles.push({
551
813
  path: `skills/${skill.name}/${companion.relativePath}`,
552
814
  content: companion.content,
553
815
  });
554
816
  }
555
817
 
556
- console.log(` ✅ skills/${skill.name}/ (${skill.companions.length + 1} files)`);
818
+ console.log(` ✅ skills/${skill.name}/ (${skill.packagedCompanions.length + 1} files)`);
557
819
  }
558
820
 
559
- // 4. Create ZIP
821
+ // ── Step 6: Create ZIP ──
822
+ console.log('');
823
+ console.log('Step 3: Creating ZIP package...');
824
+
560
825
  const outDir = path.resolve(out);
561
826
  fs.mkdirSync(outDir, { recursive: true });
562
827
  const zipPath = path.join(outDir, 'shikigami-cowork-plugin.zip');
563
828
 
564
- // Remove old zip if exists
565
829
  if (fs.existsSync(zipPath)) {
566
830
  fs.unlinkSync(zipPath);
567
831
  }
@@ -570,15 +834,23 @@ function build() {
570
834
 
571
835
  const zipSize = fs.statSync(zipPath).size;
572
836
  console.log('');
573
- console.log(`✅ Plugin package created: ${zipPath}`);
574
- console.log(` Size: ${(zipSize / 1024).toFixed(1)} KB`);
575
- console.log(` Skills: ${validatedSkills.length}`);
576
- console.log(` Version: ${version}`);
837
+ console.log('✅ Plugin package created!');
838
+ console.log('');
839
+ console.log(` 📦 ${zipPath}`);
840
+ console.log(` 📏 Size: ${(zipSize / 1024).toFixed(1)} KB`);
841
+ console.log(` 🔧 Skills: ${validatedSkills.length}`);
842
+ console.log(` 📋 Version: ${version}`);
843
+ console.log('');
844
+ console.log('Cross-platform compatibility:');
845
+ console.log(' ✅ GitHub Copilot CLI — use `npx shikigami init`');
846
+ console.log(' ✅ VS Code Copilot — same SKILL.md format');
847
+ console.log(' ✅ Copilot Cowork — upload .zip to M365 Admin Center');
848
+ console.log(' ✅ Claude Code — same SKILL.md format');
577
849
  console.log('');
578
- console.log('📝 Next steps:');
579
- console.log(' 1. Open M365 Admin Center > Manage Apps > Upload Custom App');
580
- console.log(' 2. Upload shikigami-cowork-plugin.zip');
581
- console.log(' 3. Open Cowork > Sources & Skills to verify');
850
+ console.log('📝 Next steps (Copilot Cowork):');
851
+ console.log(' 1. Open M365 Admin Center > Manage Apps > Upload Custom App');
852
+ console.log(' 2. Upload shikigami-cowork-plugin.zip');
853
+ console.log(' 3. Open Cowork > Sources & Skills to verify');
582
854
  console.log('');
583
855
  console.log('📖 https://learn.microsoft.com/microsoft-365/copilot/cowork/cowork-plugin-development');
584
856
  console.log('');