claude-dev-env 8.94.0 → 8.95.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.
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lean-worker
|
|
3
|
+
description: Default worker for a delegated task that only reads, searches, or edits files and runs shell commands. Loads only those tools, so it starts near 26k tokens where a full-tool agent starts near 55k. Pick it first for fixes, lookups, scripted checks, and reviews. Pick a full-tool agent when the task follows a skill, drives the browser or another MCP connector, uses artifacts, watches or stops background tasks, loads deferred tools, or spawns further subagents.
|
|
4
|
+
tools: Read, Edit, Write, Glob, Grep, Bash, PowerShell
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Do the task in the brief. Work only in the files and paths it names. Report what changed, the command output that proves it, and anything that blocked you.
|
|
@@ -8,7 +8,7 @@ Use this playbook when this session lands the fix for the correction itself. One
|
|
|
8
8
|
4. Search for an existing control: a rule, hook, lint, test, or skill that already names this mistake. A control that exists and still let the mistake through makes this a repeat.
|
|
9
9
|
5. Choose the layer with [`correction-lens.md`](../../../../docs/rule-guides/correction-lens.md). When `Repeat` is `yes`, or step 4 found a control, go one layer above the one that failed.
|
|
10
10
|
6. Land the control in the repository whose code, CI, or agents it guards, per the guide's "Where the control lands" section. When that repository is not in this session, add it. When it cannot be added, print the brief, name the missing repository, and stop.
|
|
11
|
-
7. Prove the control. Show it fail on the mistake from the brief's `Evidence` line, then pass with the change, per [`falsify-before-green.md`](../../../../rules/falsify-before-green.md) and [`proof-before-pull-request.md`](../../../../docs/rule-guides/proof-before-pull-request.md). Without that proof, open no pull request.
|
|
12
|
-
8. Open the pull request through `pr-lifecycle`. Put the brief, the layer, and both proof results in the body.
|
|
11
|
+
7. Prove the control. Show it fail on the mistake from the brief's `Evidence` line, then pass with the change, per [`falsify-before-green.md`](../../../../rules/falsify-before-green.md) and [`proof-before-pull-request.md`](../../../../docs/rule-guides/proof-before-pull-request.md). Without that proof, open no pull request. When the control is a hook, read the correction instructions of the repository from step 6 as step 3 does. When they name an incident eval, add the incident as a case to that eval in the same pull request. Run the eval's compare against the base branch and keep the line that shows the case fails on the base and holds on the branch.
|
|
12
|
+
8. Open the pull request through `pr-lifecycle`. Put the brief, the layer, and both proof results in the body. When step 7 added an incident case, also name its path and its compare line.
|
|
13
13
|
|
|
14
14
|
**Reply:** the layer chosen, why each higher layer cannot hold the lesson, and the pull request link.
|
|
@@ -140,3 +140,10 @@ def test_should_return_the_playbook_from_step_two() -> None:
|
|
|
140
140
|
handed_off_steps = correction_task_card.handed_off_playbook_steps(FIX_PLAYBOOK_TEXT)
|
|
141
141
|
assert handed_off_steps.startswith("2. ")
|
|
142
142
|
assert "pull request link" in handed_off_steps
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_should_hand_off_the_incident_case_step_with_the_proof() -> None:
|
|
146
|
+
handed_off_steps = correction_task_card.handed_off_playbook_steps(FIX_PLAYBOOK_TEXT)
|
|
147
|
+
proof_step = handed_off_steps.split("\n7. ", 1)[1].split("\n8. ", 1)[0]
|
|
148
|
+
assert "incident eval" in proof_step
|
|
149
|
+
assert "same pull request" in proof_step
|
|
@@ -67,6 +67,9 @@ const HOST_DEFINITIONS = Object.freeze({
|
|
|
67
67
|
orphanedVersionsDirectory: (homeDirectory, spec) => join(
|
|
68
68
|
homeDirectory, 'plugins', 'cache', spec.marketplaceName, spec.name,
|
|
69
69
|
),
|
|
70
|
+
staleCloneDirectory: (homeDirectory, spec) => join(
|
|
71
|
+
homeDirectory, 'plugins', 'marketplaces', `${spec.marketplaceRepository.replaceAll('/', '-')}..clone`,
|
|
72
|
+
),
|
|
70
73
|
installVerb: 'install',
|
|
71
74
|
uninstallVerb: 'uninstall',
|
|
72
75
|
}),
|
|
@@ -215,6 +218,24 @@ function singleLineDetail(text) {
|
|
|
215
218
|
return (text ?? '').split(/\r?\n/).map(eachLine => eachLine.trim()).filter(Boolean).join(' ');
|
|
216
219
|
}
|
|
217
220
|
|
|
221
|
+
/**
|
|
222
|
+
* Delete the clone folder an interrupted marketplace add left behind.
|
|
223
|
+
*
|
|
224
|
+
* Claude Code clones a marketplace into `<owner>-<repository>..clone` and
|
|
225
|
+
* renames it when the clone finishes. A clone that stops partway leaves that
|
|
226
|
+
* folder with its `.git/shallow.lock`, and `marketplace remove` does not
|
|
227
|
+
* delete it, so every later add fails on the lock. Removing the folder
|
|
228
|
+
* before the add lets the add start from an empty slot.
|
|
229
|
+
*
|
|
230
|
+
* @param {string|undefined} cloneDirectory The leftover clone folder.
|
|
231
|
+
* @returns {boolean} True when a folder was removed.
|
|
232
|
+
*/
|
|
233
|
+
export function removeStaleMarketplaceClone(cloneDirectory) {
|
|
234
|
+
if (!cloneDirectory || !existsSync(cloneDirectory)) return false;
|
|
235
|
+
rmSync(cloneDirectory, { recursive: true, force: true, maxRetries: 3 });
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
|
|
218
239
|
const ORPHANED_VERSION_MARKER = '.orphaned_at';
|
|
219
240
|
|
|
220
241
|
/**
|
|
@@ -269,6 +290,9 @@ function installForHost(allSpecs, host, homeDirectory, environment, runCommand,
|
|
|
269
290
|
});
|
|
270
291
|
if (namesAnAbsentCommand(outcome)) return absentHostOutcome(allSpecs, host, executable);
|
|
271
292
|
}
|
|
293
|
+
if (HOST_DEFINITIONS[host].staleCloneDirectory && homeDirectory) {
|
|
294
|
+
removeStaleMarketplaceClone(HOST_DEFINITIONS[host].staleCloneDirectory(homeDirectory, allSpecs[0]));
|
|
295
|
+
}
|
|
272
296
|
for (const commandArguments of plan.commands) {
|
|
273
297
|
const outcome = runCommand(executable, [...commandArguments], {
|
|
274
298
|
environment: commandEnvironment,
|
|
@@ -300,6 +324,8 @@ function installForHost(allSpecs, host, homeDirectory, environment, runCommand,
|
|
|
300
324
|
* Each host first removes every plugin in the set and then the marketplace,
|
|
301
325
|
* so an older version never survives beside the new one. A removal that finds
|
|
302
326
|
* nothing to remove exits non-zero on a fresh home, and the install goes on.
|
|
327
|
+
* Before the Claude marketplace add, the install deletes the half-made clone
|
|
328
|
+
* folder an interrupted earlier add left, so the add starts in an empty slot.
|
|
303
329
|
*
|
|
304
330
|
* Each host is one member of the batch. A host without its command-line tool
|
|
305
331
|
* is skipped and a host whose command fails is reported, so the rules, hooks,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
installMarketplacePlugins,
|
|
16
16
|
marketplacePluginsPlan,
|
|
17
17
|
removeOrphanedPluginVersions,
|
|
18
|
+
removeStaleMarketplaceClone,
|
|
18
19
|
removeRetiredMarketplacePlugins,
|
|
19
20
|
shouldInstallMarketplacePlugin,
|
|
20
21
|
} from './install-pstack-plugin.mjs';
|
|
@@ -478,3 +479,24 @@ test('a held folder that was not searched adds no claim about what holds it', ()
|
|
|
478
479
|
assert.match(claude.warning, /EPERM: operation not permitted$/);
|
|
479
480
|
assert.doesNotMatch(claude.warning, /likely holder|Close /);
|
|
480
481
|
});
|
|
482
|
+
|
|
483
|
+
test('should delete a half-made marketplace clone before the shared marketplace add', () => {
|
|
484
|
+
const claudeRoot = mkdtempSync(join(tmpdir(), 'cde-stale-clone-'));
|
|
485
|
+
const cloneDirectory = join(claudeRoot, 'plugins', 'marketplaces', 'jl-cmd-projects-claude-dev-env..clone');
|
|
486
|
+
mkdirSync(join(cloneDirectory, '.git'), { recursive: true });
|
|
487
|
+
writeFileSync(join(cloneDirectory, '.git', 'shallow.lock'), '');
|
|
488
|
+
const allCloneStatesAtAdd = [];
|
|
489
|
+
const outcome = installMarketplacePlugins([USAGE_WRAPUP_PLUGIN_SPEC], { claudeRoot, hosts: ['claude'] }, {
|
|
490
|
+
runCommand: (executable, commandArguments) => {
|
|
491
|
+
if (commandArguments[2] === 'add') allCloneStatesAtAdd.push(existsSync(cloneDirectory));
|
|
492
|
+
return { status: 0, stderr: '' };
|
|
493
|
+
},
|
|
494
|
+
});
|
|
495
|
+
assert.equal(outcome.status, 'installed');
|
|
496
|
+
assert.deepEqual(allCloneStatesAtAdd, [false]);
|
|
497
|
+
rmSync(claudeRoot, { recursive: true, force: true });
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
test('should report no removal when no stale marketplace clone exists', () => {
|
|
501
|
+
assert.equal(removeStaleMarketplaceClone(join(tmpdir(), 'cde-no-such-clone')), false);
|
|
502
|
+
});
|