wize-dev-kit 0.1.5 → 0.2.5
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 +130 -1
- package/README.md +64 -0
- package/package.json +1 -1
- package/src/method-skills/1-analysis/wize-document-project/workflow.md +188 -20
- package/src/method-skills/1-analysis/wize-prfaq/workflow.md +150 -11
- package/src/method-skills/1-analysis/wize-product-brief/workflow.md +90 -19
- package/src/method-skills/1-analysis/wize-refresh-knowledge/workflow.md +127 -0
- package/src/method-skills/1-analysis/wize-research/workflow.md +101 -9
- package/src/method-skills/1-analysis/wize-trigger-map/workflow.md +80 -16
- package/src/method-skills/2-plan-workflows/wize-create-prd/workflow.md +132 -23
- package/src/method-skills/2-plan-workflows/wize-ux-design/workflow.md +132 -28
- package/src/method-skills/2-plan-workflows/wize-ux-scenarios/workflow.md +91 -15
- package/src/method-skills/2-plan-workflows/wize-validate-prd/workflow.md +106 -12
- package/src/method-skills/3-solutioning/wize-check-implementation-readiness/workflow.md +101 -11
- package/src/method-skills/3-solutioning/wize-create-architecture/workflow.md +197 -29
- package/src/method-skills/3-solutioning/wize-create-epics-and-stories/workflow.md +127 -12
- package/src/method-skills/3-solutioning/wize-design-system/workflow.md +182 -22
- package/src/method-skills/3-solutioning/wize-nfr-principles/workflow.md +142 -16
- package/src/method-skills/3-solutioning/wize-tech-vision/workflow.md +127 -21
- package/src/method-skills/4-implementation/wize-code-review/workflow.md +105 -10
- package/src/method-skills/4-implementation/wize-create-story/workflow.md +131 -10
- package/src/method-skills/4-implementation/wize-dev-story/workflow.md +140 -17
- package/src/method-skills/4-implementation/wize-quick-dev/workflow.md +121 -18
- package/src/method-skills/4-implementation/wize-retrospective/workflow.md +112 -10
- package/src/method-skills/4-implementation/wize-sprint-planning/workflow.md +85 -10
- package/src/method-skills/4-implementation/wize-sprint-status/workflow.md +96 -11
- package/src/orchestrator-skills/wize-help/skill.md +25 -1
- package/src/tea-skills/wize-tea-design/workflow.md +104 -13
- package/src/tea-skills/wize-tea-gate/workflow.md +115 -25
- package/src/tea-skills/wize-tea-nfr/workflow.md +104 -14
- package/src/tea-skills/wize-tea-review/workflow.md +120 -13
- package/src/tea-skills/wize-tea-risk/workflow.md +99 -10
- package/src/tea-skills/wize-tea-trace/workflow.md +83 -12
- package/tools/installer/baseline.js +128 -0
- package/tools/installer/commands/agent.js +197 -0
- package/tools/installer/commands/sync.js +45 -0
- package/tools/installer/commands/update.js +172 -0
- package/tools/installer/version-check.js +117 -0
- package/tools/installer/wize-cli.js +98 -11
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// `wize-dev-kit agent <subcommand>` — manage built-in and custom agents.
|
|
2
|
+
//
|
|
3
|
+
// list - show every agent the active install resolves (built-in + custom)
|
|
4
|
+
// create - scaffold a brand-new custom agent in .wize/custom/agents/{code}/
|
|
5
|
+
// edit <code> - override an existing built-in via .wize/custom/agents/{code}/customize.toml
|
|
6
|
+
//
|
|
7
|
+
// All write operations validate the result against schemas/agent.schema.json
|
|
8
|
+
// (lite parser — no dependency on Ajv yet) and do a smoke dry-run before
|
|
9
|
+
// persisting, matching the contract `wize-create-agent` describes.
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const fs = require('node:fs');
|
|
14
|
+
const path = require('node:path');
|
|
15
|
+
const prompts = require('prompts');
|
|
16
|
+
const { collectAssets } = require('../render-shared.js');
|
|
17
|
+
|
|
18
|
+
const VALID_CODE = /^wize-(agent|orchestrator)(?:-[a-z0-9-]+)?$/;
|
|
19
|
+
const VALID_MODULES = ['orchestrator', 'method', 'tea', 'builder', 'core', 'web-overlay', 'app-overlay', 'custom'];
|
|
20
|
+
|
|
21
|
+
function ensureDir(p) { fs.mkdirSync(p, { recursive: true }); }
|
|
22
|
+
|
|
23
|
+
function listAgents(kitRoot, projectRoot) {
|
|
24
|
+
const builtIn = collectAssets(kitRoot, { profiles: ['core', 'web-overlay', 'app-overlay'] })
|
|
25
|
+
.filter(a => a.kind === 'agent')
|
|
26
|
+
.map(a => ({ code: a.code, name: a.name, title: a.title, source: 'built-in' }));
|
|
27
|
+
const customDir = path.join(projectRoot, '.wize/custom/agents');
|
|
28
|
+
const custom = [];
|
|
29
|
+
if (fs.existsSync(customDir)) {
|
|
30
|
+
for (const entry of fs.readdirSync(customDir, { withFileTypes: true })) {
|
|
31
|
+
if (!entry.isDirectory()) continue;
|
|
32
|
+
const yamlPath = path.join(customDir, entry.name, 'agent.yaml');
|
|
33
|
+
const customizePath = path.join(customDir, entry.name, 'customize.toml');
|
|
34
|
+
if (fs.existsSync(yamlPath)) {
|
|
35
|
+
const yaml = fs.readFileSync(yamlPath, 'utf-8');
|
|
36
|
+
const name = (yaml.match(/^name:\s*(.+)$/m) || [])[1] || entry.name;
|
|
37
|
+
const title = (yaml.match(/^title:\s*(.+)$/m) || [])[1] || '';
|
|
38
|
+
custom.push({ code: entry.name, name: name.trim(), title: title.trim(), source: 'custom' });
|
|
39
|
+
} else if (fs.existsSync(customizePath)) {
|
|
40
|
+
custom.push({ code: entry.name, name: '(override)', title: '', source: 'override' });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { builtIn, custom };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function cmdAgentList({ kitRoot, projectRoot, log = console.log } = {}) {
|
|
48
|
+
const { builtIn, custom } = listAgents(kitRoot, projectRoot);
|
|
49
|
+
log('Built-in agents:');
|
|
50
|
+
for (const a of builtIn) log(` ${a.code.padEnd(36)} ${a.name}${a.title ? ' — ' + a.title : ''}`);
|
|
51
|
+
if (custom.length) {
|
|
52
|
+
log('\nCustom / override agents (from .wize/custom/agents/):');
|
|
53
|
+
for (const a of custom) log(` ${a.code.padEnd(36)} ${a.name}${a.title ? ' — ' + a.title : ''} [${a.source}]`);
|
|
54
|
+
} else {
|
|
55
|
+
log('\nCustom / override agents: (none yet — use `wize-dev-kit agent create`)');
|
|
56
|
+
}
|
|
57
|
+
return { builtIn, custom };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function validateAgent(spec, kitRoot) {
|
|
61
|
+
const errs = [];
|
|
62
|
+
if (!spec.code) errs.push('code is required');
|
|
63
|
+
else if (!VALID_CODE.test(spec.code)) errs.push(`code "${spec.code}" must match wize-(agent|orchestrator)[-name]`);
|
|
64
|
+
if (!spec.name) errs.push('name is required');
|
|
65
|
+
if (!spec.title) errs.push('title is required');
|
|
66
|
+
if (!VALID_MODULES.includes(spec.module)) errs.push(`module must be one of: ${VALID_MODULES.join(', ')}`);
|
|
67
|
+
if (!spec.description || spec.description.length < 10) errs.push('description must be ≥ 10 characters');
|
|
68
|
+
// Collide check against built-ins when not overriding.
|
|
69
|
+
const builtinCodes = collectAssets(kitRoot, { profiles: ['core', 'web-overlay', 'app-overlay'] })
|
|
70
|
+
.filter(a => a.kind === 'agent').map(a => a.code);
|
|
71
|
+
if (builtinCodes.includes(spec.code) && !spec.allowOverride) {
|
|
72
|
+
errs.push(`code "${spec.code}" already exists as a built-in. Use \`wize-dev-kit agent edit ${spec.code}\` to override.`);
|
|
73
|
+
}
|
|
74
|
+
return errs;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function renderAgentYaml(spec) {
|
|
78
|
+
return [
|
|
79
|
+
`code: ${spec.code}`,
|
|
80
|
+
`name: ${spec.name}`,
|
|
81
|
+
`title: ${spec.title}`,
|
|
82
|
+
`icon: "${spec.icon || '🔧'}"`,
|
|
83
|
+
`team: ${spec.team || 'software-development'}`,
|
|
84
|
+
`module: ${spec.module}`,
|
|
85
|
+
'',
|
|
86
|
+
'description: |',
|
|
87
|
+
...spec.description.split('\n').map(l => ' ' + l),
|
|
88
|
+
''
|
|
89
|
+
].join('\n');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function renderPersonaMd(spec) {
|
|
93
|
+
const body = (spec.persona || `I am **${spec.name}**. ${spec.description}`).trim();
|
|
94
|
+
return [
|
|
95
|
+
`# ${spec.name} — ${spec.title}`,
|
|
96
|
+
'',
|
|
97
|
+
body,
|
|
98
|
+
''
|
|
99
|
+
].join('\n');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Smoke dry-run: write to temp dir, re-read, assert frontmatter parses.
|
|
103
|
+
function dryRunPersist(spec) {
|
|
104
|
+
const tmp = path.join(require('node:os').tmpdir(), `wize-agent-${spec.code}-${Date.now()}`);
|
|
105
|
+
ensureDir(tmp);
|
|
106
|
+
try {
|
|
107
|
+
fs.writeFileSync(path.join(tmp, 'agent.yaml'), renderAgentYaml(spec), 'utf-8');
|
|
108
|
+
fs.writeFileSync(path.join(tmp, 'persona.md'), renderPersonaMd(spec), 'utf-8');
|
|
109
|
+
const reread = fs.readFileSync(path.join(tmp, 'agent.yaml'), 'utf-8');
|
|
110
|
+
if (!/^code:\s+/m.test(reread)) throw new Error('emitted agent.yaml has no "code:" line');
|
|
111
|
+
if (!/^name:\s+/m.test(reread)) throw new Error('emitted agent.yaml has no "name:" line');
|
|
112
|
+
return { ok: true };
|
|
113
|
+
} finally {
|
|
114
|
+
try { fs.rmSync(tmp, { recursive: true, force: true }); } catch (_) {}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function cmdAgentCreate({ kitRoot, projectRoot, opts = {} } = {}) {
|
|
119
|
+
const interactive = process.stdout.isTTY && process.stdin.isTTY && !opts.fromSpec;
|
|
120
|
+
let spec = opts.fromSpec || {};
|
|
121
|
+
|
|
122
|
+
if (interactive) {
|
|
123
|
+
const a = await prompts([
|
|
124
|
+
{ type: 'text', name: 'code', message: 'Agent code (e.g. wize-agent-runbooks)',
|
|
125
|
+
validate: v => VALID_CODE.test(v) || 'must match wize-(agent|orchestrator)[-name]' },
|
|
126
|
+
{ type: 'text', name: 'name', message: 'Display name (e.g. Riri Williams)', validate: v => !!v.trim() || 'required' },
|
|
127
|
+
{ type: 'text', name: 'title', message: 'Role / title (e.g. Reliability Engineer)', validate: v => !!v.trim() || 'required' },
|
|
128
|
+
{ type: 'text', name: 'icon', message: 'Icon emoji (optional)', initial: '🔧' },
|
|
129
|
+
{ type: 'text', name: 'team', message: 'Team', initial: 'software-development' },
|
|
130
|
+
{ type: 'select', name: 'module', message: 'Module', choices: VALID_MODULES.map(v => ({ title: v, value: v })), initial: VALID_MODULES.indexOf('custom') },
|
|
131
|
+
{ type: 'text', name: 'description', message: 'One-paragraph description (≥ 10 chars)', validate: v => v.length >= 10 || 'must be ≥ 10 characters' },
|
|
132
|
+
{ type: 'text', name: 'persona', message: 'Persona body (optional — defaults to description)', initial: '' }
|
|
133
|
+
], { onCancel: () => process.exit(130) });
|
|
134
|
+
spec = { ...spec, ...a };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const errs = validateAgent(spec, kitRoot);
|
|
138
|
+
if (errs.length) {
|
|
139
|
+
console.error('✖ Validation failed:');
|
|
140
|
+
for (const e of errs) console.error(' - ' + e);
|
|
141
|
+
process.exit(2);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const dry = dryRunPersist(spec);
|
|
145
|
+
if (!dry.ok) {
|
|
146
|
+
console.error('✖ Dry-run failed; aborting.');
|
|
147
|
+
process.exit(3);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const target = path.join(projectRoot, '.wize/custom/agents', spec.code);
|
|
151
|
+
ensureDir(target);
|
|
152
|
+
fs.writeFileSync(path.join(target, 'agent.yaml'), renderAgentYaml(spec), 'utf-8');
|
|
153
|
+
fs.writeFileSync(path.join(target, 'persona.md'), renderPersonaMd(spec), 'utf-8');
|
|
154
|
+
|
|
155
|
+
console.log(`✓ Created ${path.relative(projectRoot, target)}/`);
|
|
156
|
+
console.log(' Run `wize-dev-kit sync` to refresh IDE adapter outputs with the new agent.');
|
|
157
|
+
return { code: spec.code, path: target };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function cmdAgentEdit({ kitRoot, projectRoot, code, opts = {} } = {}) {
|
|
161
|
+
if (!code) {
|
|
162
|
+
console.error('Usage: wize-dev-kit agent edit <code>');
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
const builtinCodes = collectAssets(kitRoot, { profiles: ['core', 'web-overlay', 'app-overlay'] })
|
|
166
|
+
.filter(a => a.kind === 'agent').map(a => a.code);
|
|
167
|
+
if (!builtinCodes.includes(code)) {
|
|
168
|
+
console.error(`✖ "${code}" is not a built-in agent. Use \`wize-dev-kit agent create\` for new agents.`);
|
|
169
|
+
process.exit(2);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const target = path.join(projectRoot, '.wize/custom/agents', code);
|
|
173
|
+
ensureDir(target);
|
|
174
|
+
const customize = path.join(target, 'customize.toml');
|
|
175
|
+
if (fs.existsSync(customize) && !opts.force) {
|
|
176
|
+
console.log(`= ${path.relative(projectRoot, customize)} already exists. Edit it directly.`);
|
|
177
|
+
return { code, path: customize };
|
|
178
|
+
}
|
|
179
|
+
fs.writeFileSync(customize, `# Wize Dev Kit — overrides for the built-in agent "${code}".
|
|
180
|
+
# This file is loaded on top of the built-in agent's persona.md / agent.yaml
|
|
181
|
+
# at IDE adapter render time. Only the fields you set here are overridden.
|
|
182
|
+
|
|
183
|
+
[persona]
|
|
184
|
+
# motto = "your custom motto"
|
|
185
|
+
# voice = "succinct and irreverent"
|
|
186
|
+
# style = "show with code, not prose"
|
|
187
|
+
|
|
188
|
+
[behavior]
|
|
189
|
+
# defer_to = [] # e.g. ["wize-agent-pm"] to lower priority on certain decisions
|
|
190
|
+
# tools_deny = [] # tool codes the override removes from the agent
|
|
191
|
+
`, 'utf-8');
|
|
192
|
+
console.log(`✓ Created ${path.relative(projectRoot, customize)}`);
|
|
193
|
+
console.log(' Edit the file, then run `wize-dev-kit sync` to apply.');
|
|
194
|
+
return { code, path: customize };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = { cmdAgentList, cmdAgentCreate, cmdAgentEdit, listAgents, validateAgent };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// `wize-dev-kit sync` — regenerate adapter outputs for the IDE targets the
|
|
2
|
+
// project opted into. Useful after editing .wize/config/project.toml,
|
|
3
|
+
// running `agent create`, or pulling kit changes that don't bump version.
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const fs = require('node:fs');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
const { loadProjectConfig } = require('./update.js');
|
|
10
|
+
|
|
11
|
+
function cmdSync({ kitRoot, projectRoot, opts = {} } = {}) {
|
|
12
|
+
const log = opts.log || console.log;
|
|
13
|
+
const cfg = loadProjectConfig(projectRoot);
|
|
14
|
+
if (!cfg.project) {
|
|
15
|
+
log('No .wize/config/project.toml found here. Run `wize-dev-kit install` first.');
|
|
16
|
+
return { changed: false };
|
|
17
|
+
}
|
|
18
|
+
const ideTargets = (cfg.install && cfg.install.ide_targets) || ['claude-code', 'generic'];
|
|
19
|
+
const profiles = (cfg.install && cfg.install.profiles) || ['core'];
|
|
20
|
+
log(`Syncing ${ideTargets.length} adapter(s) for profile(s) ${profiles.join(' + ')}:`);
|
|
21
|
+
|
|
22
|
+
const results = [];
|
|
23
|
+
for (const code of ideTargets) {
|
|
24
|
+
const renderPath = path.join(kitRoot, 'adapters', code, 'render.js');
|
|
25
|
+
if (!fs.existsSync(renderPath)) {
|
|
26
|
+
log(` - ${code}: skipped (adapter missing in installed kit)`);
|
|
27
|
+
results.push({ code, skipped: true });
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
delete require.cache[require.resolve(renderPath)];
|
|
31
|
+
try {
|
|
32
|
+
const out = require(renderPath).render(kitRoot, projectRoot, { profiles });
|
|
33
|
+
const n = out && Array.isArray(out.written) ? out.written.length : 0;
|
|
34
|
+
log(` ✓ ${code}: ${n} file(s) emitted`);
|
|
35
|
+
results.push({ code, written: n });
|
|
36
|
+
} catch (err) {
|
|
37
|
+
log(` ✖ ${code}: ${err.message}`);
|
|
38
|
+
results.push({ code, error: err.message });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
log('\nDone. Restart your IDE if it caches skills at startup.');
|
|
42
|
+
return { changed: true, adapters: results };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { cmdSync };
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// `wize-dev-kit update` — refresh an installed kit to the version currently
|
|
2
|
+
// resolved by `node_modules/wize-dev-kit`. Preserves .wize/config/user.toml,
|
|
3
|
+
// re-runs the active IDE adapters, and re-applies the .gitignore block when
|
|
4
|
+
// it was opted-in originally.
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fs = require('node:fs');
|
|
9
|
+
const path = require('node:path');
|
|
10
|
+
const { applyGitignore } = require('../setup-helpers.js');
|
|
11
|
+
|
|
12
|
+
// Minimal TOML reader for the subset we write — handles `[section]` headers,
|
|
13
|
+
// `key = "value"` scalars, and `key = ["a", "b"]` string arrays.
|
|
14
|
+
function readToml(file) {
|
|
15
|
+
if (!fs.existsSync(file)) return {};
|
|
16
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
17
|
+
const out = {};
|
|
18
|
+
let section = null;
|
|
19
|
+
for (const raw of content.split('\n')) {
|
|
20
|
+
const line = raw.replace(/#.*$/, '').trim();
|
|
21
|
+
if (!line) continue;
|
|
22
|
+
const head = line.match(/^\[([^\]]+)\]$/);
|
|
23
|
+
if (head) { section = head[1]; out[section] = out[section] || {}; continue; }
|
|
24
|
+
const kv = line.match(/^([a-zA-Z_][\w-]*)\s*=\s*(.+)$/);
|
|
25
|
+
if (!kv) continue;
|
|
26
|
+
const [, key, valRaw] = kv;
|
|
27
|
+
const v = parseTomlValue(valRaw.trim());
|
|
28
|
+
if (section) out[section][key] = v;
|
|
29
|
+
else out[key] = v;
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseTomlValue(raw) {
|
|
35
|
+
if (raw.startsWith('"') && raw.endsWith('"')) return raw.slice(1, -1);
|
|
36
|
+
if (raw.startsWith("'") && raw.endsWith("'")) return raw.slice(1, -1);
|
|
37
|
+
if (raw.startsWith('[') && raw.endsWith(']')) {
|
|
38
|
+
return raw.slice(1, -1).split(',').map(s => s.trim().replace(/^["']|["']$/g, '')).filter(Boolean);
|
|
39
|
+
}
|
|
40
|
+
if (raw === 'true') return true;
|
|
41
|
+
if (raw === 'false') return false;
|
|
42
|
+
if (!isNaN(Number(raw))) return Number(raw);
|
|
43
|
+
return raw;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function loadProjectConfig(projectRoot) {
|
|
47
|
+
return readToml(path.join(projectRoot, '.wize/config/project.toml'));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function loadInstalledKitVersion(kitRoot) {
|
|
51
|
+
try { return require(path.join(kitRoot, 'package.json')).version; }
|
|
52
|
+
catch { return null; }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Returns a list of CHANGELOG entries (markdown blocks) for versions strictly
|
|
56
|
+
// between `from` and `to`. Best-effort — if CHANGELOG layout is unusual,
|
|
57
|
+
// returns an empty list and falls back to a generic note.
|
|
58
|
+
function changelogBetween(kitRoot, fromVersion, toVersion) {
|
|
59
|
+
const file = path.join(kitRoot, 'CHANGELOG.md');
|
|
60
|
+
if (!fs.existsSync(file)) return [];
|
|
61
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
62
|
+
const versionRe = /^## \[(\d+\.\d+\.\d+)\][^\n]*\n/gm;
|
|
63
|
+
const matches = [];
|
|
64
|
+
let m;
|
|
65
|
+
while ((m = versionRe.exec(content)) !== null) {
|
|
66
|
+
matches.push({ v: m[1], start: m.index, headerEnd: versionRe.lastIndex });
|
|
67
|
+
}
|
|
68
|
+
for (let i = 0; i < matches.length; i++) {
|
|
69
|
+
matches[i].end = i + 1 < matches.length ? matches[i + 1].start : content.length;
|
|
70
|
+
}
|
|
71
|
+
return matches
|
|
72
|
+
.filter(x => compare(x.v, fromVersion) > 0 && compare(x.v, toVersion) <= 0)
|
|
73
|
+
.map(x => content.slice(x.start, x.end).trim());
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function compare(a, b) {
|
|
77
|
+
const pa = a.split('.').map(Number);
|
|
78
|
+
const pb = b.split('.').map(Number);
|
|
79
|
+
for (let i = 0; i < 3; i++) {
|
|
80
|
+
if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0);
|
|
81
|
+
}
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Re-runs every adapter that the project opted into at install time, using
|
|
86
|
+
// the current versions of the kit's renderers.
|
|
87
|
+
function rerunAdapters({ kitRoot, projectRoot, ideTargets, profiles, log }) {
|
|
88
|
+
const results = [];
|
|
89
|
+
for (const code of ideTargets) {
|
|
90
|
+
const renderPath = path.join(kitRoot, 'adapters', code, 'render.js');
|
|
91
|
+
if (!fs.existsSync(renderPath)) {
|
|
92
|
+
results.push({ code, skipped: true, reason: 'adapter missing in this kit version' });
|
|
93
|
+
log(` - ${code}: skipped (adapter no longer exists)`);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
delete require.cache[require.resolve(renderPath)]; // make `update` idempotent in the same process
|
|
97
|
+
try {
|
|
98
|
+
const mod = require(renderPath);
|
|
99
|
+
const out = mod.render(kitRoot, projectRoot, { profiles });
|
|
100
|
+
const n = out && Array.isArray(out.written) ? out.written.length : 0;
|
|
101
|
+
results.push({ code, written: n });
|
|
102
|
+
log(` ✓ ${code}: ${n} file(s) refreshed`);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
results.push({ code, error: err.message });
|
|
105
|
+
log(` ✖ ${code}: ${err.message}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return results;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function writeUpdatedProjectToml(projectRoot, currentToml, newKitVersion) {
|
|
112
|
+
// Surgical rewrite — only updates `kit_version = ...` under `[project]`.
|
|
113
|
+
// Leaves everything else as the user/team configured it.
|
|
114
|
+
const file = path.join(projectRoot, '.wize/config/project.toml');
|
|
115
|
+
if (!fs.existsSync(file)) return false;
|
|
116
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
117
|
+
const updated = content.replace(/^kit_version\s*=\s*".*"$/m, `kit_version = "${newKitVersion}"`);
|
|
118
|
+
if (updated === content) return false;
|
|
119
|
+
fs.writeFileSync(file, updated, 'utf-8');
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function cmdUpdate({ kitRoot, projectRoot, opts = {} } = {}) {
|
|
124
|
+
const log = opts.log || console.log;
|
|
125
|
+
const cfg = loadProjectConfig(projectRoot);
|
|
126
|
+
if (!cfg.project) {
|
|
127
|
+
log('No .wize/config/project.toml found here.');
|
|
128
|
+
log('Run `wize-dev-kit install` first.');
|
|
129
|
+
return { changed: false };
|
|
130
|
+
}
|
|
131
|
+
const fromVersion = cfg.project.kit_version || '0.0.0';
|
|
132
|
+
const toVersion = loadInstalledKitVersion(kitRoot) || 'unknown';
|
|
133
|
+
if (toVersion === 'unknown') {
|
|
134
|
+
log('Could not read kit version from node_modules. Aborting.');
|
|
135
|
+
return { changed: false };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
log(`Wize Dev Kit update: ${fromVersion} → ${toVersion}`);
|
|
139
|
+
if (fromVersion === toVersion) {
|
|
140
|
+
log('Already on the same version. Re-rendering adapters anyway (idempotent).');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const ideTargets = (cfg.install && cfg.install.ide_targets) || ['claude-code', 'generic'];
|
|
144
|
+
const profiles = (cfg.install && cfg.install.profiles) || ['core'];
|
|
145
|
+
|
|
146
|
+
log('\nRe-rendering active IDE adapters:');
|
|
147
|
+
const adapterResults = rerunAdapters({ kitRoot, projectRoot, ideTargets, profiles, log });
|
|
148
|
+
|
|
149
|
+
if (opts.refreshGitignore !== false) {
|
|
150
|
+
const r = applyGitignore(projectRoot);
|
|
151
|
+
if (r.changed) log(`\n✓ .gitignore ${r.mode}`);
|
|
152
|
+
else log('\n= .gitignore already up to date');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
writeUpdatedProjectToml(projectRoot, cfg, toVersion);
|
|
156
|
+
log(`\n✓ project.toml kit_version → ${toVersion}`);
|
|
157
|
+
|
|
158
|
+
const entries = changelogBetween(kitRoot, fromVersion, toVersion);
|
|
159
|
+
if (entries.length) {
|
|
160
|
+
log(`\nWhat changed (excerpt from CHANGELOG):`);
|
|
161
|
+
for (const e of entries) {
|
|
162
|
+
log(e.split('\n').slice(0, 4).map(l => ' ' + l).join('\n'));
|
|
163
|
+
log(' …');
|
|
164
|
+
}
|
|
165
|
+
log(`\nFull details: ${path.join(kitRoot, 'CHANGELOG.md')}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
log('\nDone. Restart your IDE so the refreshed slash commands are picked up.');
|
|
169
|
+
return { changed: true, from: fromVersion, to: toVersion, adapters: adapterResults };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = { cmdUpdate, loadProjectConfig, loadInstalledKitVersion, changelogBetween };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Best-effort, non-blocking npm registry check for "is there a newer wize-dev-kit
|
|
2
|
+
// version than the one I'm running?". Designed so the CLI can call it from
|
|
3
|
+
// `list` / `agent list` / `help` and print a single hint at the top, without
|
|
4
|
+
// ever blocking the command if the network is slow, the user is offline, or
|
|
5
|
+
// the registry is misbehaving.
|
|
6
|
+
//
|
|
7
|
+
// Strategy:
|
|
8
|
+
// - Cache the registry answer in $XDG_CACHE_HOME (or ~/.cache) for 1 hour.
|
|
9
|
+
// - Resolve via fetch with a 1.5s timeout. Failure is silent.
|
|
10
|
+
// - Compare with the kit version baked into the running CLI.
|
|
11
|
+
// - Expose a `printUpdateHintIfAny(currentVersion)` helper for the CLI.
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const fs = require('node:fs');
|
|
16
|
+
const os = require('node:os');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
|
|
19
|
+
const CACHE_TTL_MS = 60 * 60 * 1000; // 1h
|
|
20
|
+
const NETWORK_TIMEOUT_MS = 1500;
|
|
21
|
+
|
|
22
|
+
function cacheFile() {
|
|
23
|
+
const base = process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
|
|
24
|
+
return path.join(base, 'wize-dev-kit', 'registry-version.json');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readCache() {
|
|
28
|
+
const f = cacheFile();
|
|
29
|
+
try {
|
|
30
|
+
const stat = fs.statSync(f);
|
|
31
|
+
if (Date.now() - stat.mtimeMs > CACHE_TTL_MS) return null;
|
|
32
|
+
return JSON.parse(fs.readFileSync(f, 'utf-8'));
|
|
33
|
+
} catch (_) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function writeCache(payload) {
|
|
39
|
+
const f = cacheFile();
|
|
40
|
+
try {
|
|
41
|
+
fs.mkdirSync(path.dirname(f), { recursive: true });
|
|
42
|
+
fs.writeFileSync(f, JSON.stringify(payload), 'utf-8');
|
|
43
|
+
} catch (_) { /* cache failure is harmless */ }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function fetchLatestFromRegistry() {
|
|
47
|
+
const ctrl = new AbortController();
|
|
48
|
+
const timer = setTimeout(() => ctrl.abort(), NETWORK_TIMEOUT_MS);
|
|
49
|
+
try {
|
|
50
|
+
const res = await fetch('https://registry.npmjs.org/wize-dev-kit', {
|
|
51
|
+
signal: ctrl.signal,
|
|
52
|
+
headers: { Accept: 'application/vnd.npm.install-v1+json' }
|
|
53
|
+
});
|
|
54
|
+
if (!res.ok) return null;
|
|
55
|
+
const body = await res.json();
|
|
56
|
+
const latest = body && body['dist-tags'] && body['dist-tags'].latest;
|
|
57
|
+
return typeof latest === 'string' ? latest : null;
|
|
58
|
+
} catch (_) {
|
|
59
|
+
return null; // any error → silent fallback
|
|
60
|
+
} finally {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Returns the registry-latest version, hitting the cache first. Never throws,
|
|
66
|
+
// never blocks meaningfully (network call is capped + abortable).
|
|
67
|
+
async function getLatestVersion({ skipCache = false } = {}) {
|
|
68
|
+
if (!skipCache) {
|
|
69
|
+
const c = readCache();
|
|
70
|
+
if (c && c.version) return c.version;
|
|
71
|
+
}
|
|
72
|
+
const fresh = await fetchLatestFromRegistry();
|
|
73
|
+
if (fresh) writeCache({ version: fresh, fetched_at: Date.now() });
|
|
74
|
+
return fresh;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function semverGreater(a, b) {
|
|
78
|
+
const pa = String(a).split('.').map(n => parseInt(n, 10) || 0);
|
|
79
|
+
const pb = String(b).split('.').map(n => parseInt(n, 10) || 0);
|
|
80
|
+
for (let i = 0; i < 3; i++) {
|
|
81
|
+
if ((pa[i] || 0) > (pb[i] || 0)) return true;
|
|
82
|
+
if ((pa[i] || 0) < (pb[i] || 0)) return false;
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Prints a one-line hint when a newer version is available. Silent otherwise.
|
|
88
|
+
// Designed to be awaited near the top of a CLI command; if anything is slow
|
|
89
|
+
// or off, it just returns.
|
|
90
|
+
async function printUpdateHintIfAny(currentVersion, { log = console.log, isTTY = process.stdout.isTTY } = {}) {
|
|
91
|
+
if (process.env.WIZE_DISABLE_UPDATE_CHECK === '1') return;
|
|
92
|
+
if (!isTTY) return; // don't spam pipes
|
|
93
|
+
try {
|
|
94
|
+
const latest = await getLatestVersion();
|
|
95
|
+
if (!latest) return;
|
|
96
|
+
if (semverGreater(latest, currentVersion)) {
|
|
97
|
+
log('');
|
|
98
|
+
log(` ↑ Update available: wize-dev-kit ${currentVersion} → ${latest}`);
|
|
99
|
+
log(` Run \`npx wize-dev-kit@latest update\` in your project to refresh adapters.`);
|
|
100
|
+
log('');
|
|
101
|
+
}
|
|
102
|
+
} catch (_) {
|
|
103
|
+
/* total fallback — never block the command */
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = {
|
|
108
|
+
CACHE_TTL_MS,
|
|
109
|
+
NETWORK_TIMEOUT_MS,
|
|
110
|
+
cacheFile,
|
|
111
|
+
readCache,
|
|
112
|
+
writeCache,
|
|
113
|
+
fetchLatestFromRegistry,
|
|
114
|
+
getLatestVersion,
|
|
115
|
+
semverGreater,
|
|
116
|
+
printUpdateHintIfAny
|
|
117
|
+
};
|