wize-dev-kit 0.1.1 → 0.1.2

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,23 @@ Format inspired by [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.1.2] — 2026-05-31
9
+
10
+ ### Added
11
+
12
+ - **Claude Code adapter is now functional.** `npx wize-dev-kit install` (when `claude-code` is selected as IDE target) generates `.claude/skills/wize-*/SKILL.md` for every agent, workflow and skill in the kit. Each SKILL.md uses the Claude Code skill frontmatter so the slash menu picks up `/wize-orchestrator`, `/wize-product-brief`, `/wize-create-prd`, `/wize-tea-gate`, etc.
13
+ - **`wize-help` reworked as an orchestrator-aware skill.** Moved from `core-skills/` to `orchestrator-skills/`. The skill instructs Wizer to read `.wize/config/project.toml` plus the state of `.wize/planning/`, `.wize/solutioning/`, `.wize/implementation/` and reply with a phase-aware "what to do next" answer. Four modes: default, `next`, `status`, `personas`.
14
+ - **Interactive prompts in the installer.** Profile and IDE-target selection now use arrow-keys + space (checkbox) via the `prompts` dependency. Non-TTY environments fall back to the previous number-based prompt automatically (preserves CI scriptability).
15
+ - Adapter test suite (`test/adapter-claude-code.test.js`) covering: per-agent SKILL.md emission, overlay-gate behavior (core-only vs full), `wize-help` content checks, dry-run safety.
16
+
17
+ ### Changed
18
+
19
+ - Profile gating now lives inside the Claude Code adapter: overlay workflows (`wize-web-*`, `wize-app-*`) are only emitted when the corresponding overlay is in the selected profiles.
20
+
21
+ ### Dependencies
22
+
23
+ - Added `prompts@^2.4.2` (1 sub-dependency, ~5 KB). Only used by the interactive installer; not bundled into the kit's runtime artifacts.
24
+
8
25
  ## [0.1.1] — 2026-05-31
9
26
 
10
27
  ### Changed
@@ -41,6 +58,7 @@ Format inspired by [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
41
58
  - Inspired by [BMAD Method v6.8.0](https://github.com/bmad-code-org/BMAD-METHOD).
42
59
  - WDS module inspired by [bmad-method-wds-expansion](https://github.com/bmad-code-org/bmad-method-wds-expansion).
43
60
 
44
- [Unreleased]: https://github.com/qwize-br/wize-development-kit/compare/v0.1.1...HEAD
61
+ [Unreleased]: https://github.com/qwize-br/wize-development-kit/compare/v0.1.2...HEAD
62
+ [0.1.2]: https://github.com/qwize-br/wize-development-kit/compare/v0.1.1...v0.1.2
45
63
  [0.1.1]: https://github.com/qwize-br/wize-development-kit/compare/v0.1.0...v0.1.1
46
64
  [0.1.0]: https://github.com/qwize-br/wize-development-kit/releases/tag/v0.1.0
@@ -1,15 +1,186 @@
1
- /* Stub renderer for this adapter — emits a console preview of what would be written. */
1
+ //
2
+ // Claude Code adapter — renders kit assets into .claude/skills/wize-{code}/SKILL.md.
3
+ //
4
+ // Strategy:
5
+ // - For each agent (agent.yaml + persona.md), emit a SKILL.md that pairs the
6
+ // agent identity with its responsibilities, so Claude Code can activate
7
+ // the persona via slash command.
8
+ // - For each workflow/skill file, emit a SKILL.md that wraps the workflow
9
+ // content with an activation frontmatter Claude Code understands.
10
+ //
11
+ // Each SKILL.md uses the Claude Code skill format:
12
+ // ---
13
+ // name: <code>
14
+ // description: <one-liner shown in the slash menu>
15
+ // ---
16
+ // <body>
17
+ //
2
18
  'use strict';
3
19
 
4
20
  const fs = require('node:fs');
5
21
  const path = require('node:path');
22
+ const { walkAgents, walkWorkflows, walkSkills } = require('../../tools/installer/validators/walk.js');
6
23
 
24
+ function readYamlField(content, field) {
25
+ // Minimal scalar/quoted-string extractor for top-level YAML scalars.
26
+ const re = new RegExp(`^${field}:\\s*(?:"([^"]*)"|'([^']*)'|(.*?))\\s*$`, 'm');
27
+ const m = content.match(re);
28
+ if (!m) return null;
29
+ return (m[1] || m[2] || m[3] || '').trim();
30
+ }
31
+
32
+ function readFrontmatter(content) {
33
+ // Returns an object of top-level key:value pairs from a leading
34
+ // ---\n...\n--- block. Simple scalars only.
35
+ if (!content.startsWith('---')) return {};
36
+ const end = content.indexOf('\n---', 3);
37
+ if (end === -1) return {};
38
+ const fm = content.slice(3, end).split('\n');
39
+ const out = {};
40
+ for (const line of fm) {
41
+ const m = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*):\s*(.*?)\s*$/);
42
+ if (m) out[m[1]] = m[2].replace(/^"|"$/g, '').replace(/^'|'$/g, '');
43
+ }
44
+ return out;
45
+ }
46
+
47
+ function bodyAfterFrontmatter(content) {
48
+ if (!content.startsWith('---')) return content;
49
+ const end = content.indexOf('\n---', 3);
50
+ if (end === -1) return content;
51
+ return content.slice(end + 4).replace(/^\s+/, '');
52
+ }
53
+
54
+ function escapeForYamlDouble(str) {
55
+ return String(str).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
56
+ }
57
+
58
+ function clipOneLine(str, max = 240) {
59
+ const flat = String(str).replace(/\s+/g, ' ').trim();
60
+ if (flat.length <= max) return flat;
61
+ return flat.slice(0, max - 1).trimEnd() + '…';
62
+ }
63
+
64
+ function renderAgentSkill({ code, name, title, description, body }) {
65
+ const desc = clipOneLine(`${name} (${title}) — ${description}`);
66
+ return `---
67
+ name: ${code}
68
+ description: "${escapeForYamlDouble(desc)}"
69
+ ---
70
+
71
+ # ${name} — ${title}
72
+
73
+ ${body.trim()}
74
+ `;
75
+ }
76
+
77
+ function renderWorkflowSkill({ code, name, description, body }) {
78
+ const desc = clipOneLine(description || `Workflow ${code}`);
79
+ return `---
80
+ name: ${code}
81
+ description: "${escapeForYamlDouble(desc)}"
82
+ ---
83
+
84
+ # ${name}
85
+
86
+ ${body.trim()}
87
+ `;
88
+ }
89
+
90
+ function ensureDir(dir) {
91
+ fs.mkdirSync(dir, { recursive: true });
92
+ }
93
+
94
+ function writeSkill(skillsDir, code, content) {
95
+ const dir = path.join(skillsDir, code);
96
+ ensureDir(dir);
97
+ fs.writeFileSync(path.join(dir, 'SKILL.md'), content, 'utf-8');
98
+ }
99
+
100
+ /**
101
+ * Render the full kit into `.claude/skills/` at the project root.
102
+ *
103
+ * @param {string} kitRoot - absolute path to the installed wize-dev-kit package
104
+ * @param {string} projectRoot - absolute path to the target repo (cwd of install)
105
+ * @param {object} opts
106
+ * @param {string[]} [opts.profiles] - selected profile codes (e.g., ['core', 'web-overlay'])
107
+ * @param {boolean} [opts.dryRun] - when true, return the list without writing
108
+ * @returns {{ written: string[], skipped: string[] }}
109
+ */
7
110
  function render(kitRoot, projectRoot, opts = {}) {
8
- const adapterDir = __dirname;
9
- const cfg = require('node:fs').readFileSync(path.join(adapterDir, 'adapter.yaml'), 'utf-8');
10
- const targetPath = (cfg.match(/^target_path:\s*"(.+)"$/m) || [])[1] || '';
11
- const filePattern = (cfg.match(/^file_pattern:\s*"(.+)"$/m) || [])[1] || '';
12
- console.log(`[adapter:${path.basename(adapterDir)}] would emit ${filePattern} under ${path.join(projectRoot, targetPath)}`);
111
+ const skillsDir = path.join(projectRoot, '.claude', 'skills');
112
+ const written = [];
113
+ const skipped = [];
114
+ const profiles = new Set(opts.profiles || ['core']);
115
+ const dryRun = !!opts.dryRun;
116
+
117
+ if (!dryRun) ensureDir(skillsDir);
118
+
119
+ // 1. Agents
120
+ for (const yamlPath of walkAgents(kitRoot)) {
121
+ const dir = path.dirname(yamlPath);
122
+ const yaml = fs.readFileSync(yamlPath, 'utf-8');
123
+ const code = readYamlField(yaml, 'code');
124
+ const name = readYamlField(yaml, 'name');
125
+ const title = readYamlField(yaml, 'title');
126
+ const description = readYamlField(yaml, 'description') || '';
127
+ if (!code || !name) { skipped.push(yamlPath); continue; }
128
+
129
+ const personaPath = path.join(dir, 'persona.md');
130
+ const persona = fs.existsSync(personaPath) ? fs.readFileSync(personaPath, 'utf-8') : '';
131
+ const body = persona || description;
132
+
133
+ const content = renderAgentSkill({ code, name, title, description, body });
134
+ if (dryRun) {
135
+ written.push(`[dry-run] ${code}/SKILL.md`);
136
+ } else {
137
+ writeSkill(skillsDir, code, content);
138
+ written.push(`${code}/SKILL.md`);
139
+ }
140
+ }
141
+
142
+ // 2. Workflows — gate by overlay selection
143
+ for (const wfPath of walkWorkflows(kitRoot)) {
144
+ const content = fs.readFileSync(wfPath, 'utf-8');
145
+ const fm = readFrontmatter(content);
146
+ const code = fm.code;
147
+ const name = fm.name || code;
148
+ if (!code) { skipped.push(wfPath); continue; }
149
+
150
+ if (fm.overlay === 'web' && !profiles.has('web-overlay')) { skipped.push(wfPath); continue; }
151
+ if (fm.overlay === 'app' && !profiles.has('app-overlay')) { skipped.push(wfPath); continue; }
152
+
153
+ const description = `${fm.phase || fm.gate || 'workflow'}: ${name}`;
154
+ const body = bodyAfterFrontmatter(content);
155
+ const skillContent = renderWorkflowSkill({ code, name, description, body });
156
+ if (dryRun) {
157
+ written.push(`[dry-run] ${code}/SKILL.md`);
158
+ } else {
159
+ writeSkill(skillsDir, code, skillContent);
160
+ written.push(`${code}/SKILL.md`);
161
+ }
162
+ }
163
+
164
+ // 3. Standalone skills
165
+ for (const skPath of walkSkills(kitRoot)) {
166
+ const content = fs.readFileSync(skPath, 'utf-8');
167
+ const fm = readFrontmatter(content);
168
+ const code = fm.code;
169
+ const name = fm.name || code;
170
+ if (!code) { skipped.push(skPath); continue; }
171
+
172
+ const description = fm.module ? `${fm.module} skill: ${name}` : name;
173
+ const body = bodyAfterFrontmatter(content);
174
+ const skillContent = renderWorkflowSkill({ code, name, description, body });
175
+ if (dryRun) {
176
+ written.push(`[dry-run] ${code}/SKILL.md`);
177
+ } else {
178
+ writeSkill(skillsDir, code, skillContent);
179
+ written.push(`${code}/SKILL.md`);
180
+ }
181
+ }
182
+
183
+ return { written, skipped };
13
184
  }
14
185
 
15
186
  module.exports = { render };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "wize-dev-kit",
4
- "version": "0.1.1",
4
+ "version": "0.1.2",
5
5
  "description": "Full-lifecycle AI-assisted development kit with Test Architect and Whiteport Design Studio embedded. Inspired by BMAD Method and WDS.",
6
6
  "keywords": [
7
7
  "ai",
@@ -48,5 +48,8 @@
48
48
  "ARCH.md",
49
49
  "ROSTER.md",
50
50
  "DECISIONS.md"
51
- ]
51
+ ],
52
+ "dependencies": {
53
+ "prompts": "^2.4.2"
54
+ }
52
55
  }
@@ -16,9 +16,6 @@ skills:
16
16
  - code: wize-brainstorming
17
17
  name: "Brainstorming"
18
18
  description: "Divergent-then-convergent ideation; produces idea-pool.md."
19
- - code: wize-help
20
- name: "Help"
21
- description: "Routes user to the right agent/workflow based on intent."
22
19
  - code: wize-shard-doc
23
20
  name: "Shard Doc"
24
21
  description: "Splits large markdown docs (PRD, architecture) into addressable shards."
@@ -13,6 +13,9 @@ agents:
13
13
  description: "Knows the user deeply, parses raw demand, routes to the right specialist. Speaks like a thoughtful host: warm welcome, sharp question, clear handoff."
14
14
 
15
15
  skills:
16
+ - code: wize-help
17
+ name: "Help (Wizer)"
18
+ description: "Project-state-aware help. Reads .wize/ and tells the user what to do next. Modes: default, next, status, personas."
16
19
  - code: wize-onboarding
17
20
  name: "Onboarding"
18
21
  description: "Post-install triage: greenfield vs brownfield, profile, objective. Delegates to specialists."
@@ -0,0 +1,102 @@
1
+ ---
2
+ code: wize-help
3
+ name: Help (Wizer)
4
+ module: orchestrator
5
+ owner: wize-orchestrator
6
+ status: ready
7
+ ---
8
+
9
+ # Wizer · `/wize-help`
10
+
11
+ You are **Wizer**, the orchestrator. The user invoked `/wize-help`. Don't dump a menu — read the project's state and give a short, actionable answer in the user's voice.
12
+
13
+ ## Modes
14
+
15
+ | Invocation | What to do |
16
+ |---|---|
17
+ | `/wize-help` or `/wize` (no argument) | Greeting + project snapshot + the **single best next step**. |
18
+ | `/wize-help next` | Just the next step. Skip the snapshot. |
19
+ | `/wize-help status` | Snapshot only — phase, last TEA gate, in-flight stories. |
20
+ | `/wize-help personas` | List only the personas relevant to the active profiles. |
21
+
22
+ ## Step 1 — read project state
23
+
24
+ Read these files if they exist (they may not — that's information too):
25
+
26
+ | Path | Tells you |
27
+ |---|---|
28
+ | `.wize/config/project.toml` | Active profiles, IDE targets, communication & document languages, project name. |
29
+ | `.wize/config/tea.toml` | TEA gate policy (advisory vs enforcing). |
30
+ | `.wize/planning/brief.md` | Whether Phase 1 (Pepper) started. |
31
+ | `.wize/planning/research.md` | Whether research was done. |
32
+ | `.wize/planning/ux/trigger-map.md` | Whether WDS trigger map exists. |
33
+ | `.wize/planning/prd.md` | Whether Phase 2 (Maria Hill) PRD exists. |
34
+ | `.wize/planning/ux/ux-scenarios.md`, `.wize/planning/ux/ux-design/**` | Whether Mantis ran UX. |
35
+ | `.wize/planning/tech-vision.md`, `nfr-principles.md` | Whether Fury set tech strategy. |
36
+ | `.wize/solutioning/architecture.md` | Whether Tony's architecture exists. |
37
+ | `.wize/solutioning/stories/**/*.md` | Whether stories are sliced. |
38
+ | `.wize/implementation/tea/risk-profile.md` | Whether Hawkeye's risk profile is in place. |
39
+ | `.wize/implementation/tea/**/gate.md` | Last gate decisions per story. |
40
+ | `.wize/implementation/sprint-status.md` | Active sprint state. |
41
+
42
+ ## Step 2 — determine current phase
43
+
44
+ Apply this heuristic, top-down. Stop at the first match.
45
+
46
+ 1. **No `.wize/` folder.** → Tell the user the kit isn't installed; suggest `npx wize-dev-kit install`.
47
+ 2. **No `brief.md`.** → Phase 1. Next: **Pepper / `wize-product-brief`**.
48
+ 3. **`brief.md` exists, no `trigger-map.md`.** → Still Phase 1. Next: **Pepper / `wize-trigger-map`**.
49
+ 4. **No `prd.md`.** → Phase 2. Next: **Maria Hill / `wize-create-prd`**.
50
+ 5. **`prd.md` exists, no `ux-scenarios.md`.** → Phase 2 UX. Next: **Mantis / `wize-ux-scenarios`**.
51
+ 6. **`ux-design/` empty.** → Phase 2 UX continues. Next: **Mantis / `wize-ux-design`**.
52
+ 7. **No `tech-vision.md` or `nfr-principles.md`.** → Phase 2→3 boundary. Next: **Fury / `wize-tech-vision`** then `wize-nfr-principles`.
53
+ 8. **No `architecture.md`.** → Phase 3. Next: **Tony / `wize-create-architecture`**.
54
+ 9. **No `stories/**/*.md`.** → Phase 3 still. Next: **Tony / `wize-create-epics-and-stories`**.
55
+ 10. **No `tea/risk-profile.md`.** → Phase 3 closeout. Next: **Hawkeye / `wize-tea-risk`**.
56
+ 11. **Has stories but `sprint-status.md` shows no active sprint.** → Phase 4 start. Next: **Maria Hill / `wize-sprint-planning`**.
57
+ 12. **Has active sprint, oldest in-flight story has no `tea/.../design.md`.** → Next: **Hawkeye / `wize-tea-design`** for that story.
58
+ 13. **In-flight story exists, no implementation commits.** → Next: **Shuri / `wize-dev-story`** on that story.
59
+ 14. **In-flight story exists with code, no `gate.md`.** → Next: **Hawkeye / `wize-tea-trace` → `wize-tea-review` → `wize-tea-gate`** for that story.
60
+ 15. **All stories gated.** → Next: **Wizer / `wize-retrospective`** + plan next epic.
61
+
62
+ For brownfield repos where `.wize/knowledge/document-project/` is missing, prepend: "Run `wize-document-project` first to baseline the codebase."
63
+
64
+ ## Step 3 — respond
65
+
66
+ Default response shape (3 lines):
67
+
68
+ ```
69
+ Welcome back. {project name} — {profiles, e.g., "Core + Web"}.
70
+ You're at: {phase + last completed artifact}.
71
+ Next: /{next workflow} ({persona}).
72
+ ```
73
+
74
+ For `status`, return a markdown table:
75
+
76
+ ```
77
+ | Item | State |
78
+ |---|---|
79
+ | Phase | … |
80
+ | Profiles | … |
81
+ | Last TEA gate | … (PASS/CONCERNS/FAIL/WAIVED) |
82
+ | In-flight stories | … |
83
+ | Active sprint | … |
84
+ | TEA policy | advisory / enforcing |
85
+ ```
86
+
87
+ For `personas`, list only personas whose role applies to active profiles. Always include Wizer, Pepper, Peggy, Maria Hill, Mantis, Fury, Tony, Hawkeye, Shuri (all are profile-independent core roles). If `web-overlay` active, note that **Mantis** has the WCAG/responsive playbook loaded and **Hawkeye** has Playwright/Vitest patterns. If `app-overlay` active, note HIG/Material 3 for Mantis and Detox/Maestro for Hawkeye.
88
+
89
+ ## Step 4 — offer to act
90
+
91
+ End every response with one of:
92
+
93
+ - "Want me to call {persona}?" — if the next step is clear.
94
+ - "Want me to baseline the repo first?" — if brownfield + no document-project.
95
+ - "Want me to call a party-mode with {persona1} + {persona2}?" — if the next step has a cross-cutting decision.
96
+
97
+ ## Style
98
+
99
+ - Speak in the user's `communication` language (from `.wize/config/project.toml`).
100
+ - One sharp question is better than three sentences of advice.
101
+ - If the user invoked `/wize-help next`, give them just the next step in one line and stop.
102
+ - If user invoked `/wize-help status`, give the table and stop. Don't suggest actions.
@@ -12,6 +12,9 @@
12
12
  const fs = require('node:fs');
13
13
  const path = require('node:path');
14
14
  const readline = require('node:readline');
15
+ const prompts = require('prompts');
16
+
17
+ const INTERACTIVE = process.stdout.isTTY && process.stdin.isTTY;
15
18
 
16
19
  const KIT_ROOT = path.resolve(__dirname, '..', '..');
17
20
  const KIT_VERSION = require(path.join(KIT_ROOT, 'package.json')).version;
@@ -94,12 +97,41 @@ async function confirm(question, defaultYes = true) {
94
97
  }
95
98
 
96
99
  async function selectLanguage(label, defaultCode = 'en') {
100
+ if (INTERACTIVE) {
101
+ const choices = LANGUAGES.map(l => ({
102
+ title: `${l.code.padEnd(6)} — ${l.label}`,
103
+ value: l.code,
104
+ description: l.code === defaultCode ? 'default' : undefined
105
+ }));
106
+ choices.push({ title: 'Other (type a custom BCP-47 code)…', value: '__custom' });
107
+ const initial = LANGUAGES.findIndex(l => l.code === defaultCode);
108
+ const { picked } = await prompts({
109
+ type: 'select',
110
+ name: 'picked',
111
+ message: label,
112
+ choices,
113
+ initial: initial === -1 ? 0 : initial
114
+ });
115
+ if (picked === undefined) process.exit(130);
116
+ if (picked === '__custom') {
117
+ const { custom } = await prompts({
118
+ type: 'text',
119
+ name: 'custom',
120
+ message: 'Type a BCP-47 language code (e.g. ko, ar, nl, ru)',
121
+ initial: defaultCode,
122
+ validate: v => /^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})?$/.test(v) || 'use a valid code like "en" or "pt-BR"'
123
+ });
124
+ return custom || defaultCode;
125
+ }
126
+ return picked;
127
+ }
128
+ // Non-TTY fallback: number-driven.
97
129
  console.log(`\n${label}:`);
98
130
  LANGUAGES.forEach((l, i) => {
99
131
  const def = l.code === defaultCode ? '(default)' : '';
100
132
  console.log(` ${String(i + 1).padStart(2)}. ${l.code.padEnd(6)} — ${l.label} ${def}`);
101
133
  });
102
- const ans = (await prompt(`Pick a number, type a code (e.g. "en", "pt-BR", "ko"), or ENTER for ${defaultCode}: `)).trim();
134
+ const ans = (await prompt(`Pick a number, type a code, or ENTER for ${defaultCode}: `)).trim();
103
135
  if (!ans) return defaultCode;
104
136
  const asNum = parseInt(ans, 10);
105
137
  if (!isNaN(asNum) && asNum >= 1 && asNum <= LANGUAGES.length) return LANGUAGES[asNum - 1].code;
@@ -107,6 +139,26 @@ async function selectLanguage(label, defaultCode = 'en') {
107
139
  }
108
140
 
109
141
  async function multiSelect(label, items, isSelected = i => i.default) {
142
+ if (INTERACTIVE) {
143
+ const { picked } = await prompts({
144
+ type: 'multiselect',
145
+ name: 'picked',
146
+ message: label,
147
+ hint: '↑/↓ navigate · space toggle · enter confirm',
148
+ instructions: false,
149
+ choices: items.map(it => ({
150
+ title: it.label + (it.required ? ' (required)' : ''),
151
+ value: it.code,
152
+ selected: it.required || isSelected(it),
153
+ disabled: it.required
154
+ }))
155
+ });
156
+ if (picked === undefined) process.exit(130);
157
+ const codes = new Set(picked);
158
+ for (const req of items.filter(i => i.required)) codes.add(req.code);
159
+ return items.filter(it => codes.has(it.code));
160
+ }
161
+ // Non-TTY fallback.
110
162
  console.log(`\n${label}:`);
111
163
  items.forEach((it, i) => {
112
164
  const star = it.required ? '★ ' : ' ';
@@ -114,9 +166,7 @@ async function multiSelect(label, items, isSelected = i => i.default) {
114
166
  console.log(` ${i + 1}. ${star}${it.label} ${def}`);
115
167
  });
116
168
  const ans = await prompt('Pick numbers (comma-separated) or ENTER for defaults: ');
117
- if (!ans) {
118
- return items.filter(it => it.required || isSelected(it));
119
- }
169
+ if (!ans) return items.filter(it => it.required || isSelected(it));
120
170
  const indices = ans.split(',').map(s => parseInt(s.trim(), 10) - 1).filter(i => !isNaN(i));
121
171
  const picked = indices.map(i => items[i]).filter(Boolean);
122
172
  for (const req of items.filter(i => i.required)) {
@@ -227,6 +277,35 @@ function userToml() {
227
277
  `;
228
278
  }
229
279
 
280
+ function renderAdapters({ kitRoot, projectRoot, targets, profiles }) {
281
+ const results = [];
282
+ const profileCodes = profiles.map(p => p.code);
283
+ for (const target of targets) {
284
+ const adapterDir = path.join(kitRoot, 'adapters', target.code);
285
+ const renderPath = path.join(adapterDir, 'render.js');
286
+ if (!fs.existsSync(renderPath)) {
287
+ results.push({ code: target.code, skipped: true, reason: 'adapter missing' });
288
+ continue;
289
+ }
290
+ try {
291
+ const mod = require(renderPath);
292
+ if (typeof mod.render !== 'function') {
293
+ results.push({ code: target.code, skipped: true, reason: 'no render() export' });
294
+ continue;
295
+ }
296
+ const out = mod.render(kitRoot, projectRoot, { profiles: profileCodes });
297
+ if (out && typeof out === 'object' && Array.isArray(out.written)) {
298
+ results.push({ code: target.code, written: out.written.length });
299
+ } else {
300
+ results.push({ code: target.code, skipped: true, reason: 'stub (no skills emitted)' });
301
+ }
302
+ } catch (err) {
303
+ results.push({ code: target.code, error: err.message });
304
+ }
305
+ }
306
+ return results;
307
+ }
308
+
230
309
  async function cmdInstall(args) {
231
310
  const cwd = process.cwd();
232
311
  console.log(logo());
@@ -270,8 +349,18 @@ async function cmdInstall(args) {
270
349
  console.log(`✓ profiles: ${profiles.map(p => p.code).join(', ')}`);
271
350
  console.log(`✓ ide targets: ${targets.map(t => t.code).join(', ')}`);
272
351
 
273
- console.log('\n(stub) IDE adapter generation would run now for each target.');
274
- console.log('(stub) onboarding handoff to Wizer would start here.');
352
+ console.log('\nGenerating IDE adapters...');
353
+ const renderResults = renderAdapters({
354
+ kitRoot: KIT_ROOT,
355
+ projectRoot: cwd,
356
+ targets,
357
+ profiles
358
+ });
359
+ for (const r of renderResults) {
360
+ if (r.skipped) console.log(` - ${r.code}: skipped (${r.reason})`);
361
+ else if (r.written) console.log(` ✓ ${r.code}: ${r.written} skill(s) emitted`);
362
+ else if (r.error) console.log(` ✖ ${r.code}: ${r.error}`);
363
+ }
275
364
 
276
365
  if (detection.brownfield) {
277
366
  const baseline = await confirm('\nRun `wize-document-project` to baseline the existing repo now?', true);
@@ -1,18 +0,0 @@
1
- ---
2
- code: wize-help
3
- name: Help
4
- module: core
5
- status: stub
6
- ---
7
-
8
- # Help
9
-
10
- Routes user intent to the right agent or workflow.
11
-
12
- ## Examples
13
- - "I need to start a new project." → Wizer onboarding.
14
- - "I have a brief; what's next?" → Maria Hill / `wize-create-prd`.
15
- - "Design system?" → Mantis / `wize-design-system`.
16
- - "Architecture?" → Tony / `wize-create-architecture`.
17
- - "Test plan for this story?" → Hawkeye / `wize-tea-design`.
18
- - "Add a custom agent." → Builder / `wize-create-agent`.