wize-dev-kit 0.26.1 → 0.27.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.
Files changed (69) hide show
  1. package/ARCH.md +2 -1
  2. package/CHANGELOG.md +64 -0
  3. package/README.es.md +28 -6
  4. package/README.md +28 -6
  5. package/README.pt-BR.md +28 -6
  6. package/adapters/continue/render.js +2 -3
  7. package/adapters/copilot/render.js +3 -3
  8. package/adapters/cursor/render.js +2 -3
  9. package/adapters/generic/render.js +2 -3
  10. package/adapters/opencode/render.js +3 -4
  11. package/adapters/windsurf/render.js +2 -3
  12. package/docs/WORKTREES.md +150 -0
  13. package/docs/WORKTREES.pt-BR.md +150 -0
  14. package/docs/harnesses/antigravity.md +2 -0
  15. package/docs/harnesses/antigravity.pt-BR.md +2 -0
  16. package/docs/harnesses/claude-code.md +2 -0
  17. package/docs/harnesses/claude-code.pt-BR.md +2 -0
  18. package/docs/harnesses/codex.md +3 -1
  19. package/docs/harnesses/codex.pt-BR.md +3 -1
  20. package/docs/harnesses/continue.md +2 -0
  21. package/docs/harnesses/continue.pt-BR.md +2 -0
  22. package/docs/harnesses/copilot.md +2 -0
  23. package/docs/harnesses/copilot.pt-BR.md +2 -0
  24. package/docs/harnesses/cursor.md +2 -0
  25. package/docs/harnesses/cursor.pt-BR.md +2 -0
  26. package/docs/harnesses/generic.md +2 -0
  27. package/docs/harnesses/generic.pt-BR.md +2 -0
  28. package/docs/harnesses/hermes.md +2 -0
  29. package/docs/harnesses/hermes.pt-BR.md +2 -0
  30. package/docs/harnesses/kimi-code.md +2 -0
  31. package/docs/harnesses/kimi-code.pt-BR.md +2 -0
  32. package/docs/harnesses/kiro.md +2 -0
  33. package/docs/harnesses/kiro.pt-BR.md +2 -0
  34. package/docs/harnesses/opencode.md +2 -0
  35. package/docs/harnesses/opencode.pt-BR.md +2 -0
  36. package/docs/harnesses/windsurf.md +2 -0
  37. package/docs/harnesses/windsurf.pt-BR.md +2 -0
  38. package/package.json +1 -1
  39. package/src/orchestrator-skills/module.yaml +1 -1
  40. package/src/orchestrator-skills/wize/skill.md +17 -7
  41. package/src/orchestrator-skills/wize-help/skill.md +20 -7
  42. package/src/orchestrator-skills/wize-update/skill.md +33 -27
  43. package/tools/installer/commands/doctor.js +186 -40
  44. package/tools/installer/commands/document-project.js +13 -3
  45. package/tools/installer/commands/hooks.js +215 -0
  46. package/tools/installer/commands/hydrate.js +298 -0
  47. package/tools/installer/commands/sync.js +50 -23
  48. package/tools/installer/commands/update.js +596 -120
  49. package/tools/installer/commands/version-check.js +69 -10
  50. package/tools/installer/document-project/modes/full-rescan.js +1 -1
  51. package/tools/installer/document-project/modes/initial-scan.js +5 -4
  52. package/tools/installer/document-project/modes/quick.js +11 -4
  53. package/tools/installer/git-context.js +219 -0
  54. package/tools/installer/git-ops.js +211 -0
  55. package/tools/installer/install-plan.js +136 -0
  56. package/tools/installer/marker.js +67 -0
  57. package/tools/installer/project-root.js +68 -0
  58. package/tools/installer/project-status.js +171 -0
  59. package/tools/installer/render-shared.js +170 -4
  60. package/tools/installer/render-targets.js +112 -0
  61. package/tools/installer/semver.js +59 -0
  62. package/tools/installer/setup-helpers.js +89 -31
  63. package/tools/installer/templates/hydrate.sh +163 -0
  64. package/tools/installer/templates/wize-kit-update.yml +38 -0
  65. package/tools/installer/toml.js +133 -0
  66. package/tools/installer/util.js +16 -0
  67. package/tools/installer/version-check.js +167 -37
  68. package/tools/installer/wize-cli.js +216 -78
  69. package/tools/installer/sync.js +0 -29
@@ -0,0 +1,16 @@
1
+ // Small helpers shared by the installer modules.
2
+
3
+ 'use strict';
4
+
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+
8
+ // realpath with a lexical fallback for paths that do not exist.
9
+ function real(p) {
10
+ try { return fs.realpathSync(p); } catch { return path.resolve(p); }
11
+ }
12
+
13
+ // Same members, ignoring order and duplicates.
14
+ const sameSet = (a, b) => JSON.stringify([...new Set(a)].sort()) === JSON.stringify([...new Set(b)].sort());
15
+
16
+ module.exports = { real, sameSet };
@@ -5,18 +5,24 @@
5
5
  // the registry is misbehaving.
6
6
  //
7
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.
8
+ // - Cache the registry answer in $XDG_CACHE_HOME (or ~/.cache) as
9
+ // { version, fetched_at, attempted_at }. The 1h TTL runs from fetched_at.
10
+ // - Hints never wait on the network: getLatestNoWait() returns whatever is
11
+ // cached (any age) and, when it is stale, starts a detached refresher.
12
+ // - The explicit `version-check` command resolves via fetch with a 1.5s
13
+ // timeout. Failure is silent and falls back to the cache.
10
14
  // - Compare with the kit version baked into the running CLI.
11
- // - Expose a `printUpdateHintIfAny(currentVersion)` helper for the CLI.
12
15
 
13
16
  'use strict';
14
17
 
15
18
  const fs = require('node:fs');
16
19
  const os = require('node:os');
17
20
  const path = require('node:path');
21
+ const childProcess = require('node:child_process');
22
+ const { semverGreater, compareVersions } = require('./semver.js');
18
23
 
19
24
  const CACHE_TTL_MS = 60 * 60 * 1000; // 1h
25
+ const ATTEMPT_THROTTLE_MS = 10 * 60 * 1000; // 10 min between background refresh attempts
20
26
  const NETWORK_TIMEOUT_MS = 1500;
21
27
 
22
28
  function cacheFile() {
@@ -24,17 +30,43 @@ function cacheFile() {
24
30
  return path.join(base, 'wize-dev-kit', 'registry-version.json');
25
31
  }
26
32
 
27
- function readCache() {
33
+ function toMs(v) {
34
+ if (typeof v === 'number' && Number.isFinite(v)) return v;
35
+ if (typeof v === 'string') { const t = Date.parse(v); if (Number.isFinite(t)) return t; }
36
+ return null;
37
+ }
38
+
39
+ // The cache entry regardless of age: { version, fetchedAt, attemptedAt }.
40
+ // fetched_at drives the TTL; the file mtime only stands in for a legacy payload
41
+ // that has none. Null when the file is missing or unreadable.
42
+ function readCacheEntry() {
28
43
  const f = cacheFile();
29
44
  try {
30
45
  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'));
46
+ const payload = JSON.parse(fs.readFileSync(f, 'utf-8'));
47
+ if (!payload || typeof payload !== 'object') return null;
48
+ const fetchedAt = toMs(payload.fetched_at);
49
+ return {
50
+ version: typeof payload.version === 'string' && payload.version ? payload.version : null,
51
+ fetchedAt: fetchedAt === null ? stat.mtimeMs : fetchedAt,
52
+ attemptedAt: toMs(payload.attempted_at) || 0,
53
+ payload
54
+ };
33
55
  } catch (_) {
34
56
  return null;
35
57
  }
36
58
  }
37
59
 
60
+ function isFresh(entry, now = Date.now()) {
61
+ return !!(entry && entry.version && now - entry.fetchedAt <= CACHE_TTL_MS);
62
+ }
63
+
64
+ // Returns the cached payload only while it is fresh.
65
+ function readCache() {
66
+ const entry = readCacheEntry();
67
+ return isFresh(entry) ? entry.payload : null;
68
+ }
69
+
38
70
  function writeCache(payload) {
39
71
  const f = cacheFile();
40
72
  try {
@@ -43,7 +75,19 @@ function writeCache(payload) {
43
75
  } catch (_) { /* cache failure is harmless */ }
44
76
  }
45
77
 
46
- async function fetchLatestFromRegistry() {
78
+ // Records a refresh attempt without touching the cached answer or its age.
79
+ function markAttempted(now = Date.now()) {
80
+ const entry = readCacheEntry();
81
+ writeCache({
82
+ version: entry ? entry.version : null,
83
+ fetched_at: entry ? entry.fetchedAt : 0,
84
+ attempted_at: now
85
+ });
86
+ }
87
+
88
+ // { latest, versions } from the registry packument, or null on any failure.
89
+ // `versions` lists every published version (null when the packument has none).
90
+ async function fetchRegistryInfo() {
47
91
  const ctrl = new AbortController();
48
92
  const timer = setTimeout(() => ctrl.abort(), NETWORK_TIMEOUT_MS);
49
93
  try {
@@ -54,7 +98,9 @@ async function fetchLatestFromRegistry() {
54
98
  if (!res.ok) return null;
55
99
  const body = await res.json();
56
100
  const latest = body && body['dist-tags'] && body['dist-tags'].latest;
57
- return typeof latest === 'string' ? latest : null;
101
+ if (typeof latest !== 'string') return null;
102
+ const versions = body.versions && typeof body.versions === 'object' ? Object.keys(body.versions) : null;
103
+ return { latest, versions };
58
104
  } catch (_) {
59
105
  return null; // any error → silent fallback
60
106
  } finally {
@@ -62,41 +108,107 @@ async function fetchLatestFromRegistry() {
62
108
  }
63
109
  }
64
110
 
111
+ async function fetchLatestFromRegistry() {
112
+ const info = await fetchRegistryInfo();
113
+ return info ? info.latest : null;
114
+ }
115
+
116
+ // Resolves the registry-latest version: fresh cache first, then the network
117
+ // (capped, abortable). On failure the previous answer survives as a stale
118
+ // cache hit. Never throws.
119
+ // -> { latest, source: 'cache'|'network'|'none', stale }
120
+ async function resolveLatest({ skipCache = false } = {}) {
121
+ const entry = readCacheEntry();
122
+ if (!skipCache && isFresh(entry)) return { latest: entry.version, source: 'cache', stale: false };
123
+ const info = await fetchRegistryInfo();
124
+ if (info) {
125
+ const now = Date.now();
126
+ writeCache({ version: info.latest, fetched_at: now, attempted_at: now });
127
+ return { latest: info.latest, source: 'network', stale: false, versions: info.versions };
128
+ }
129
+ markAttempted();
130
+ if (entry && entry.version) return { latest: entry.version, source: 'cache', stale: true };
131
+ return { latest: null, source: 'none', stale: false };
132
+ }
133
+
65
134
  // Returns the registry-latest version, hitting the cache first. Never throws,
66
- // never blocks meaningfully (network call is capped + abortable).
135
+ // never blocks meaningfully (network call is capped + abortable). A stale
136
+ // cache is not an answer here: when the fetch fails this resolves to null.
67
137
  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;
138
+ const r = await resolveLatest({ skipCache });
139
+ return r.stale ? null : r.latest;
75
140
  }
76
141
 
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;
142
+ function isCI(env = process.env) {
143
+ const v = env.CI;
144
+ return !!v && v !== '0' && v.toLowerCase() !== 'false';
85
145
  }
86
146
 
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;
147
+ function refresherArgs() {
148
+ return [path.join(__dirname, 'wize-cli.js'), 'version-check', '--refresh', '--quiet'];
149
+ }
150
+
151
+ // Stale-while-revalidate: returns the cached version whatever its age (null
152
+ // when there is none) and never waits on the network. When the cache is
153
+ // older than the TTL and no attempt was made in the last 10 minutes, stamps
154
+ // attempted_at and starts a detached `version-check --refresh --quiet`.
155
+ // No refresher runs when updates are disabled, in CI, or with
156
+ // WIZE_NO_BG_REFRESH=1. `spawn` is injectable for tests.
157
+ function getLatestNoWait({ spawn = childProcess.spawn, env = process.env, now = Date.now() } = {}) {
158
+ const entry = readCacheEntry();
159
+ const version = entry ? entry.version : null;
160
+ try {
161
+ if (env.WIZE_DISABLE_UPDATE_CHECK === '1' || env.WIZE_NO_BG_REFRESH === '1' || isCI(env)) return version;
162
+ if (isFresh(entry, now)) return version;
163
+ if (entry && now - entry.attemptedAt < ATTEMPT_THROTTLE_MS) return version;
164
+ markAttempted(now);
165
+ const child = spawn(process.execPath, refresherArgs(), {
166
+ detached: true,
167
+ stdio: 'ignore',
168
+ env: { ...env, WIZE_NO_BG_REFRESH: '1' }
169
+ });
170
+ if (child && typeof child.on === 'function') child.on('error', () => {});
171
+ if (child && typeof child.unref === 'function') child.unref();
172
+ } catch (_) { /* a failed spawn never disturbs the command */ }
173
+ return version;
174
+ }
175
+
176
+ // One-line hints for a human's terminal. Never waits on the network: the
177
+ // registry answer comes from getLatestNoWait. Silent when stdout is not a
178
+ // TTY, in CI, when updates are disabled, or when `--json` is in argv.
179
+ // When the project's kit_version trails the running kit, that line comes
180
+ // first.
181
+ async function printUpdateHintIfAny(currentVersion, opts = {}) {
182
+ const {
183
+ log = console.log,
184
+ isTTY = process.stdout.isTTY,
185
+ env = process.env,
186
+ argv = process.argv.slice(2),
187
+ projectRoot = null,
188
+ spawn
189
+ } = opts;
190
+ if (env.WIZE_DISABLE_UPDATE_CHECK === '1') return;
92
191
  if (!isTTY) return; // don't spam pipes
192
+ if (isCI(env)) return;
193
+ if (argv.includes('--json')) return;
93
194
  try {
94
- const latest = await getLatestVersion();
95
- if (!latest) return;
96
- if (semverGreater(latest, currentVersion)) {
195
+ const lines = [];
196
+ if (projectRoot) {
197
+ const { loadProjectConfig } = require('./toml.js');
198
+ const cfg = loadProjectConfig(projectRoot);
199
+ const pin = cfg.project && cfg.project.kit_version;
200
+ if (pin && compareVersions(pin, currentVersion) < 0) {
201
+ lines.push(` ↑ wize-dev-kit: project pinned ${pin}, kit ${currentVersion} -> run npx wize-dev-kit@latest update (or /wize-update)`);
202
+ }
203
+ }
204
+ const latest = getLatestNoWait({ env, ...(spawn ? { spawn } : {}) });
205
+ if (latest && semverGreater(latest, currentVersion)) {
206
+ lines.push(` ↑ Update available: wize-dev-kit ${currentVersion} → ${latest}`);
207
+ lines.push(' Run `npx wize-dev-kit@latest update` in your project to refresh adapters.');
208
+ }
209
+ if (lines.length) {
97
210
  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.`);
211
+ for (const l of lines) log(l);
100
212
  log('');
101
213
  }
102
214
  } catch (_) {
@@ -123,15 +235,33 @@ async function getVersionCheckResult(currentVersion, { skipCache = false } = {})
123
235
  };
124
236
  }
125
237
 
238
+ // Same answer plus where it came from. Unlike getVersionCheckResult it falls
239
+ // back to a stale cache when the registry is unreachable.
240
+ async function getVersionCheckDetailed(currentVersion, { skipCache = false } = {}) {
241
+ if (process.env.WIZE_DISABLE_UPDATE_CHECK === '1') {
242
+ return { installed: currentVersion, latest: null, updateAvailable: false, disabled: true, latest_source: 'none' };
243
+ }
244
+ const r = await resolveLatest({ skipCache });
245
+ return {
246
+ installed: currentVersion,
247
+ latest: r.latest,
248
+ updateAvailable: r.latest ? semverGreater(r.latest, currentVersion) : false,
249
+ latest_source: r.source,
250
+ ...(r.versions ? { published_versions: r.versions } : {})
251
+ };
252
+ }
253
+
126
254
  module.exports = {
127
- CACHE_TTL_MS,
128
- NETWORK_TIMEOUT_MS,
129
255
  cacheFile,
130
256
  readCache,
131
257
  writeCache,
132
258
  fetchLatestFromRegistry,
259
+ resolveLatest,
133
260
  getLatestVersion,
261
+ getLatestNoWait,
134
262
  semverGreater,
263
+ isCI,
135
264
  printUpdateHintIfAny,
136
- getVersionCheckResult
265
+ getVersionCheckResult,
266
+ getVersionCheckDetailed
137
267
  };