wize-dev-kit 0.1.2 → 0.1.4
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 +47 -1
- package/adapters/antigravity/adapter.yaml +3 -3
- package/adapters/antigravity/render.js +8 -7
- package/adapters/claude-code/adapter.yaml +1 -1
- package/adapters/claude-code/render.js +7 -178
- package/adapters/codex/adapter.yaml +3 -3
- package/adapters/codex/render.js +6 -7
- package/adapters/continue/adapter.yaml +4 -4
- package/adapters/continue/render.js +34 -6
- package/adapters/cursor/adapter.yaml +2 -2
- package/adapters/cursor/render.js +40 -6
- package/adapters/generic/adapter.yaml +2 -2
- package/adapters/generic/render.js +26 -6
- package/adapters/kimi-code/adapter.yaml +3 -3
- package/adapters/kimi-code/render.js +8 -7
- package/adapters/opencode/adapter.yaml +3 -3
- package/adapters/opencode/render.js +61 -6
- package/adapters/windsurf/adapter.yaml +2 -2
- package/adapters/windsurf/render.js +31 -6
- package/package.json +1 -1
- package/src/orchestrator-skills/wize-help/skill.md +9 -2
- package/src/orchestrator-skills/wize-orchestrator/persona.md +9 -1
- package/tools/installer/render-shared.js +187 -0
- package/tools/installer/setup-helpers.js +105 -0
- package/tools/installer/wize-cli.js +26 -9
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,50 @@ Format inspired by [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.1.4] — 2026-06-01
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`user_name` prompt at install time.** The installer asks "How should the agents call you?" (default = `$USER`), and persists the answer to `.wize/config/user.toml` under `[user] name`. Wizer and `wize-help` read this file and greet the user by name (e.g., *"Welcome back, [USER_NAME]. {project} — {profiles}. Next: …"*).
|
|
13
|
+
- **Opt-in suggested `.gitignore` block.** The installer asks whether to apply the suggested entries. When accepted, an idempotent block is injected between `# >>> wize-dev-kit (managed) >>>` and `# <<< wize-dev-kit (managed) <<<` markers covering: per-developer files (`user.toml`, `scratch/`, `.local/`, `quick-dev-log.md`) and the generated IDE adapter outputs (`.claude/skills/wize-*`, `.agent/skills/wize-*`, `.cursor/rules/wize-*.mdc`, etc.). Re-running install only updates lines between the markers; everything outside is untouched. Declining keeps `.gitignore` exactly as it was.
|
|
14
|
+
- `tools/installer/setup-helpers.js`: pure-ish helpers for `applyGitignore()` (create / append / replace / unchanged) and `generateUserToml()`.
|
|
15
|
+
- 8 new unit tests covering gitignore idempotency (create, append, idempotent re-run, stale-block replace, dry-run) and user.toml generation (name, escaping, optional role). Total: 33 passing.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Wizer (orchestrator) persona and `wize-help` skill now instruct the agent to read `.wize/config/user.toml` and use `[user] name` when greeting.
|
|
20
|
+
- `project.toml` template comment clarifies "personal preferences live in user.toml".
|
|
21
|
+
|
|
22
|
+
### Recommended layout (for a team repo)
|
|
23
|
+
|
|
24
|
+
Commit: `.wize/config/project.toml`, `.wize/config/tea.toml`, `.wize/planning/`, `.wize/solutioning/`, `.wize/implementation/tea/`, `.wize/implementation/retrospective/`, `.wize/knowledge/`, `.wize/custom/`, `AGENTS.md`.
|
|
25
|
+
Ignore (handled by the suggested block): `.wize/config/user.toml`, `.wize/scratch/`, `.wize/.local/`, `.wize/implementation/quick-dev-log.md`, and all generated adapter outputs (each developer runs `npx wize-dev-kit install` for their own IDE target).
|
|
26
|
+
|
|
27
|
+
## [0.1.3] — 2026-06-01
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **All 9 IDE adapters now ship real renderers** (previously 8 of them were stub printers). Each adapter writes files at the canonical path each harness expects:
|
|
32
|
+
- **Claude Code** → `.claude/skills/wize-{code}/SKILL.md`
|
|
33
|
+
- **Antigravity (Google)** → `.agent/skills/wize-{code}/SKILL.md` (singular `.agent`; `.antigravitycli/` is the CLI's own state and is left untouched)
|
|
34
|
+
- **OpenAI Codex CLI** → `.agents/skills/wize-{code}/SKILL.md`
|
|
35
|
+
- **Moonshot Kimi Code** → `.kimi/skills/wize-{code}/SKILL.md`
|
|
36
|
+
- **Cursor** → `.cursor/rules/wize-{code}.mdc` (with `description`, `globs`, `alwaysApply` frontmatter)
|
|
37
|
+
- **Windsurf (Codeium)** → `.windsurf/rules/wize-{code}.md`
|
|
38
|
+
- **Continue.dev** → `.continue/prompts/wize-{code}.prompt` (`invokable: true` → slash command)
|
|
39
|
+
- **OpenCode (sst/opencode)** → `.opencode/agents/wize-{code}.md` (personas, with `mode: primary` for Wizer and `mode: subagent` for the others) + `.opencode/commands/wize-{code}.md` (workflows/skills as slash commands)
|
|
40
|
+
- **Generic fallback** → `.wize/agents/wize-{code}.md` + a root `AGENTS.md` baseline (read by Codex, Cursor, Windsurf and Antigravity even without their dedicated adapter)
|
|
41
|
+
- New shared module `tools/installer/render-shared.js` centralizes kit traversal, asset parsing, and the Anthropic-style SKILL.md emitter reused by Claude Code, Antigravity, Codex and Kimi adapters.
|
|
42
|
+
- Adapter test suite expanded from 12 → 25 passing tests, with one sanity test per adapter plus format-specific checks (Cursor frontmatter shape, Continue `invokable: true`, OpenCode primary/subagent mode split, generic AGENTS.md emission).
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- Antigravity adapter previously created/touched nothing useful. It now emits skills at the correct location (`.agent/skills/`) and explicitly avoids `.antigravitycli/` (CLI state, owned by the Antigravity CLI itself).
|
|
47
|
+
|
|
48
|
+
### Notes
|
|
49
|
+
|
|
50
|
+
- Several harnesses inject custom skills at session startup; you may need to restart the IDE after `wize-dev-kit install` for slash commands to appear.
|
|
51
|
+
|
|
8
52
|
## [0.1.2] — 2026-05-31
|
|
9
53
|
|
|
10
54
|
### Added
|
|
@@ -58,7 +102,9 @@ Format inspired by [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
58
102
|
- Inspired by [BMAD Method v6.8.0](https://github.com/bmad-code-org/BMAD-METHOD).
|
|
59
103
|
- WDS module inspired by [bmad-method-wds-expansion](https://github.com/bmad-code-org/bmad-method-wds-expansion).
|
|
60
104
|
|
|
61
|
-
[Unreleased]: https://github.com/qwize-br/wize-development-kit/compare/v0.1.
|
|
105
|
+
[Unreleased]: https://github.com/qwize-br/wize-development-kit/compare/v0.1.4...HEAD
|
|
106
|
+
[0.1.4]: https://github.com/qwize-br/wize-development-kit/compare/v0.1.3...v0.1.4
|
|
107
|
+
[0.1.3]: https://github.com/qwize-br/wize-development-kit/compare/v0.1.2...v0.1.3
|
|
62
108
|
[0.1.2]: https://github.com/qwize-br/wize-development-kit/compare/v0.1.1...v0.1.2
|
|
63
109
|
[0.1.1]: https://github.com/qwize-br/wize-development-kit/compare/v0.1.0...v0.1.1
|
|
64
110
|
[0.1.0]: https://github.com/qwize-br/wize-development-kit/releases/tag/v0.1.0
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
code: antigravity
|
|
2
|
-
description: "Antigravity
|
|
3
|
-
target_path: ".
|
|
4
|
-
file_pattern: "wize
|
|
2
|
+
description: "Google Antigravity skills (.agent/skills, NOT .antigravitycli which is CLI state)."
|
|
3
|
+
target_path: ".agent/skills/"
|
|
4
|
+
file_pattern: "wize-*/SKILL.md"
|
|
5
5
|
file_format: markdown
|
|
6
6
|
hooks:
|
|
7
7
|
pre_render: []
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
// Google Antigravity adapter — emits .agent/skills/wize-{code}/SKILL.md per asset.
|
|
2
|
+
// Note: directory is SINGULAR `.agent`. The `.antigravitycli/` directory is
|
|
3
|
+
// Antigravity's own CLI state and must not be touched. Antigravity also reads
|
|
4
|
+
// the root AGENTS.md.
|
|
5
|
+
|
|
2
6
|
'use strict';
|
|
3
7
|
|
|
4
|
-
const fs = require('node:fs');
|
|
5
8
|
const path = require('node:path');
|
|
9
|
+
const { renderAnthropicSkills } = require('../../tools/installer/render-shared.js');
|
|
6
10
|
|
|
7
11
|
function render(kitRoot, projectRoot, opts = {}) {
|
|
8
|
-
const
|
|
9
|
-
|
|
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)}`);
|
|
12
|
+
const targetDir = path.join(projectRoot, '.agent', 'skills');
|
|
13
|
+
return renderAnthropicSkills(kitRoot, targetDir, opts);
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
module.exports = { render };
|
|
@@ -1,186 +1,15 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
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
|
-
//
|
|
1
|
+
// Claude Code adapter — emits .claude/skills/wize-{code}/SKILL.md per asset.
|
|
2
|
+
// Skill format is identical to the upstream Anthropic spec, so we delegate
|
|
3
|
+
// to the shared renderer.
|
|
4
|
+
|
|
18
5
|
'use strict';
|
|
19
6
|
|
|
20
|
-
const fs = require('node:fs');
|
|
21
7
|
const path = require('node:path');
|
|
22
|
-
const {
|
|
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}
|
|
8
|
+
const { renderAnthropicSkills } = require('../../tools/installer/render-shared.js');
|
|
85
9
|
|
|
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
|
-
*/
|
|
110
10
|
function render(kitRoot, projectRoot, opts = {}) {
|
|
111
|
-
const
|
|
112
|
-
|
|
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 };
|
|
11
|
+
const targetDir = path.join(projectRoot, '.claude', 'skills');
|
|
12
|
+
return renderAnthropicSkills(kitRoot, targetDir, opts);
|
|
184
13
|
}
|
|
185
14
|
|
|
186
15
|
module.exports = { render };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
code: codex
|
|
2
|
-
description: "OpenAI Codex CLI agents."
|
|
3
|
-
target_path: ".
|
|
4
|
-
file_pattern: "wize
|
|
2
|
+
description: "OpenAI Codex CLI agent skills (.agents/skills/, Anthropic-compatible)."
|
|
3
|
+
target_path: ".agents/skills/"
|
|
4
|
+
file_pattern: "wize-*/SKILL.md"
|
|
5
5
|
file_format: markdown
|
|
6
6
|
hooks:
|
|
7
7
|
pre_render: []
|
package/adapters/codex/render.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
// OpenAI Codex CLI adapter — emits .agents/skills/wize-{code}/SKILL.md per asset.
|
|
2
|
+
// Codex shares the Anthropic SKILL.md format and also reads the root AGENTS.md.
|
|
3
|
+
|
|
2
4
|
'use strict';
|
|
3
5
|
|
|
4
|
-
const fs = require('node:fs');
|
|
5
6
|
const path = require('node:path');
|
|
7
|
+
const { renderAnthropicSkills } = require('../../tools/installer/render-shared.js');
|
|
6
8
|
|
|
7
9
|
function render(kitRoot, projectRoot, opts = {}) {
|
|
8
|
-
const
|
|
9
|
-
|
|
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)}`);
|
|
10
|
+
const targetDir = path.join(projectRoot, '.agents', 'skills');
|
|
11
|
+
return renderAnthropicSkills(kitRoot, targetDir, opts);
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
module.exports = { render };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
code: continue
|
|
2
|
-
description: "Continue
|
|
3
|
-
target_path: ".continue/
|
|
4
|
-
file_pattern: "wize-*.
|
|
5
|
-
file_format:
|
|
2
|
+
description: "Continue.dev slash commands (.continue/prompts/, .prompt files)."
|
|
3
|
+
target_path: ".continue/prompts/"
|
|
4
|
+
file_pattern: "wize-*.prompt"
|
|
5
|
+
file_format: prompt
|
|
6
6
|
hooks:
|
|
7
7
|
pre_render: []
|
|
8
8
|
post_render: []
|
|
@@ -1,15 +1,43 @@
|
|
|
1
|
-
|
|
1
|
+
// Continue.dev adapter — emits .continue/prompts/wize-{code}.prompt per asset.
|
|
2
|
+
// Prompt files are markdown with YAML frontmatter: name, description, invokable.
|
|
3
|
+
// `invokable: true` registers the prompt as a slash command.
|
|
4
|
+
// Reference: https://docs.continue.dev/customize/deep-dives/prompts
|
|
5
|
+
|
|
2
6
|
'use strict';
|
|
3
7
|
|
|
4
8
|
const fs = require('node:fs');
|
|
5
9
|
const path = require('node:path');
|
|
10
|
+
const { collectAssets, escapeYamlDouble, clipOneLine, ensureDir } = require('../../tools/installer/render-shared.js');
|
|
6
11
|
|
|
7
12
|
function render(kitRoot, projectRoot, opts = {}) {
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
const targetDir = path.join(projectRoot, '.continue', 'prompts');
|
|
14
|
+
const assets = collectAssets(kitRoot, opts);
|
|
15
|
+
const written = [];
|
|
16
|
+
if (!opts.dryRun) ensureDir(targetDir);
|
|
17
|
+
|
|
18
|
+
for (const a of assets) {
|
|
19
|
+
const desc = clipOneLine(a.description, 180);
|
|
20
|
+
const content = [
|
|
21
|
+
'---',
|
|
22
|
+
`name: ${a.code}`,
|
|
23
|
+
`description: "${escapeYamlDouble(desc)}"`,
|
|
24
|
+
`invokable: true`,
|
|
25
|
+
'---',
|
|
26
|
+
'',
|
|
27
|
+
a.kind === 'agent' ? `# ${a.name} — ${a.title}` : `# ${a.name}`,
|
|
28
|
+
'',
|
|
29
|
+
a.body.trim(),
|
|
30
|
+
''
|
|
31
|
+
].join('\n');
|
|
32
|
+
|
|
33
|
+
if (opts.dryRun) {
|
|
34
|
+
written.push(`[dry-run] ${a.code}.prompt`);
|
|
35
|
+
} else {
|
|
36
|
+
fs.writeFileSync(path.join(targetDir, `${a.code}.prompt`), content, 'utf-8');
|
|
37
|
+
written.push(`${a.code}.prompt`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { written, skipped: [] };
|
|
13
41
|
}
|
|
14
42
|
|
|
15
43
|
module.exports = { render };
|
|
@@ -1,15 +1,49 @@
|
|
|
1
|
-
|
|
1
|
+
// Cursor adapter — emits .cursor/rules/wize-{code}.mdc per asset.
|
|
2
|
+
// Cursor rule frontmatter keys: description (required), globs (comma list),
|
|
3
|
+
// alwaysApply (boolean). Mode is inferred from combinations.
|
|
4
|
+
// Reference: https://cursor.com/docs/context/rules
|
|
5
|
+
|
|
2
6
|
'use strict';
|
|
3
7
|
|
|
4
8
|
const fs = require('node:fs');
|
|
5
9
|
const path = require('node:path');
|
|
10
|
+
const { collectAssets, escapeYamlDouble, clipOneLine, ensureDir } = require('../../tools/installer/render-shared.js');
|
|
6
11
|
|
|
7
12
|
function render(kitRoot, projectRoot, opts = {}) {
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
const targetDir = path.join(projectRoot, '.cursor', 'rules');
|
|
14
|
+
const assets = collectAssets(kitRoot, opts);
|
|
15
|
+
const written = [];
|
|
16
|
+
if (!opts.dryRun) ensureDir(targetDir);
|
|
17
|
+
|
|
18
|
+
for (const a of assets) {
|
|
19
|
+
const desc = clipOneLine(
|
|
20
|
+
a.kind === 'agent'
|
|
21
|
+
? `${a.name} (${a.title}) — ${a.description}`
|
|
22
|
+
: a.description
|
|
23
|
+
);
|
|
24
|
+
// Agents become alwaysApply=false rules attached on demand;
|
|
25
|
+
// workflows/skills are Agent-attachable via description.
|
|
26
|
+
const content = [
|
|
27
|
+
'---',
|
|
28
|
+
`description: "${escapeYamlDouble(desc)}"`,
|
|
29
|
+
`globs:`,
|
|
30
|
+
`alwaysApply: false`,
|
|
31
|
+
'---',
|
|
32
|
+
'',
|
|
33
|
+
a.kind === 'agent' ? `# ${a.name} — ${a.title}` : `# ${a.name}`,
|
|
34
|
+
'',
|
|
35
|
+
a.body.trim(),
|
|
36
|
+
''
|
|
37
|
+
].join('\n');
|
|
38
|
+
|
|
39
|
+
if (opts.dryRun) {
|
|
40
|
+
written.push(`[dry-run] ${a.code}.mdc`);
|
|
41
|
+
} else {
|
|
42
|
+
fs.writeFileSync(path.join(targetDir, `${a.code}.mdc`), content, 'utf-8');
|
|
43
|
+
written.push(`${a.code}.mdc`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { written, skipped: [] };
|
|
13
47
|
}
|
|
14
48
|
|
|
15
49
|
module.exports = { render };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
code: generic
|
|
2
|
-
description: "Generic markdown fallback
|
|
2
|
+
description: "Generic markdown fallback (.wize/agents/) + AGENTS.md baseline."
|
|
3
3
|
target_path: ".wize/agents/"
|
|
4
|
-
file_pattern: "wize-*.md"
|
|
4
|
+
file_pattern: "wize-*.md + AGENTS.md"
|
|
5
5
|
file_format: markdown
|
|
6
6
|
hooks:
|
|
7
7
|
pre_render: []
|
|
@@ -1,15 +1,35 @@
|
|
|
1
|
-
|
|
1
|
+
// Generic fallback adapter — emits plain markdown under .wize/agents/ and
|
|
2
|
+
// writes a root AGENTS.md as a baseline pointer (read by Codex, Cursor,
|
|
3
|
+
// Windsurf and Antigravity even without their dedicated adapter installed).
|
|
4
|
+
|
|
2
5
|
'use strict';
|
|
3
6
|
|
|
4
7
|
const fs = require('node:fs');
|
|
5
8
|
const path = require('node:path');
|
|
9
|
+
const { collectAssets, renderAgentsMd, clipOneLine, ensureDir } = require('../../tools/installer/render-shared.js');
|
|
6
10
|
|
|
7
11
|
function render(kitRoot, projectRoot, opts = {}) {
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
const targetDir = path.join(projectRoot, '.wize', 'agents');
|
|
13
|
+
const assets = collectAssets(kitRoot, opts);
|
|
14
|
+
const written = [];
|
|
15
|
+
if (!opts.dryRun) ensureDir(targetDir);
|
|
16
|
+
|
|
17
|
+
for (const a of assets) {
|
|
18
|
+
const summary = clipOneLine(a.description, 180);
|
|
19
|
+
const header = a.kind === 'agent' ? `# ${a.name} — ${a.title}` : `# ${a.name}`;
|
|
20
|
+
const content = [header, '', `> ${summary}`, '', a.body.trim(), ''].join('\n');
|
|
21
|
+
if (opts.dryRun) {
|
|
22
|
+
written.push(`[dry-run] ${a.code}.md`);
|
|
23
|
+
} else {
|
|
24
|
+
fs.writeFileSync(path.join(targetDir, `${a.code}.md`), content, 'utf-8');
|
|
25
|
+
written.push(`${a.code}.md`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Baseline AGENTS.md (not overwritten if user already has one).
|
|
30
|
+
const agentsMd = renderAgentsMd(kitRoot, projectRoot, opts);
|
|
31
|
+
written.push(...agentsMd.written);
|
|
32
|
+
return { written, skipped: agentsMd.skipped };
|
|
13
33
|
}
|
|
14
34
|
|
|
15
35
|
module.exports = { render };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
code: kimi-code
|
|
2
|
-
description: "Moonshot Kimi Code
|
|
3
|
-
target_path: ".kimi/
|
|
4
|
-
file_pattern: "wize
|
|
2
|
+
description: "Moonshot Kimi Code skills (.kimi/skills/, Anthropic-compatible)."
|
|
3
|
+
target_path: ".kimi/skills/"
|
|
4
|
+
file_pattern: "wize-*/SKILL.md"
|
|
5
5
|
file_format: markdown
|
|
6
6
|
hooks:
|
|
7
7
|
pre_render: []
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
// Moonshot Kimi Code adapter — emits .kimi/skills/wize-{code}/SKILL.md per asset.
|
|
2
|
+
// Kimi Code consumes the Anthropic SKILL.md format verbatim and additionally
|
|
3
|
+
// auto-detects .claude/skills/ and .codex/skills/, so installing alongside
|
|
4
|
+
// those adapters is harmless.
|
|
5
|
+
|
|
2
6
|
'use strict';
|
|
3
7
|
|
|
4
|
-
const fs = require('node:fs');
|
|
5
8
|
const path = require('node:path');
|
|
9
|
+
const { renderAnthropicSkills } = require('../../tools/installer/render-shared.js');
|
|
6
10
|
|
|
7
11
|
function render(kitRoot, projectRoot, opts = {}) {
|
|
8
|
-
const
|
|
9
|
-
|
|
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)}`);
|
|
12
|
+
const targetDir = path.join(projectRoot, '.kimi', 'skills');
|
|
13
|
+
return renderAnthropicSkills(kitRoot, targetDir, opts);
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
module.exports = { render };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
code: opencode
|
|
2
|
-
description: "OpenCode
|
|
3
|
-
target_path: ".opencode/
|
|
4
|
-
file_pattern: "wize-*.md"
|
|
2
|
+
description: "OpenCode agents + commands (.opencode/agents/ and .opencode/commands/)."
|
|
3
|
+
target_path: ".opencode/"
|
|
4
|
+
file_pattern: "agents/wize-*.md, commands/wize-*.md"
|
|
5
5
|
file_format: markdown
|
|
6
6
|
hooks:
|
|
7
7
|
pre_render: []
|
|
@@ -1,15 +1,70 @@
|
|
|
1
|
-
|
|
1
|
+
// OpenCode (sst/opencode) adapter — emits two trees:
|
|
2
|
+
// - .opencode/agents/wize-{code}.md (personas → system prompts)
|
|
3
|
+
// - .opencode/commands/wize-{code}.md (workflows/skills → slash commands)
|
|
4
|
+
// Frontmatter (agents): description, mode (primary|subagent|all)
|
|
5
|
+
// Frontmatter (commands): description, template (the prompt body)
|
|
6
|
+
// Reference: https://opencode.ai/docs/agents/ + /docs/commands/
|
|
7
|
+
|
|
2
8
|
'use strict';
|
|
3
9
|
|
|
4
10
|
const fs = require('node:fs');
|
|
5
11
|
const path = require('node:path');
|
|
12
|
+
const { collectAssets, escapeYamlDouble, clipOneLine, ensureDir } = require('../../tools/installer/render-shared.js');
|
|
6
13
|
|
|
7
14
|
function render(kitRoot, projectRoot, opts = {}) {
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
15
|
+
const agentsDir = path.join(projectRoot, '.opencode', 'agents');
|
|
16
|
+
const commandsDir = path.join(projectRoot, '.opencode', 'commands');
|
|
17
|
+
const assets = collectAssets(kitRoot, opts);
|
|
18
|
+
const written = [];
|
|
19
|
+
|
|
20
|
+
if (!opts.dryRun) {
|
|
21
|
+
ensureDir(agentsDir);
|
|
22
|
+
ensureDir(commandsDir);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
for (const a of assets) {
|
|
26
|
+
if (a.kind === 'agent') {
|
|
27
|
+
const desc = clipOneLine(`${a.name} (${a.title}) — ${a.description}`);
|
|
28
|
+
const mode = a.code === 'wize-orchestrator' ? 'primary' : 'subagent';
|
|
29
|
+
const content = [
|
|
30
|
+
'---',
|
|
31
|
+
`description: "${escapeYamlDouble(desc)}"`,
|
|
32
|
+
`mode: ${mode}`,
|
|
33
|
+
'---',
|
|
34
|
+
'',
|
|
35
|
+
`# ${a.name} — ${a.title}`,
|
|
36
|
+
'',
|
|
37
|
+
a.body.trim(),
|
|
38
|
+
''
|
|
39
|
+
].join('\n');
|
|
40
|
+
if (opts.dryRun) {
|
|
41
|
+
written.push(`[dry-run] agents/${a.code}.md`);
|
|
42
|
+
} else {
|
|
43
|
+
fs.writeFileSync(path.join(agentsDir, `${a.code}.md`), content, 'utf-8');
|
|
44
|
+
written.push(`agents/${a.code}.md`);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
// workflow or skill → command
|
|
48
|
+
const desc = clipOneLine(a.description, 180);
|
|
49
|
+
const content = [
|
|
50
|
+
'---',
|
|
51
|
+
`description: "${escapeYamlDouble(desc)}"`,
|
|
52
|
+
'---',
|
|
53
|
+
'',
|
|
54
|
+
`# ${a.name}`,
|
|
55
|
+
'',
|
|
56
|
+
a.body.trim(),
|
|
57
|
+
''
|
|
58
|
+
].join('\n');
|
|
59
|
+
if (opts.dryRun) {
|
|
60
|
+
written.push(`[dry-run] commands/${a.code}.md`);
|
|
61
|
+
} else {
|
|
62
|
+
fs.writeFileSync(path.join(commandsDir, `${a.code}.md`), content, 'utf-8');
|
|
63
|
+
written.push(`commands/${a.code}.md`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { written, skipped: [] };
|
|
13
68
|
}
|
|
14
69
|
|
|
15
70
|
module.exports = { render };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
code: windsurf
|
|
2
|
-
description: "Windsurf Cascade
|
|
3
|
-
target_path: ".windsurf/
|
|
2
|
+
description: "Windsurf Cascade rules (.windsurf/rules/, plain markdown)."
|
|
3
|
+
target_path: ".windsurf/rules/"
|
|
4
4
|
file_pattern: "wize-*.md"
|
|
5
5
|
file_format: markdown
|
|
6
6
|
hooks:
|
|
@@ -1,15 +1,40 @@
|
|
|
1
|
-
|
|
1
|
+
// Windsurf (Codeium) adapter — emits .windsurf/rules/wize-{code}.md per asset.
|
|
2
|
+
// Windsurf rules are plain markdown; activation mode is configured inside the
|
|
3
|
+
// IDE Rules panel, not in frontmatter. Cascade picks them up on session start.
|
|
4
|
+
// Reference: https://docs.windsurf.com/windsurf/cascade/memories#rules
|
|
5
|
+
|
|
2
6
|
'use strict';
|
|
3
7
|
|
|
4
8
|
const fs = require('node:fs');
|
|
5
9
|
const path = require('node:path');
|
|
10
|
+
const { collectAssets, clipOneLine, ensureDir } = require('../../tools/installer/render-shared.js');
|
|
6
11
|
|
|
7
12
|
function render(kitRoot, projectRoot, opts = {}) {
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
const targetDir = path.join(projectRoot, '.windsurf', 'rules');
|
|
14
|
+
const assets = collectAssets(kitRoot, opts);
|
|
15
|
+
const written = [];
|
|
16
|
+
if (!opts.dryRun) ensureDir(targetDir);
|
|
17
|
+
|
|
18
|
+
for (const a of assets) {
|
|
19
|
+
const summary = clipOneLine(a.description, 180);
|
|
20
|
+
const header = a.kind === 'agent' ? `# ${a.name} — ${a.title}` : `# ${a.name}`;
|
|
21
|
+
const content = [
|
|
22
|
+
header,
|
|
23
|
+
'',
|
|
24
|
+
`> ${summary}`,
|
|
25
|
+
'',
|
|
26
|
+
a.body.trim(),
|
|
27
|
+
''
|
|
28
|
+
].join('\n');
|
|
29
|
+
|
|
30
|
+
if (opts.dryRun) {
|
|
31
|
+
written.push(`[dry-run] ${a.code}.md`);
|
|
32
|
+
} else {
|
|
33
|
+
fs.writeFileSync(path.join(targetDir, `${a.code}.md`), content, 'utf-8');
|
|
34
|
+
written.push(`${a.code}.md`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { written, skipped: [] };
|
|
13
38
|
}
|
|
14
39
|
|
|
15
40
|
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.
|
|
4
|
+
"version": "0.1.4",
|
|
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",
|
|
@@ -26,6 +26,7 @@ Read these files if they exist (they may not — that's information too):
|
|
|
26
26
|
| Path | Tells you |
|
|
27
27
|
|---|---|
|
|
28
28
|
| `.wize/config/project.toml` | Active profiles, IDE targets, communication & document languages, project name. |
|
|
29
|
+
| `.wize/config/user.toml` | Per-developer preferences. Use `[user] name` to greet the user by name. Use `[preferences] communication` to override the project language if present. |
|
|
29
30
|
| `.wize/config/tea.toml` | TEA gate policy (advisory vs enforcing). |
|
|
30
31
|
| `.wize/planning/brief.md` | Whether Phase 1 (Pepper) started. |
|
|
31
32
|
| `.wize/planning/research.md` | Whether research was done. |
|
|
@@ -63,14 +64,20 @@ For brownfield repos where `.wize/knowledge/document-project/` is missing, prepe
|
|
|
63
64
|
|
|
64
65
|
## Step 3 — respond
|
|
65
66
|
|
|
66
|
-
Default response shape (3 lines):
|
|
67
|
+
Default response shape (3 lines). When `user.toml` provides a `[user] name`, include it in the greeting:
|
|
67
68
|
|
|
68
69
|
```
|
|
69
|
-
Welcome back. {project name} — {profiles, e.g., "Core + Web"}.
|
|
70
|
+
Welcome back{{, <user.name> when present}}. {project name} — {profiles, e.g., "Core + Web"}.
|
|
70
71
|
You're at: {phase + last completed artifact}.
|
|
71
72
|
Next: /{next workflow} ({persona}).
|
|
72
73
|
```
|
|
73
74
|
|
|
75
|
+
Concrete example with personalization filled in:
|
|
76
|
+
|
|
77
|
+
> Welcome back, [USER_NAME]. wize-development-kit — Core + Web.
|
|
78
|
+
> You're at: Phase 3 closeout — architecture signed, no risk profile yet.
|
|
79
|
+
> Next: `/wize-tea-risk` (Hawkeye).
|
|
80
|
+
|
|
74
81
|
For `status`, return a markdown table:
|
|
75
82
|
|
|
76
83
|
```
|
|
@@ -17,6 +17,14 @@ I am **Wizer**. I am the host of this development kit. I know who you are, what
|
|
|
17
17
|
- I speak the user's language (configurable in `.wize/config/project.toml`).
|
|
18
18
|
- I never narrate my reasoning aloud — I just route.
|
|
19
19
|
|
|
20
|
+
## Personalization
|
|
21
|
+
|
|
22
|
+
Before greeting, read `.wize/config/user.toml` if it exists. If it has `[user] name = "…"`, call the user by that name. If it also has `role = "…"`, factor that into how technical/strategic you frame follow-ups (a PM gets framing, a developer gets file paths).
|
|
23
|
+
|
|
24
|
+
If `user.toml` is missing or has no `name`, fall back to a neutral greeting.
|
|
25
|
+
|
|
20
26
|
## Greet
|
|
21
27
|
|
|
22
|
-
> "Welcome back. What are we working on?"
|
|
28
|
+
> "Welcome back{{`, ` + user.name when present, else ''}}. What are we working on?"
|
|
29
|
+
|
|
30
|
+
Example with personalization filled in: *"Welcome back, [USER_NAME]. What are we working on?"*
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// Shared helpers used by all IDE adapters: kit traversal, asset parsing,
|
|
2
|
+
// and renderers for the Anthropic-style "SKILL.md" format (Claude Code,
|
|
3
|
+
// Antigravity, OpenAI Codex, Kimi Code all use it verbatim).
|
|
4
|
+
//
|
|
5
|
+
// Adapters that need a different format (Cursor .mdc, Windsurf, Continue,
|
|
6
|
+
// OpenCode) consume the parsed assets via collectAssets() and then render
|
|
7
|
+
// per their own conventions.
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('node:fs');
|
|
12
|
+
const path = require('node:path');
|
|
13
|
+
const { walkAgents, walkWorkflows, walkSkills } = require('./validators/walk.js');
|
|
14
|
+
|
|
15
|
+
function readYamlField(content, field) {
|
|
16
|
+
const re = new RegExp('^' + field + ':\\s*(?:"([^"]*)"|\'([^\']*)\'|(.*?))\\s*$', 'm');
|
|
17
|
+
const m = content.match(re);
|
|
18
|
+
if (!m) return null;
|
|
19
|
+
return (m[1] || m[2] || m[3] || '').trim();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readFrontmatter(content) {
|
|
23
|
+
if (!content.startsWith('---')) return {};
|
|
24
|
+
const end = content.indexOf('\n---', 3);
|
|
25
|
+
if (end === -1) return {};
|
|
26
|
+
const lines = content.slice(3, end).split('\n');
|
|
27
|
+
const out = {};
|
|
28
|
+
for (const line of lines) {
|
|
29
|
+
const m = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*):\s*(.*?)\s*$/);
|
|
30
|
+
if (m) out[m[1]] = m[2].replace(/^"|"$/g, '').replace(/^'|'$/g, '');
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function bodyAfterFrontmatter(content) {
|
|
36
|
+
if (!content.startsWith('---')) return content;
|
|
37
|
+
const end = content.indexOf('\n---', 3);
|
|
38
|
+
if (end === -1) return content;
|
|
39
|
+
return content.slice(end + 4).replace(/^\s+/, '');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function escapeYamlDouble(str) {
|
|
43
|
+
return String(str).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function clipOneLine(str, max = 240) {
|
|
47
|
+
const flat = String(str).replace(/\s+/g, ' ').trim();
|
|
48
|
+
if (flat.length <= max) return flat;
|
|
49
|
+
return flat.slice(0, max - 1).trimEnd() + '…';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function ensureDir(dir) {
|
|
53
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Walks the kit and returns a flat array of "assets" the adapters can render.
|
|
57
|
+
// Each asset has: { kind: 'agent'|'workflow'|'skill', code, name, title, description, body, overlay }
|
|
58
|
+
function collectAssets(kitRoot, { profiles = ['core'] } = {}) {
|
|
59
|
+
const out = [];
|
|
60
|
+
const profSet = new Set(profiles);
|
|
61
|
+
|
|
62
|
+
for (const yamlPath of walkAgents(kitRoot)) {
|
|
63
|
+
const dir = path.dirname(yamlPath);
|
|
64
|
+
const yaml = fs.readFileSync(yamlPath, 'utf-8');
|
|
65
|
+
const code = readYamlField(yaml, 'code');
|
|
66
|
+
const name = readYamlField(yaml, 'name');
|
|
67
|
+
const title = readYamlField(yaml, 'title') || '';
|
|
68
|
+
const description = readYamlField(yaml, 'description') || '';
|
|
69
|
+
if (!code || !name) continue;
|
|
70
|
+
const personaPath = path.join(dir, 'persona.md');
|
|
71
|
+
const persona = fs.existsSync(personaPath) ? fs.readFileSync(personaPath, 'utf-8') : '';
|
|
72
|
+
out.push({ kind: 'agent', code, name, title, description, body: persona || description, overlay: null });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const wfPath of walkWorkflows(kitRoot)) {
|
|
76
|
+
const content = fs.readFileSync(wfPath, 'utf-8');
|
|
77
|
+
const fm = readFrontmatter(content);
|
|
78
|
+
if (!fm.code) continue;
|
|
79
|
+
if (fm.overlay === 'web' && !profSet.has('web-overlay')) continue;
|
|
80
|
+
if (fm.overlay === 'app' && !profSet.has('app-overlay')) continue;
|
|
81
|
+
out.push({
|
|
82
|
+
kind: 'workflow',
|
|
83
|
+
code: fm.code,
|
|
84
|
+
name: fm.name || fm.code,
|
|
85
|
+
title: fm.phase || fm.gate || '',
|
|
86
|
+
description: `${fm.phase || fm.gate || 'workflow'}: ${fm.name || fm.code}`,
|
|
87
|
+
body: bodyAfterFrontmatter(content),
|
|
88
|
+
overlay: fm.overlay || null
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (const skPath of walkSkills(kitRoot)) {
|
|
93
|
+
const content = fs.readFileSync(skPath, 'utf-8');
|
|
94
|
+
const fm = readFrontmatter(content);
|
|
95
|
+
if (!fm.code) continue;
|
|
96
|
+
out.push({
|
|
97
|
+
kind: 'skill',
|
|
98
|
+
code: fm.code,
|
|
99
|
+
name: fm.name || fm.code,
|
|
100
|
+
title: fm.module || '',
|
|
101
|
+
description: fm.module ? `${fm.module} skill: ${fm.name || fm.code}` : (fm.name || fm.code),
|
|
102
|
+
body: bodyAfterFrontmatter(content),
|
|
103
|
+
overlay: null
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Anthropic-style SKILL.md renderer used by Claude Code, Antigravity, Codex, Kimi.
|
|
111
|
+
// Each asset becomes <targetDir>/<code>/SKILL.md with YAML frontmatter (name, description).
|
|
112
|
+
function renderAnthropicSkills(kitRoot, targetDir, opts = {}) {
|
|
113
|
+
const assets = collectAssets(kitRoot, opts);
|
|
114
|
+
const written = [];
|
|
115
|
+
if (!opts.dryRun) ensureDir(targetDir);
|
|
116
|
+
|
|
117
|
+
for (const a of assets) {
|
|
118
|
+
const desc = clipOneLine(
|
|
119
|
+
a.kind === 'agent'
|
|
120
|
+
? `${a.name} (${a.title}) — ${a.description}`
|
|
121
|
+
: a.description
|
|
122
|
+
);
|
|
123
|
+
const heading = a.kind === 'agent' ? `# ${a.name} — ${a.title}` : `# ${a.name}`;
|
|
124
|
+
const content = [
|
|
125
|
+
'---',
|
|
126
|
+
`name: ${a.code}`,
|
|
127
|
+
`description: "${escapeYamlDouble(desc)}"`,
|
|
128
|
+
'---',
|
|
129
|
+
'',
|
|
130
|
+
heading,
|
|
131
|
+
'',
|
|
132
|
+
a.body.trim(),
|
|
133
|
+
''
|
|
134
|
+
].join('\n');
|
|
135
|
+
|
|
136
|
+
if (opts.dryRun) {
|
|
137
|
+
written.push(`[dry-run] ${a.code}/SKILL.md`);
|
|
138
|
+
} else {
|
|
139
|
+
const skillDir = path.join(targetDir, a.code);
|
|
140
|
+
ensureDir(skillDir);
|
|
141
|
+
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), content, 'utf-8');
|
|
142
|
+
written.push(`${a.code}/SKILL.md`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { written, skipped: [] };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Convenience emitter for a root-level AGENTS.md — read by Codex, Cursor,
|
|
149
|
+
// Windsurf and Antigravity as a baseline pointer to the kit's deeper docs.
|
|
150
|
+
function renderAgentsMd(kitRoot, projectRoot, opts = {}) {
|
|
151
|
+
const assets = collectAssets(kitRoot, opts);
|
|
152
|
+
const agents = assets.filter(a => a.kind === 'agent');
|
|
153
|
+
const file = path.join(projectRoot, 'AGENTS.md');
|
|
154
|
+
if (fs.existsSync(file) && !opts.overwrite) {
|
|
155
|
+
return { written: [], skipped: ['AGENTS.md (already present; not overwritten)'] };
|
|
156
|
+
}
|
|
157
|
+
const lines = [
|
|
158
|
+
'# Agents — Wize Development Kit',
|
|
159
|
+
'',
|
|
160
|
+
'This repo is wired with the [`wize-dev-kit`](https://www.npmjs.com/package/wize-dev-kit).',
|
|
161
|
+
'Detailed artifacts live under `.wize/`. The agents below are activated through your AI IDE',
|
|
162
|
+
'using slash commands (Claude Code, Codex, Cursor, Windsurf, Antigravity all read this file).',
|
|
163
|
+
'',
|
|
164
|
+
'## Roster',
|
|
165
|
+
''
|
|
166
|
+
];
|
|
167
|
+
for (const a of agents) {
|
|
168
|
+
lines.push(`- **${a.name}** (\`${a.code}\`) — ${a.title}. ${clipOneLine(a.description, 180)}`);
|
|
169
|
+
}
|
|
170
|
+
lines.push('', '## Where to start', '', 'Activate the orchestrator: `wize-orchestrator` (Wizer). Then ask `/wize-help`.', '');
|
|
171
|
+
|
|
172
|
+
if (opts.dryRun) return { written: ['[dry-run] AGENTS.md'], skipped: [] };
|
|
173
|
+
fs.writeFileSync(file, lines.join('\n'), 'utf-8');
|
|
174
|
+
return { written: ['AGENTS.md'], skipped: [] };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = {
|
|
178
|
+
collectAssets,
|
|
179
|
+
renderAnthropicSkills,
|
|
180
|
+
renderAgentsMd,
|
|
181
|
+
// Re-exports for adapter authors who need to roll their own format:
|
|
182
|
+
readFrontmatter,
|
|
183
|
+
bodyAfterFrontmatter,
|
|
184
|
+
escapeYamlDouble,
|
|
185
|
+
clipOneLine,
|
|
186
|
+
ensureDir
|
|
187
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Setup helpers used by `wize-dev-kit install`:
|
|
2
|
+
// - generateUserToml({ name, role }) → .wize/config/user.toml template
|
|
3
|
+
// - applyGitignore(projectRoot) → idempotent gitignore block injection
|
|
4
|
+
//
|
|
5
|
+
// Both are pure functions where possible; only applyGitignore writes to disk.
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const fs = require('node:fs');
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
|
|
12
|
+
const GITIGNORE_BEGIN = '# >>> wize-dev-kit (managed) >>>';
|
|
13
|
+
const GITIGNORE_END = '# <<< wize-dev-kit (managed) <<<';
|
|
14
|
+
|
|
15
|
+
const GITIGNORE_BODY = [
|
|
16
|
+
'# Personal / per-developer (do NOT commit)',
|
|
17
|
+
'.wize/config/user.toml',
|
|
18
|
+
'.wize/scratch/',
|
|
19
|
+
'.wize/.local/',
|
|
20
|
+
'.wize/implementation/quick-dev-log.md',
|
|
21
|
+
'',
|
|
22
|
+
'# Generated IDE adapter outputs (regenerate with `npx wize-dev-kit install`)',
|
|
23
|
+
'.claude/skills/wize-*',
|
|
24
|
+
'.agent/skills/wize-*',
|
|
25
|
+
'.agents/skills/wize-*',
|
|
26
|
+
'.kimi/skills/wize-*',
|
|
27
|
+
'.cursor/rules/wize-*.mdc',
|
|
28
|
+
'.windsurf/rules/wize-*.md',
|
|
29
|
+
'.continue/prompts/wize-*.prompt',
|
|
30
|
+
'.opencode/agents/wize-*.md',
|
|
31
|
+
'.opencode/commands/wize-*.md',
|
|
32
|
+
'.wize/agents/wize-*.md'
|
|
33
|
+
].join('\n');
|
|
34
|
+
|
|
35
|
+
function buildGitignoreBlock() {
|
|
36
|
+
return [
|
|
37
|
+
GITIGNORE_BEGIN,
|
|
38
|
+
'# Managed by `npx wize-dev-kit install`. Re-running install will update',
|
|
39
|
+
'# the lines between the markers below; lines outside are untouched.',
|
|
40
|
+
'',
|
|
41
|
+
GITIGNORE_BODY,
|
|
42
|
+
GITIGNORE_END
|
|
43
|
+
].join('\n');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Idempotent: if the block exists, replace it; otherwise append.
|
|
47
|
+
// Returns { changed: boolean, mode: 'created'|'replaced'|'appended'|'unchanged' }.
|
|
48
|
+
function applyGitignore(projectRoot, { dryRun = false } = {}) {
|
|
49
|
+
const file = path.join(projectRoot, '.gitignore');
|
|
50
|
+
const block = buildGitignoreBlock();
|
|
51
|
+
let current = '';
|
|
52
|
+
let exists = fs.existsSync(file);
|
|
53
|
+
if (exists) current = fs.readFileSync(file, 'utf-8');
|
|
54
|
+
|
|
55
|
+
const startIdx = current.indexOf(GITIGNORE_BEGIN);
|
|
56
|
+
const endIdx = current.indexOf(GITIGNORE_END);
|
|
57
|
+
let next;
|
|
58
|
+
let mode;
|
|
59
|
+
|
|
60
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
61
|
+
const before = current.slice(0, startIdx).replace(/\n+$/, '');
|
|
62
|
+
const after = current.slice(endIdx + GITIGNORE_END.length).replace(/^\n+/, '');
|
|
63
|
+
next = [before, block, after].filter(Boolean).join('\n\n');
|
|
64
|
+
if (!next.endsWith('\n')) next += '\n';
|
|
65
|
+
mode = current.includes(block) ? 'unchanged' : 'replaced';
|
|
66
|
+
} else if (exists) {
|
|
67
|
+
next = current.replace(/\n+$/, '') + '\n\n' + block + '\n';
|
|
68
|
+
mode = 'appended';
|
|
69
|
+
} else {
|
|
70
|
+
next = block + '\n';
|
|
71
|
+
mode = 'created';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const changed = next !== current;
|
|
75
|
+
if (changed && !dryRun) fs.writeFileSync(file, next, 'utf-8');
|
|
76
|
+
return { changed, mode };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function generateUserToml({ name = '', role = '' } = {}) {
|
|
80
|
+
const escapedName = String(name).replace(/"/g, '\\"');
|
|
81
|
+
const roleLine = role
|
|
82
|
+
? `role = "${String(role).replace(/"/g, '\\"')}"`
|
|
83
|
+
: `# role = "developer" # optional — "developer", "PM", "designer", etc.`;
|
|
84
|
+
return `# Wize Development Kit — user-level customizations.
|
|
85
|
+
# This file is per-developer; the kit places it in .gitignore so each member
|
|
86
|
+
# of the team gets their own copy. Do NOT commit.
|
|
87
|
+
|
|
88
|
+
[user]
|
|
89
|
+
name = "${escapedName}"
|
|
90
|
+
${roleLine}
|
|
91
|
+
|
|
92
|
+
[preferences]
|
|
93
|
+
# Override project-level language if you prefer another locally:
|
|
94
|
+
# communication = "pt-BR"
|
|
95
|
+
# document_output = "en"
|
|
96
|
+
`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = {
|
|
100
|
+
GITIGNORE_BEGIN,
|
|
101
|
+
GITIGNORE_END,
|
|
102
|
+
buildGitignoreBlock,
|
|
103
|
+
applyGitignore,
|
|
104
|
+
generateUserToml
|
|
105
|
+
};
|
|
@@ -10,9 +10,11 @@
|
|
|
10
10
|
'use strict';
|
|
11
11
|
|
|
12
12
|
const fs = require('node:fs');
|
|
13
|
+
const os = require('node:os');
|
|
13
14
|
const path = require('node:path');
|
|
14
15
|
const readline = require('node:readline');
|
|
15
16
|
const prompts = require('prompts');
|
|
17
|
+
const { applyGitignore, generateUserToml } = require('./setup-helpers.js');
|
|
16
18
|
|
|
17
19
|
const INTERACTIVE = process.stdout.isTTY && process.stdin.isTTY;
|
|
18
20
|
|
|
@@ -228,7 +230,7 @@ function projectToml({ profiles, targets, communication_language, document_outpu
|
|
|
228
230
|
const profileLine = profiles.map(p => `"${p.code}"`).join(', ');
|
|
229
231
|
const targetLine = targets.map(t => `"${t.code}"`).join(', ');
|
|
230
232
|
return `# Wize Development Kit — project config
|
|
231
|
-
# Generated at install.
|
|
233
|
+
# Generated at install. Commit this file. Personal preferences live in user.toml.
|
|
232
234
|
|
|
233
235
|
[project]
|
|
234
236
|
name = "${project_name}"
|
|
@@ -239,7 +241,7 @@ profiles = [${profileLine}]
|
|
|
239
241
|
ide_targets = [${targetLine}]
|
|
240
242
|
|
|
241
243
|
[language]
|
|
242
|
-
# Language used when agents talk to
|
|
244
|
+
# Language used when agents talk to the team in chat.
|
|
243
245
|
communication = "${communication_language}"
|
|
244
246
|
# Language used when agents write artifacts to disk
|
|
245
247
|
# (brief.md, prd.md, architecture.md, gate.md, etc.).
|
|
@@ -271,12 +273,6 @@ gate = { granularity = "per-story" }
|
|
|
271
273
|
`;
|
|
272
274
|
}
|
|
273
275
|
|
|
274
|
-
function userToml() {
|
|
275
|
-
return `# Wize Development Kit — user-level customizations
|
|
276
|
-
# This file is preserved on update; project.toml may be rewritten.
|
|
277
|
-
`;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
276
|
function renderAdapters({ kitRoot, projectRoot, targets, profiles }) {
|
|
281
277
|
const results = [];
|
|
282
278
|
const profileCodes = profiles.map(p => p.code);
|
|
@@ -336,18 +332,39 @@ async function cmdInstall(args) {
|
|
|
336
332
|
communication_language
|
|
337
333
|
);
|
|
338
334
|
|
|
335
|
+
// Personal touch — the user_name lands in .wize/config/user.toml (per-developer).
|
|
336
|
+
const defaultName = (os.userInfo().username || '').trim();
|
|
337
|
+
const user_name = (await prompt(
|
|
338
|
+
`How should the agents call you? [${defaultName || 'leave blank'}]: `
|
|
339
|
+
)).trim() || defaultName;
|
|
340
|
+
|
|
341
|
+
// Gitignore — opt-in, idempotent.
|
|
342
|
+
const wantsGitignore = await confirm(
|
|
343
|
+
'Add the suggested entries to .gitignore (user.toml + scratch + generated adapter outputs)?',
|
|
344
|
+
true
|
|
345
|
+
);
|
|
346
|
+
|
|
339
347
|
console.log('\nCreating .wize/ skeleton...');
|
|
340
348
|
for (const dir of WIZE_DIRS) mkdirp(path.join(cwd, dir));
|
|
341
349
|
|
|
342
350
|
writeIfMissing(path.join(cwd, '.wize/config/project.toml'), projectToml({
|
|
343
351
|
profiles, targets, communication_language, document_output_language, project_name
|
|
344
352
|
}));
|
|
345
|
-
writeIfMissing(path.join(cwd, '.wize/config/user.toml'),
|
|
353
|
+
writeIfMissing(path.join(cwd, '.wize/config/user.toml'), generateUserToml({ name: user_name }));
|
|
346
354
|
writeIfMissing(path.join(cwd, '.wize/config/tea.toml'), teaToml());
|
|
347
355
|
|
|
348
356
|
console.log('✓ .wize/ created');
|
|
349
357
|
console.log(`✓ profiles: ${profiles.map(p => p.code).join(', ')}`);
|
|
350
358
|
console.log(`✓ ide targets: ${targets.map(t => t.code).join(', ')}`);
|
|
359
|
+
if (user_name) console.log(`✓ user.toml: agents will call you "${user_name}"`);
|
|
360
|
+
|
|
361
|
+
if (wantsGitignore) {
|
|
362
|
+
const r = applyGitignore(cwd);
|
|
363
|
+
if (r.changed) console.log(`✓ .gitignore ${r.mode} with the wize-dev-kit block`);
|
|
364
|
+
else console.log(`= .gitignore already up to date`);
|
|
365
|
+
} else {
|
|
366
|
+
console.log('= .gitignore untouched (you opted out of suggested entries)');
|
|
367
|
+
}
|
|
351
368
|
|
|
352
369
|
console.log('\nGenerating IDE adapters...');
|
|
353
370
|
const renderResults = renderAdapters({
|