worclaude 2.9.3 → 2.10.1
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 +41 -0
- package/README.md +1 -1
- package/package.json +7 -7
- package/src/commands/doctor.js +31 -0
- package/src/core/merger.js +25 -10
- package/src/core/scaffolder.js +23 -8
- package/templates/settings/base.json +6 -0
- package/templates/specs/spec-md-library.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,47 @@ All notable changes to worclaude are documented in this file. Format loosely fol
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.10.1] — 2026-04-29
|
|
8
|
+
|
|
9
|
+
Adds opt-in scaffolding for Claude Code 2.1.113's `sandbox.network` deny/allow lists. Worclaude is a scaffolder, so the new `templates/settings/base.json` ships empty `deniedDomains` and `allowedDomains` stubs rather than an opinionated default list — project owners decide their own network policy. The merge paths for both fresh init (Scenario A) and existing-project init/upgrade (Scenarios B/C) union-merge the new arrays preserving any user-added domains, and a new `worclaude doctor` check warns when the block is missing or malformed (with `worclaude upgrade` as the remediation hint). Also bundles a Dependabot major bump to `commander` 14, which is now Node-20+-only and was unblocked by the v2.10.0 Node 18 drop.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Sandbox network scaffolding** (PR #172) — `templates/settings/base.json` now scaffolds `sandbox.network.deniedDomains: []` and `allowedDomains: []` between the existing `permissions` and `hooks` blocks. New `mergeSettings` helper `unionStringList(inputs, accessor)` in `src/core/scaffolder.js` handles both `permissions.allow` and the new sandbox arrays uniformly. Backward compatible: a base without `sandbox` produces output without `sandbox`, so legacy callers in tests or downstream consumers don't surface the key spuriously.
|
|
14
|
+
- **`appendUnique(target, key, source)` helper** in `src/core/merger.js` (PR #172) — folds three previously-duplicated union-merge call sites in `mergeSettingsPermissionsAndHooks` (allow / deny / sandbox-arrays) into one-liners. Extracted during a `/simplify` pass after three parallel review agents flagged the duplication.
|
|
15
|
+
- **`checkSandboxBlock` doctor check** (PR #172) — warns when `settings.json` is missing the `sandbox` block (with a `worclaude upgrade` remediation pointer for legacy installs), when `sandbox.network` is malformed, or when either array isn't actually an array.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **`commander` 13.1.0 → 14.0.3** (PR #171) — Dependabot major bump. Commander 14 requires Node 20+ (already satisfied after v2.10.0's Node 18 drop) and adds `helpGroup`/`optionsGroup`/`commandsGroup` APIs plus unescaped negative-number support. Worclaude's CLI surface is unaffected.
|
|
20
|
+
- ⚠ **PR #171 shipped without a `Version bump:` declaration** — Dependabot-generated body, no manual annotation. Treated as `none` per `/sync`'s "missing → none" rule and surfaced here permanently. PR #172's `patch` declaration drove the release.
|
|
21
|
+
|
|
22
|
+
### Tests
|
|
23
|
+
|
|
24
|
+
- 967 → 992 (+25 net). Per-stack sandbox-array assertions across all 16 supported language templates (replaced one all-stacks loop test for individual failure attribution); 3 scaffolder unit tests covering union-merge, dedup, and legacy-passthrough; 2 Scenario B regressions for legacy-install upgrade and user-domain preservation through subsequent merges; 2 doctor checks for missing-block (legacy install) and malformed-block scenarios.
|
|
25
|
+
|
|
26
|
+
Release group: 2 PRs (1 patch, 1 missing-declaration treated as none). v2.10.0 → v2.10.1.
|
|
27
|
+
|
|
28
|
+
## [2.10.0] — 2026-04-29
|
|
29
|
+
|
|
30
|
+
Drops support for Node 18, which reached LTS end-of-life on 2025-04-30 (12 months before this release). The drop unblocks two Dependabot PRs stuck on Node-20-only features (`inquirer 13`'s `util.styleText` and `ora 9`'s regex `v` flag) and ships those bumps in the same release. Also recovers from a Dependabot routing misconfiguration: `.github/dependabot.yml` now declares `target-branch: develop` for both ecosystems, fixing a config gap that caused 5 PRs in the v2.9.3 → v2.10.0 window to be opened against main instead of develop. Their content is preserved across both branches via a recovery sync.
|
|
31
|
+
|
|
32
|
+
### Breaking
|
|
33
|
+
|
|
34
|
+
- **Node 18 no longer supported** (PR #167) — `engines.node` is now `>=20.0.0`. Running `npm install -g worclaude` on Node 18 will print an `EBADENGINE` warning (npm doesn't block by default but the warning is visible). CI test matrix dropped from `[18, 20, 22]` to `[20, 22]`. Required-status-checks on the `develop-protection` and `main-protection` rulesets updated accordingly. Tech-stack mentions refreshed in CLAUDE.md, AGENTS.md, README.md, `docs/guide/getting-started.md`, and `templates/specs/spec-md-library.md`.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- **`ora` 8.2.0 → 9.4.0** (PR #169) — major bump. ora 9 uses regex `v` flag (Node 20+); previously blocked by the v2.9.x Node 18 matrix.
|
|
39
|
+
- **`inquirer` 12.11.1 → 13.4.2** (PR #169) — major bump. inquirer 13 uses `util.styleText` (Node 20.12+); previously blocked by the v2.9.x Node 18 matrix.
|
|
40
|
+
- **Dependabot routing fixed** (PR #168) — added `target-branch: develop` to both `npm` and `github-actions` ecosystems in `.github/dependabot.yml`. Previously, Dependabot defaulted to the repo's default branch (main), causing PRs to misroute. Future Dependabot Monday runs will correctly target develop.
|
|
41
|
+
|
|
42
|
+
### Internal
|
|
43
|
+
|
|
44
|
+
- **Recovery sync develop ← main** (PR #168) — brings 5 misrouted Dependabot squash commits from main onto develop (prettier 3.8.3, claude-code-action 1.0.109, actions/cache 5, vitest 4, eslint 10). All updates were legitimate; merge made via `git merge origin/main --no-ff` with auto-resolution.
|
|
45
|
+
|
|
46
|
+
Release group: 3 PRs (1 minor, 1 patch, 1 none). No missing Version bump declarations.
|
|
47
|
+
|
|
7
48
|
## [2.9.3] — 2026-04-29
|
|
8
49
|
|
|
9
50
|
Security tooling refresh shipped as a paired group: a CI-tooling migration from Snyk (whose free-tier scan limit had blocked the v2.9.2 release PR) to a GitHub-native open-source SCA stack (Dependabot + OSV-Scanner), and the cleanup of the inaugural CodeQL scan after enabling the default setup. CodeQL surfaced 5 findings — 2× High "Incomplete multi-character sanitization" on the project-scanner README detector's HTML-stripping helpers, and 3× Medium "Workflow does not contain permissions" on `ci.yml`'s three jobs — all closed in this release. The sanitization fix extracts a `stripUntilStable(text, regex)` helper for the do-while-until-stable pattern; the permissions fix adds a top-level `permissions: contents: read` block matching the rest of the repo's workflows. SECURITY.md's AI-detected typosquat section also refined with the actual chain context: the `claude` npm package is `bcherny/redirect-claude`, an intentional Boris-Cherny-maintained typosquat-warning redirect, not an abandoned package as previously documented.
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<a href="https://www.npmjs.com/package/worclaude"><img src="https://img.shields.io/npm/dm/worclaude" alt="downloads" /></a>
|
|
8
8
|
<a href="https://github.com/sefaertunc/Worclaude/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/sefaertunc/Worclaude/ci.yml?label=tests" alt="tests" /></a>
|
|
9
9
|
<a href="LICENSE"><img src="https://img.shields.io/github/license/sefaertunc/Worclaude" alt="license" /></a>
|
|
10
|
-
<img src="https://img.shields.io/badge/node-%3E%
|
|
10
|
+
<img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen" alt="node >= 20" />
|
|
11
11
|
<img src="https://img.shields.io/badge/built%20for-Claude%20Code-cc785c" alt="Built for Claude Code" />
|
|
12
12
|
</p>
|
|
13
13
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "worclaude",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.1",
|
|
4
4
|
"description": "The Workflow Layer for Claude Code — scaffold agents, commands, skills, hooks, and memory into any project",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"funding": "https://github.com/sponsors/sefaertunc",
|
|
29
29
|
"author": "Sefa Ertunç",
|
|
30
30
|
"engines": {
|
|
31
|
-
"node": ">=
|
|
31
|
+
"node": ">=20.0.0"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"test": "vitest run",
|
|
@@ -66,18 +66,18 @@
|
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"@sefaertunc/anthropic-watch-client": "^1.0.2",
|
|
68
68
|
"chalk": "^5.4.1",
|
|
69
|
-
"commander": "^
|
|
69
|
+
"commander": "^14.0.3",
|
|
70
70
|
"fs-extra": "^11.3.0",
|
|
71
|
-
"inquirer": "^
|
|
72
|
-
"ora": "^
|
|
71
|
+
"inquirer": "^13.4.2",
|
|
72
|
+
"ora": "^9.4.0",
|
|
73
73
|
"smol-toml": "^1.6.1",
|
|
74
74
|
"yaml": "^2.8.3"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"eslint": "^
|
|
77
|
+
"eslint": "^10.2.1",
|
|
78
78
|
"prettier": "^3.5.3",
|
|
79
79
|
"vitepress": "^1.6.4",
|
|
80
|
-
"vitest": "^
|
|
80
|
+
"vitest": "^4.1.5"
|
|
81
81
|
},
|
|
82
82
|
"overrides": {
|
|
83
83
|
"brace-expansion": "^1.1.13",
|
package/src/commands/doctor.js
CHANGED
|
@@ -286,6 +286,36 @@ async function readSettingsJson(projectRoot) {
|
|
|
286
286
|
}
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
+
async function checkSandboxBlock(projectRoot) {
|
|
290
|
+
const settings = await readSettingsJson(projectRoot);
|
|
291
|
+
if (!settings) return null;
|
|
292
|
+
|
|
293
|
+
if (!settings.sandbox) {
|
|
294
|
+
return result(
|
|
295
|
+
WARN,
|
|
296
|
+
'Sandbox block',
|
|
297
|
+
'settings.json missing `sandbox` block. Run `worclaude upgrade` to scaffold network deny/allow lists.'
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const network = settings.sandbox.network;
|
|
302
|
+
if (!network || typeof network !== 'object') {
|
|
303
|
+
return result(WARN, 'Sandbox block', '`sandbox.network` block missing or malformed');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const issues = [];
|
|
307
|
+
if (!Array.isArray(network.deniedDomains)) {
|
|
308
|
+
issues.push('`deniedDomains` not an array');
|
|
309
|
+
}
|
|
310
|
+
if (!Array.isArray(network.allowedDomains)) {
|
|
311
|
+
issues.push('`allowedDomains` not an array');
|
|
312
|
+
}
|
|
313
|
+
if (issues.length > 0) {
|
|
314
|
+
return result(WARN, 'Sandbox block', issues.join('; '));
|
|
315
|
+
}
|
|
316
|
+
return result(PASS, 'Sandbox block', null);
|
|
317
|
+
}
|
|
318
|
+
|
|
289
319
|
async function checkHookEventNames(projectRoot) {
|
|
290
320
|
const settings = await readSettingsJson(projectRoot);
|
|
291
321
|
if (!settings) {
|
|
@@ -1047,6 +1077,7 @@ export async function doctorCommand(options = {}) {
|
|
|
1047
1077
|
record('core', await checkClaudeMdMemoryGuidance(projectRoot));
|
|
1048
1078
|
record('core', await checkAgentsMd(projectRoot));
|
|
1049
1079
|
record('core', await checkSettingsJson(projectRoot));
|
|
1080
|
+
record('core', await checkSandboxBlock(projectRoot));
|
|
1050
1081
|
record('core', await checkSessions(projectRoot));
|
|
1051
1082
|
spacer();
|
|
1052
1083
|
|
package/src/core/merger.js
CHANGED
|
@@ -241,20 +241,28 @@ export async function mergeSettingsPermissionsAndHooks(
|
|
|
241
241
|
const existing = parseUserJson(existingRaw, '.claude/settings.json');
|
|
242
242
|
|
|
243
243
|
// Merge permissions (Tier 1) — union-merge both allow and deny
|
|
244
|
-
const existingAllow = existing.permissions?.allow || [];
|
|
245
|
-
const workflowAllow = workflowSettings.permissions?.allow || [];
|
|
246
|
-
const newAllow = workflowAllow.filter((p) => !existingAllow.includes(p));
|
|
247
244
|
if (!existing.permissions) existing.permissions = {};
|
|
248
|
-
existing.permissions
|
|
245
|
+
const newAllow = appendUnique(existing.permissions, 'allow', workflowSettings.permissions?.allow);
|
|
249
246
|
|
|
250
|
-
const
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
existing.permissions.deny = [...existingDeny, ...newDeny];
|
|
247
|
+
const existingDenyLen = (existing.permissions.deny ?? []).length;
|
|
248
|
+
const newDeny = appendUnique(existing.permissions, 'deny', workflowSettings.permissions?.deny);
|
|
249
|
+
if (existingDenyLen === 0 && newDeny === 0) {
|
|
250
|
+
delete existing.permissions.deny;
|
|
255
251
|
}
|
|
256
252
|
|
|
257
|
-
report.added.permissions = newAllow
|
|
253
|
+
report.added.permissions = newAllow + newDeny;
|
|
254
|
+
|
|
255
|
+
// Merge sandbox block (Tier 1 — additive, preserves user customizations)
|
|
256
|
+
const workflowSandbox = workflowSettings.sandbox?.network;
|
|
257
|
+
if (workflowSandbox) {
|
|
258
|
+
if (!existing.sandbox) existing.sandbox = {};
|
|
259
|
+
if (!existing.sandbox.network || typeof existing.sandbox.network !== 'object') {
|
|
260
|
+
existing.sandbox.network = {};
|
|
261
|
+
}
|
|
262
|
+
for (const key of ['deniedDomains', 'allowedDomains']) {
|
|
263
|
+
appendUnique(existing.sandbox.network, key, workflowSandbox[key]);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
258
266
|
|
|
259
267
|
// Merge hooks (Tier 1 + Tier 3)
|
|
260
268
|
if (!existing.hooks) existing.hooks = {};
|
|
@@ -360,6 +368,13 @@ async function mergeSettingsJson(projectRoot, existingScan, selections, report)
|
|
|
360
368
|
}
|
|
361
369
|
}
|
|
362
370
|
|
|
371
|
+
function appendUnique(target, key, source) {
|
|
372
|
+
const existing = Array.isArray(target[key]) ? target[key] : [];
|
|
373
|
+
const additions = Array.isArray(source) ? source.filter((d) => !existing.includes(d)) : [];
|
|
374
|
+
target[key] = [...existing, ...additions];
|
|
375
|
+
return additions.length;
|
|
376
|
+
}
|
|
377
|
+
|
|
363
378
|
function countHooks(hooks) {
|
|
364
379
|
if (!hooks) return 0;
|
|
365
380
|
return Object.values(hooks).reduce((sum, entries) => sum + entries.length, 0);
|
package/src/core/scaffolder.js
CHANGED
|
@@ -204,16 +204,31 @@ export async function scaffoldMemoryDocs(projectRoot) {
|
|
|
204
204
|
|
|
205
205
|
export function mergeSettings(base, ...stacks) {
|
|
206
206
|
const merged = JSON.parse(JSON.stringify(base));
|
|
207
|
-
const
|
|
207
|
+
const inputs = [base, ...stacks].filter(Boolean);
|
|
208
208
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
209
|
+
merged.permissions.allow = unionStringList(inputs, (i) => i.permissions?.allow);
|
|
210
|
+
|
|
211
|
+
if (merged.sandbox?.network) {
|
|
212
|
+
merged.sandbox.network.deniedDomains = unionStringList(
|
|
213
|
+
inputs,
|
|
214
|
+
(i) => i.sandbox?.network?.deniedDomains
|
|
215
|
+
);
|
|
216
|
+
merged.sandbox.network.allowedDomains = unionStringList(
|
|
217
|
+
inputs,
|
|
218
|
+
(i) => i.sandbox?.network?.allowedDomains
|
|
219
|
+
);
|
|
215
220
|
}
|
|
216
221
|
|
|
217
|
-
merged.permissions.allow = [...new Set(baseAllow)];
|
|
218
222
|
return merged;
|
|
219
223
|
}
|
|
224
|
+
|
|
225
|
+
function unionStringList(inputs, accessor) {
|
|
226
|
+
const set = new Set();
|
|
227
|
+
for (const input of inputs) {
|
|
228
|
+
const list = accessor(input);
|
|
229
|
+
if (Array.isArray(list)) {
|
|
230
|
+
for (const item of list) set.add(item);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return [...set];
|
|
234
|
+
}
|
|
@@ -52,7 +52,7 @@ catch (error) { /* [ErrorType]: [message] */ }
|
|
|
52
52
|
## Compatibility Matrix
|
|
53
53
|
| Runtime/Version | Supported | Notes |
|
|
54
54
|
|------------------------|-----------|----------------------------|
|
|
55
|
-
| [Node.js >=
|
|
55
|
+
| [Node.js >= 20] | Yes | [ESM and CJS] |
|
|
56
56
|
| [Node.js 16] | No | [Reason] |
|
|
57
57
|
| [Python >= 3.10] | Yes | [Type hints required] |
|
|
58
58
|
| [Browser (ESM)] | Yes | [Bundle size: ~N kB gzip] |
|