@agentworkforce/workload-router 0.15.0 → 0.15.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 +2 -0
- package/README.md +8 -2
- package/dist/generated/personas.d.ts +2 -1034
- package/dist/generated/personas.d.ts.map +1 -1
- package/dist/generated/personas.js +2 -850
- package/dist/generated/personas.js.map +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -34
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +129 -444
- package/dist/index.test.js.map +1 -1
- package/package.json +1 -1
package/dist/index.test.js
CHANGED
|
@@ -1,304 +1,85 @@
|
|
|
1
1
|
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { HARNESS_SKILL_TARGETS, HARNESS_VALUES, materializeSkills, materializeSkillsFor, personaCatalog, resolvePersona, resolvePersonaByTier, resolveSidecar, usePersona, useSelection } from './index.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
tier: 'best',
|
|
62
|
-
rationale: 'cross-layer workflow failures need deeper investigation'
|
|
63
|
-
},
|
|
64
|
-
'npm-provenance': {
|
|
65
|
-
tier: 'best-value',
|
|
66
|
-
rationale: 'mechanical workflow wiring'
|
|
67
|
-
},
|
|
68
|
-
'cloud-sandbox-infra': {
|
|
69
|
-
tier: 'best',
|
|
70
|
-
rationale: 'infra changes need deep reasoning'
|
|
71
|
-
},
|
|
72
|
-
'sage-slack-egress-migration': {
|
|
73
|
-
tier: 'best-value',
|
|
74
|
-
rationale: 'migration wiring can use the balanced default'
|
|
75
|
-
},
|
|
76
|
-
'sage-proactive-rewire': {
|
|
77
|
-
tier: 'best-value',
|
|
78
|
-
rationale: 'rewiring work is configuration-heavy rather than max-depth by default'
|
|
79
|
-
},
|
|
80
|
-
'cloud-slack-proxy-guard': {
|
|
81
|
-
tier: 'best-value',
|
|
82
|
-
rationale: 'proxy guard checks usually fit the balanced default tier'
|
|
83
|
-
},
|
|
84
|
-
'sage-cloud-e2e-conduction': {
|
|
85
|
-
tier: 'best-value',
|
|
86
|
-
rationale: 'e2e conduction benefits from strong reasoning without the highest-cost default'
|
|
87
|
-
},
|
|
88
|
-
'capability-discovery': {
|
|
89
|
-
tier: 'best-value',
|
|
90
|
-
rationale: 'lightweight discovery work'
|
|
91
|
-
},
|
|
92
|
-
'npm-package-compat': {
|
|
93
|
-
tier: 'best-value',
|
|
94
|
-
rationale: 'mechanical package.json audits'
|
|
95
|
-
},
|
|
96
|
-
posthog: {
|
|
97
|
-
tier: 'best-value',
|
|
98
|
-
rationale: 'analytics lookups via MCP'
|
|
99
|
-
},
|
|
100
|
-
'persona-authoring': {
|
|
101
|
-
tier: 'best-value',
|
|
102
|
-
rationale: 'scaffolding a persona is mechanical wiring work'
|
|
103
|
-
},
|
|
104
|
-
'slop-audit': {
|
|
105
|
-
tier: 'minimum',
|
|
106
|
-
rationale: 'quick slop sweep is enough here'
|
|
107
|
-
},
|
|
108
|
-
'api-contract-review': {
|
|
109
|
-
tier: 'best',
|
|
110
|
-
rationale: 'breaking-change classification has high blast radius'
|
|
111
|
-
},
|
|
112
|
-
'local-stack-orchestration': {
|
|
113
|
-
tier: 'best-value',
|
|
114
|
-
rationale: 'compose wiring is mechanical given the topology'
|
|
115
|
-
},
|
|
116
|
-
'e2e-validation': {
|
|
117
|
-
tier: 'best',
|
|
118
|
-
rationale: 'hop-by-hop validation is the last line of defense'
|
|
119
|
-
},
|
|
120
|
-
'write-integration-tests': {
|
|
121
|
-
tier: 'best-value',
|
|
122
|
-
rationale: 'integration test template is well-defined'
|
|
123
|
-
},
|
|
124
|
-
'agent-relay-workflow': {
|
|
125
|
-
tier: 'best-value',
|
|
126
|
-
rationale: 'workflow orchestration uses balanced reasoning'
|
|
127
|
-
},
|
|
128
|
-
'relay-orchestrator': {
|
|
129
|
-
tier: 'best-value',
|
|
130
|
-
rationale: 'relay orchestration uses balanced reasoning'
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
assert.equal(result.personaId, 'code-reviewer');
|
|
135
|
-
assert.equal(result.tier, 'minimum');
|
|
136
|
-
assert.equal(result.runtime.harness, 'opencode');
|
|
137
|
-
});
|
|
138
|
-
test('resolves npm-package-compat to npm-package-bundler-guard from default routing profile', () => {
|
|
139
|
-
const result = resolvePersona('npm-package-compat');
|
|
140
|
-
assert.equal(result.personaId, 'npm-package-bundler-guard');
|
|
141
|
-
assert.equal(result.tier, 'best-value');
|
|
142
|
-
assert.equal(result.runtime.harness, 'claude');
|
|
143
|
-
assert.match(result.rationale, /balanced-default/);
|
|
144
|
-
});
|
|
145
|
-
test('legacy tier override remains available via resolvePersonaByTier', () => {
|
|
146
|
-
const result = resolvePersonaByTier('architecture-plan', 'best');
|
|
147
|
-
assert.equal(result.runtime.harness, 'codex');
|
|
148
|
-
assert.equal(result.runtime.harnessSettings.reasoning, 'high');
|
|
149
|
-
assert.match(result.rationale, /legacy-tier-override/);
|
|
150
|
-
});
|
|
151
|
-
test('resolvePersona propagates mcpServers and permissions to the selection', () => {
|
|
152
|
-
// posthog is the library's canonical carrier for mcpServers + permissions.
|
|
153
|
-
// It used to also carry env.POSTHOG_API_KEY, but the persona switched to
|
|
154
|
-
// mcp-remote/OAuth (stdio, no env, no bearer header) in e3342c7. Env
|
|
155
|
-
// propagation through the loader is covered by the cli package's
|
|
156
|
-
// local-personas cascade tests.
|
|
157
|
-
const selection = resolvePersona('posthog');
|
|
158
|
-
assert.equal(selection.personaId, 'posthog');
|
|
159
|
-
// mcpServers carry through with the full stdio shape (command + args).
|
|
160
|
-
const posthogServer = selection.mcpServers?.posthog;
|
|
161
|
-
assert.ok(posthogServer, 'expected mcpServers.posthog on the selection');
|
|
162
|
-
assert.equal(posthogServer.type, 'stdio');
|
|
163
|
-
if (posthogServer.type === 'stdio') {
|
|
164
|
-
assert.equal(posthogServer.command, 'npx');
|
|
165
|
-
assert.deepEqual(posthogServer.args, [
|
|
166
|
-
'-y',
|
|
167
|
-
'mcp-remote@latest',
|
|
168
|
-
'https://mcp.posthog.com/mcp'
|
|
169
|
-
]);
|
|
170
|
-
}
|
|
171
|
-
// permissions.allow is carried without modification.
|
|
172
|
-
assert.deepEqual(selection.permissions?.allow, ['mcp__posthog']);
|
|
173
|
-
});
|
|
174
|
-
test('resolvePersonaByTier also propagates mcpServers / permissions', () => {
|
|
175
|
-
const selection = resolvePersonaByTier('posthog', 'minimum');
|
|
176
|
-
assert.equal(selection.tier, 'minimum');
|
|
177
|
-
assert.ok(selection.mcpServers, 'mcpServers should flow through tier override resolver');
|
|
178
|
-
assert.ok(selection.permissions, 'permissions should flow through tier override resolver');
|
|
179
|
-
});
|
|
180
|
-
test('resolvePersonaByTier propagates persona input declarations', () => {
|
|
181
|
-
const selection = resolvePersonaByTier('persona-authoring', 'best');
|
|
3
|
+
import { HARNESS_SKILL_TARGETS, HARNESS_VALUES, PERSONA_INTENTS, listBuiltInPersonas, materializeSkills, materializeSkillsFor, personaCatalog, resolvePersona, resolvePersonaByTier, resolveSidecar, routingProfiles, usePersona, useSelection } from './index.js';
|
|
4
|
+
const prpmSkill = {
|
|
5
|
+
id: 'prpm/npm-trusted-publishing',
|
|
6
|
+
source: '@prpm/npm-trusted-publishing',
|
|
7
|
+
description: 'trusted publishing skill'
|
|
8
|
+
};
|
|
9
|
+
const skillShSkill = {
|
|
10
|
+
id: 'skill.sh/find-skills',
|
|
11
|
+
source: 'https://github.com/vercel-labs/skills#find-skills',
|
|
12
|
+
description: 'skill.sh discovery skill'
|
|
13
|
+
};
|
|
14
|
+
function syntheticSelection(over = {}) {
|
|
15
|
+
const runtime = {
|
|
16
|
+
harness: 'codex',
|
|
17
|
+
model: 'test-model',
|
|
18
|
+
systemPrompt: 'test prompt',
|
|
19
|
+
harnessSettings: { reasoning: 'medium', timeoutSeconds: 300 }
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
personaId: 'synthetic',
|
|
23
|
+
tier: 'best-value',
|
|
24
|
+
runtime,
|
|
25
|
+
skills: [],
|
|
26
|
+
rationale: 'test',
|
|
27
|
+
...over
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function syntheticSpec(over = {}) {
|
|
31
|
+
const baseRuntime = {
|
|
32
|
+
harness: 'claude',
|
|
33
|
+
model: 'claude-3-5-sonnet',
|
|
34
|
+
systemPrompt: 'base',
|
|
35
|
+
harnessSettings: { reasoning: 'medium', timeoutSeconds: 300 }
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
id: 's',
|
|
39
|
+
intent: 'documentation',
|
|
40
|
+
tags: ['documentation'],
|
|
41
|
+
description: 'd',
|
|
42
|
+
skills: [],
|
|
43
|
+
tiers: { best: baseRuntime, 'best-value': baseRuntime, minimum: baseRuntime },
|
|
44
|
+
...over
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
test('built-in catalog is limited to internal system personas', () => {
|
|
48
|
+
const builtIns = listBuiltInPersonas();
|
|
49
|
+
assert.deepEqual(builtIns.map((p) => p.id), ['persona-maker']);
|
|
50
|
+
assert.equal(personaCatalog['persona-authoring']?.id, 'persona-maker');
|
|
51
|
+
assert.equal(personaCatalog.review, undefined);
|
|
52
|
+
assert.ok(PERSONA_INTENTS.includes('review'));
|
|
53
|
+
assert.equal(routingProfiles.default.intents.review.tier, 'best-value');
|
|
54
|
+
});
|
|
55
|
+
test('resolves persona-maker from the default routing profile', () => {
|
|
56
|
+
const selection = resolvePersona('persona-authoring');
|
|
57
|
+
assert.equal(selection.personaId, 'persona-maker');
|
|
58
|
+
assert.equal(selection.tier, 'best');
|
|
59
|
+
assert.equal(selection.runtime.harness, 'codex');
|
|
60
|
+
assert.match(selection.rationale, /balanced-default/);
|
|
182
61
|
assert.equal(selection.inputs?.TARGET_DIR?.default, '.agentworkforce/workforce/personas');
|
|
183
62
|
assert.equal(selection.inputs?.CREATE_MODE?.default, 'local');
|
|
184
|
-
// Persona-maker carries its full authoring spec in agentsMdContent; the
|
|
185
|
-
// CLI renders input placeholders into the sidecar before materialization,
|
|
186
|
-
// so the unrendered selection here still contains the literal `$TARGET_DIR`
|
|
187
|
-
// reference.
|
|
188
63
|
assert.match(selection.agentsMdContent ?? '', /\$TARGET_DIR\/<id>\.json/);
|
|
189
64
|
assert.equal(selection.runtime.harnessSettings.sandboxMode, 'workspace-write');
|
|
190
65
|
assert.equal(selection.runtime.harnessSettings.approvalPolicy, 'on-request');
|
|
191
66
|
assert.equal(selection.runtime.harnessSettings.workspaceWriteNetworkAccess, true);
|
|
192
67
|
assert.match(selection.agentsMdContent ?? '', /Do not request network escalation only to complete this fallback/);
|
|
193
|
-
assert.doesNotMatch(selection.agentsMdContent ?? '', /Check prpm\.dev as a secondary registry when skills\.sh has nothing relevant/);
|
|
194
|
-
});
|
|
195
|
-
test('personas with no optional fields keep them undefined on the selection', () => {
|
|
196
|
-
// code-reviewer has no env/mcpServers/permissions in its JSON.
|
|
197
|
-
const selection = resolvePersona('review');
|
|
198
|
-
assert.equal(selection.env, undefined);
|
|
199
|
-
assert.equal(selection.mcpServers, undefined);
|
|
200
|
-
assert.equal(selection.permissions, undefined);
|
|
201
68
|
});
|
|
202
|
-
test('
|
|
203
|
-
|
|
204
|
-
assert.
|
|
205
|
-
assert.equal(testStrategy.tier, 'best-value');
|
|
206
|
-
const tdd = resolvePersona('tdd-enforcement');
|
|
207
|
-
assert.equal(tdd.personaId, 'tdd-guard');
|
|
208
|
-
assert.equal(tdd.tier, 'best-value');
|
|
209
|
-
const flake = resolvePersona('flake-investigation');
|
|
210
|
-
assert.equal(flake.personaId, 'flake-hunter');
|
|
211
|
-
assert.equal(flake.tier, 'best');
|
|
212
|
-
assert.equal(flake.runtime.harness, 'codex');
|
|
69
|
+
test('optional pack-owned intents do not resolve from the built-in catalog', () => {
|
|
70
|
+
assert.throws(() => resolvePersona('review'), /No built-in persona is registered for intent "review".*personas-core/);
|
|
71
|
+
assert.throws(() => resolvePersonaByTier('review', 'best'), /No built-in persona is registered for intent "review"/);
|
|
213
72
|
});
|
|
214
|
-
test('
|
|
215
|
-
const
|
|
216
|
-
assert.equal(
|
|
217
|
-
assert.equal(
|
|
218
|
-
|
|
219
|
-
assert.
|
|
220
|
-
assert.equal(debuggerSelection.tier, 'best');
|
|
221
|
-
assert.equal(debuggerSelection.runtime.harness, 'codex');
|
|
222
|
-
const security = resolvePersona('security-review');
|
|
223
|
-
assert.equal(security.personaId, 'security-reviewer');
|
|
224
|
-
assert.equal(security.tier, 'best');
|
|
225
|
-
const docs = resolvePersona('documentation');
|
|
226
|
-
assert.equal(docs.personaId, 'technical-writer');
|
|
227
|
-
assert.equal(docs.tier, 'best-value');
|
|
228
|
-
const verification = resolvePersona('verification');
|
|
229
|
-
assert.equal(verification.personaId, 'verifier');
|
|
230
|
-
assert.equal(verification.tier, 'best-value');
|
|
231
|
-
const opencodeWorkflow = resolvePersona('opencode-workflow-correctness');
|
|
232
|
-
assert.equal(opencodeWorkflow.personaId, 'opencode-workflow-specialist');
|
|
233
|
-
assert.equal(opencodeWorkflow.tier, 'best');
|
|
234
|
-
assert.equal(opencodeWorkflow.runtime.harness, 'codex');
|
|
235
|
-
});
|
|
236
|
-
test('resolves agent-relay-workflow persona from the default routing profile', () => {
|
|
237
|
-
const maker = resolvePersona('agent-relay-workflow');
|
|
238
|
-
assert.equal(maker.personaId, 'agent-relay-workflow');
|
|
239
|
-
assert.equal(maker.tier, 'best-value');
|
|
240
|
-
assert.equal(maker.runtime.harness, 'opencode');
|
|
241
|
-
assert.equal(maker.skills.length, 4);
|
|
242
|
-
assert.equal(maker.skills[0].id, 'skill.sh/writing-agent-relay-workflows');
|
|
243
|
-
assert.equal(maker.skills[3].id, 'prpm/choosing-swarm-patterns');
|
|
244
|
-
assert.match(maker.runtime.systemPrompt, /complete workflow source/);
|
|
245
|
-
assert.match(maker.runtime.systemPrompt, /GitHub primitive shipping steps/);
|
|
246
|
-
assert.match(maker.runtime.systemPrompt, /createGitHubStep/);
|
|
247
|
-
});
|
|
248
|
-
// removed: writing-agent-relay-workflows persona renamed to agent-relay-workflow
|
|
249
|
-
test('resolves relay-orchestrator persona from the default routing profile', () => {
|
|
250
|
-
const relay = resolvePersona('relay-orchestrator');
|
|
251
|
-
assert.equal(relay.personaId, 'relay-orchestrator');
|
|
252
|
-
assert.equal(relay.tier, 'best-value');
|
|
253
|
-
assert.equal(relay.runtime.harness, 'opencode');
|
|
254
|
-
assert.equal(relay.skills.length, 1);
|
|
255
|
-
assert.equal(relay.skills[0].id, 'running-headless-orchestrator');
|
|
256
|
-
});
|
|
257
|
-
test('resolves anti-slop-auditor with the jscpd skill.sh skill attached', () => {
|
|
258
|
-
const auditor = resolvePersona('slop-audit');
|
|
259
|
-
assert.equal(auditor.personaId, 'anti-slop-auditor');
|
|
260
|
-
assert.equal(auditor.tier, 'best');
|
|
261
|
-
assert.equal(auditor.runtime.harness, 'codex');
|
|
262
|
-
assert.equal(auditor.skills.length, 1);
|
|
263
|
-
assert.equal(auditor.skills[0].id, 'kucherenko/jscpd');
|
|
264
|
-
assert.match(auditor.skills[0].source, /github\.com\/kucherenko\/jscpd#jscpd/);
|
|
265
|
-
// materializeSkillsFor must not throw — the skill.sh URL must be supported.
|
|
266
|
-
const plan = materializeSkillsFor(auditor);
|
|
267
|
-
assert.equal(plan.installs.length, 1);
|
|
268
|
-
assert.deepEqual(plan.installs[0].installCommand, [
|
|
269
|
-
'npx',
|
|
270
|
-
'-y',
|
|
271
|
-
'skills',
|
|
272
|
-
'add',
|
|
273
|
-
'https://github.com/kucherenko/jscpd',
|
|
274
|
-
'--skill',
|
|
275
|
-
'jscpd',
|
|
276
|
-
'-y'
|
|
277
|
-
]);
|
|
73
|
+
test('legacy tier override remains available for internal personas', () => {
|
|
74
|
+
const selection = resolvePersonaByTier('persona-authoring', 'minimum');
|
|
75
|
+
assert.equal(selection.personaId, 'persona-maker');
|
|
76
|
+
assert.equal(selection.tier, 'minimum');
|
|
77
|
+
assert.equal(selection.runtime.harness, 'opencode');
|
|
78
|
+
assert.match(selection.rationale, /legacy-tier-override/);
|
|
278
79
|
});
|
|
279
80
|
test('claude is a recognized harness value', () => {
|
|
280
81
|
assert.ok(HARNESS_VALUES.includes('claude'));
|
|
281
82
|
});
|
|
282
|
-
test('personas default to an empty skills array when none declared', () => {
|
|
283
|
-
const reviewer = personaCatalog.review;
|
|
284
|
-
assert.ok(Array.isArray(reviewer.skills));
|
|
285
|
-
assert.equal(reviewer.skills.length, 0);
|
|
286
|
-
});
|
|
287
|
-
test('resolves npm-provenance persona with the trusted publishing skill attached', () => {
|
|
288
|
-
const selection = resolvePersona('npm-provenance');
|
|
289
|
-
assert.equal(selection.personaId, 'npm-provenance-publisher');
|
|
290
|
-
assert.equal(selection.tier, 'best-value');
|
|
291
|
-
assert.equal(selection.skills.length, 1);
|
|
292
|
-
const [skill] = selection.skills;
|
|
293
|
-
assert.equal(skill.id, 'prpm/npm-trusted-publishing');
|
|
294
|
-
assert.match(skill.source, /prpm\.dev\/packages\/@prpm\/npm-trusted-publishing/);
|
|
295
|
-
assert.match(selection.runtime.systemPrompt, /prpm\/npm-trusted-publishing/);
|
|
296
|
-
});
|
|
297
|
-
test('resolvePersonaByTier carries persona skills through legacy path', () => {
|
|
298
|
-
const selection = resolvePersonaByTier('npm-provenance', 'best');
|
|
299
|
-
assert.equal(selection.runtime.harness, 'codex');
|
|
300
|
-
assert.equal(selection.skills[0]?.id, 'prpm/npm-trusted-publishing');
|
|
301
|
-
});
|
|
302
83
|
test('HARNESS_SKILL_TARGETS covers every harness value', () => {
|
|
303
84
|
for (const harness of HARNESS_VALUES) {
|
|
304
85
|
const target = HARNESS_SKILL_TARGETS[harness];
|
|
@@ -307,12 +88,27 @@ test('HARNESS_SKILL_TARGETS covers every harness value', () => {
|
|
|
307
88
|
assert.ok(target.dir.length > 0);
|
|
308
89
|
}
|
|
309
90
|
});
|
|
91
|
+
test('materializeSkillsFor derives an install plan from a resolved internal persona', () => {
|
|
92
|
+
const selection = resolvePersona('persona-authoring');
|
|
93
|
+
const plan = materializeSkillsFor(selection);
|
|
94
|
+
assert.equal(plan.harness, 'codex');
|
|
95
|
+
assert.equal(plan.installs.length, 1);
|
|
96
|
+
assert.deepEqual([...plan.installs[0].installCommand], [
|
|
97
|
+
'npx',
|
|
98
|
+
'-y',
|
|
99
|
+
'skills',
|
|
100
|
+
'add',
|
|
101
|
+
'https://github.com/vercel-labs/skills',
|
|
102
|
+
'--skill',
|
|
103
|
+
'find-skills',
|
|
104
|
+
'-y'
|
|
105
|
+
]);
|
|
106
|
+
});
|
|
310
107
|
test('materializeSkills emits a codex-scoped prpm install for a prpm.dev URL', () => {
|
|
311
108
|
const plan = materializeSkills([
|
|
312
109
|
{
|
|
313
|
-
|
|
314
|
-
source: 'https://prpm.dev/packages/@prpm/npm-trusted-publishing'
|
|
315
|
-
description: 'trusted publishing skill'
|
|
110
|
+
...prpmSkill,
|
|
111
|
+
source: 'https://prpm.dev/packages/@prpm/npm-trusted-publishing'
|
|
316
112
|
}
|
|
317
113
|
], 'codex');
|
|
318
114
|
assert.equal(plan.harness, 'codex');
|
|
@@ -325,43 +121,29 @@ test('materializeSkills emits a codex-scoped prpm install for a prpm.dev URL', (
|
|
|
325
121
|
assert.equal(install.installedManifest, '.agents/skills/npm-trusted-publishing/SKILL.md');
|
|
326
122
|
});
|
|
327
123
|
test('materializeSkills routes claude skills to .claude/skills via --as claude', () => {
|
|
328
|
-
const plan = materializeSkills([
|
|
329
|
-
{
|
|
330
|
-
id: 'prpm/npm-trusted-publishing',
|
|
331
|
-
source: '@prpm/npm-trusted-publishing',
|
|
332
|
-
description: 'bare ref form'
|
|
333
|
-
}
|
|
334
|
-
], 'claude');
|
|
124
|
+
const plan = materializeSkills([prpmSkill], 'claude');
|
|
335
125
|
const [install] = plan.installs;
|
|
336
126
|
assert.deepEqual([...install.installCommand], ['npx', '-y', 'prpm', 'install', '@prpm/npm-trusted-publishing', '--as', 'claude']);
|
|
337
127
|
assert.equal(install.installedDir, '.claude/skills/npm-trusted-publishing');
|
|
338
128
|
});
|
|
339
|
-
test('materializeSkillsFor derives an install plan from a resolved persona', () => {
|
|
340
|
-
const selection = resolvePersona('npm-provenance');
|
|
341
|
-
const plan = materializeSkillsFor(selection);
|
|
342
|
-
assert.equal(plan.harness, selection.runtime.harness);
|
|
343
|
-
assert.equal(plan.installs.length, 1);
|
|
344
|
-
const cmd = plan.installs[0].installCommand.join(' ');
|
|
345
|
-
assert.match(cmd, /prpm install @prpm\/npm-trusted-publishing --as /);
|
|
346
|
-
});
|
|
347
129
|
test('materializeSkills emits a skill.sh install for a github#skill source', () => {
|
|
348
|
-
const plan = materializeSkills([
|
|
349
|
-
{
|
|
350
|
-
id: 'skill.sh/find-skills',
|
|
351
|
-
source: 'https://github.com/vercel-labs/skills#find-skills',
|
|
352
|
-
description: 'skill.sh discovery skill'
|
|
353
|
-
}
|
|
354
|
-
], 'claude');
|
|
130
|
+
const plan = materializeSkills([skillShSkill], 'claude');
|
|
355
131
|
assert.equal(plan.installs.length, 1);
|
|
356
132
|
const [install] = plan.installs;
|
|
357
133
|
assert.equal(install.sourceKind, 'skill.sh');
|
|
358
134
|
assert.equal(install.packageRef, 'https://github.com/vercel-labs/skills#find-skills');
|
|
359
|
-
assert.deepEqual([...install.installCommand], [
|
|
360
|
-
|
|
135
|
+
assert.deepEqual([...install.installCommand], [
|
|
136
|
+
'npx',
|
|
137
|
+
'-y',
|
|
138
|
+
'skills',
|
|
139
|
+
'add',
|
|
140
|
+
'https://github.com/vercel-labs/skills',
|
|
141
|
+
'--skill',
|
|
142
|
+
'find-skills',
|
|
143
|
+
'-y'
|
|
144
|
+
]);
|
|
361
145
|
assert.equal(install.installedDir, '.agents/skills/find-skills');
|
|
362
146
|
assert.equal(install.installedManifest, '.agents/skills/find-skills/SKILL.md');
|
|
363
|
-
// Cleanup should target every harness symlink + the universal dir, but
|
|
364
|
-
// never the lockfile itself.
|
|
365
147
|
assert.deepEqual([...install.cleanupPaths], [
|
|
366
148
|
'.agents/skills/find-skills',
|
|
367
149
|
'.claude/skills/find-skills',
|
|
@@ -435,171 +217,99 @@ test('materializeSkills rejects unsafe skill.sh skill names', () => {
|
|
|
435
217
|
}
|
|
436
218
|
], 'opencode'), /Unsupported skill source/);
|
|
437
219
|
});
|
|
438
|
-
test('prpm installs carry a harness-scoped cleanup path
|
|
439
|
-
const plan = materializeSkills([
|
|
440
|
-
{
|
|
441
|
-
id: 'prpm/npm-trusted-publishing',
|
|
442
|
-
source: '@prpm/npm-trusted-publishing',
|
|
443
|
-
description: 'bare ref form'
|
|
444
|
-
}
|
|
445
|
-
], 'codex');
|
|
220
|
+
test('prpm installs carry a harness-scoped cleanup path, not the lockfile', () => {
|
|
221
|
+
const plan = materializeSkills([prpmSkill], 'codex');
|
|
446
222
|
const [install] = plan.installs;
|
|
447
223
|
assert.deepEqual([...install.cleanupPaths], ['.agents/skills/npm-trusted-publishing']);
|
|
448
224
|
assert.ok(!install.cleanupPaths.includes('prpm.lock'));
|
|
449
225
|
});
|
|
450
|
-
test('
|
|
451
|
-
|
|
452
|
-
// the install step, which ran BEFORE the agent step and deleted skill files
|
|
453
|
-
// the agent needed to read. Cleanup now lives on a separate post-agent step
|
|
454
|
-
// and on install.cleanupCommandString for Mode B callers.
|
|
455
|
-
const context = usePersona('npm-provenance');
|
|
226
|
+
test('useSelection install command never embeds cleanup', () => {
|
|
227
|
+
const context = useSelection(syntheticSelection({ skills: [prpmSkill] }));
|
|
456
228
|
assert.doesNotMatch(context.install.commandString, /rm -rf/);
|
|
457
|
-
assert.match(context.install.commandString, /prpm install @prpm\/npm-trusted-publishing --as
|
|
229
|
+
assert.match(context.install.commandString, /prpm install @prpm\/npm-trusted-publishing --as codex/);
|
|
458
230
|
});
|
|
459
|
-
test('
|
|
460
|
-
const context =
|
|
231
|
+
test('useSelection exposes a post-run cleanupCommandString targeting skill artifacts', () => {
|
|
232
|
+
const context = useSelection(syntheticSelection({ skills: [prpmSkill] }));
|
|
461
233
|
assert.ok(Array.isArray(context.install.cleanupCommand));
|
|
462
234
|
assert.equal(context.install.cleanupCommand[0], 'sh');
|
|
463
235
|
assert.match(context.install.cleanupCommandString, /^rm -rf /);
|
|
464
236
|
assert.match(context.install.cleanupCommandString, /npm-trusted-publishing/);
|
|
465
|
-
// The provider lockfile must never be cleaned — repeat runs depend on it.
|
|
466
237
|
assert.doesNotMatch(context.install.cleanupCommandString, /prpm\.lock|skills-lock\.json/);
|
|
467
238
|
});
|
|
468
|
-
test('
|
|
469
|
-
const context =
|
|
239
|
+
test('useSelection cleanupCommandString chains paths from every install in the plan', () => {
|
|
240
|
+
const context = useSelection(syntheticSelection({ skills: [skillShSkill, prpmSkill] }));
|
|
470
241
|
const cleanup = context.install.cleanupCommandString;
|
|
471
|
-
// Both the skill.sh symlink set and the prpm per-harness dir should appear
|
|
472
|
-
// in a single rm -rf chain.
|
|
473
242
|
assert.match(cleanup, /^rm -rf /);
|
|
474
243
|
assert.match(cleanup, /find-skills/);
|
|
475
|
-
assert.match(cleanup, /
|
|
476
|
-
// Cover every skill.sh harness symlink, not just the universal dir.
|
|
244
|
+
assert.match(cleanup, /npm-trusted-publishing/);
|
|
477
245
|
assert.match(cleanup, /\.agents\/skills\/find-skills/);
|
|
478
246
|
assert.match(cleanup, /\.claude\/skills\/find-skills/);
|
|
479
247
|
assert.match(cleanup, /\.factory\/skills\/find-skills/);
|
|
480
248
|
assert.match(cleanup, /\.kiro\/skills\/find-skills/);
|
|
481
249
|
});
|
|
482
|
-
test('
|
|
483
|
-
const context =
|
|
250
|
+
test('useSelection cleanupCommandString is a shell no-op when the persona declares no skills', () => {
|
|
251
|
+
const context = useSelection(syntheticSelection());
|
|
484
252
|
assert.equal(context.install.cleanupCommandString, ':');
|
|
485
253
|
});
|
|
486
254
|
test('materializeSkills with installRoot stages claude skills under the stage dir', () => {
|
|
487
255
|
const installRoot = '/tmp/agent-workforce/sessions/test-run/claude/plugin';
|
|
488
|
-
const plan = materializeSkills([
|
|
489
|
-
{
|
|
490
|
-
id: 'prpm/npm-trusted-publishing',
|
|
491
|
-
source: '@prpm/npm-trusted-publishing',
|
|
492
|
-
description: 'bare ref form'
|
|
493
|
-
}
|
|
494
|
-
], 'claude', { installRoot });
|
|
256
|
+
const plan = materializeSkills([prpmSkill], 'claude', { installRoot });
|
|
495
257
|
assert.equal(plan.sessionInstallRoot, installRoot);
|
|
496
258
|
const [install] = plan.installs;
|
|
497
259
|
assert.equal(install.installedDir, `${installRoot}/.claude/skills/npm-trusted-publishing`);
|
|
498
260
|
assert.equal(install.installedManifest, `${installRoot}/.claude/skills/npm-trusted-publishing/SKILL.md`);
|
|
499
|
-
// Per-install command is self-contained: runs prpm inside the stage dir.
|
|
500
261
|
assert.equal(install.installCommand[0], 'sh');
|
|
501
262
|
assert.equal(install.installCommand[1], '-c');
|
|
502
263
|
const script = install.installCommand[2];
|
|
503
264
|
assert.match(script, /^cd /);
|
|
504
265
|
assert.match(script, /agent-workforce\/sessions\/test-run\/claude\/plugin/);
|
|
505
266
|
assert.match(script, /npx -y prpm install @prpm\/npm-trusted-publishing --as claude/);
|
|
506
|
-
// Per-skill cleanupPaths is empty; cleanup lives at the plan level.
|
|
507
267
|
assert.deepEqual([...install.cleanupPaths], []);
|
|
508
268
|
});
|
|
509
269
|
test('materializeSkills rejects installRoot for non-claude harnesses', () => {
|
|
510
|
-
assert.throws(() => materializeSkills([
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
source: '@prpm/x',
|
|
514
|
-
description: 'x'
|
|
515
|
-
}
|
|
516
|
-
], 'codex', { installRoot: '/tmp/agent-workforce/sessions/abc/claude/plugin' }), /installRoot is only supported for the claude harness/);
|
|
270
|
+
assert.throws(() => materializeSkills([prpmSkill], 'codex', {
|
|
271
|
+
installRoot: '/tmp/agent-workforce/sessions/abc/claude/plugin'
|
|
272
|
+
}), /installRoot is only supported for the claude harness/);
|
|
517
273
|
});
|
|
518
|
-
test('useSelection with installRoot emits scaffold
|
|
274
|
+
test('useSelection with installRoot emits scaffold plus chained prpm', () => {
|
|
519
275
|
const installRoot = '/tmp/agent-workforce/sessions/scaffold-test/claude/plugin';
|
|
520
|
-
const selection =
|
|
521
|
-
// Force harness=claude so the installRoot path is exercised regardless of
|
|
522
|
-
// the persona's default tier harness.
|
|
276
|
+
const selection = syntheticSelection({ skills: [prpmSkill] });
|
|
523
277
|
const context = useSelection(selection, { harness: 'claude', installRoot });
|
|
524
278
|
assert.equal(context.install.plan.sessionInstallRoot, installRoot);
|
|
525
279
|
const cmd = context.install.commandString;
|
|
526
|
-
// Scaffold: the three mkdir/ln/printf steps go first.
|
|
527
280
|
assert.match(cmd, /^mkdir -p /);
|
|
528
281
|
assert.match(cmd, /\.claude-plugin/);
|
|
529
282
|
assert.match(cmd, /ln -sfn \.claude\/skills /);
|
|
530
283
|
assert.match(cmd, /printf '%s' /);
|
|
531
|
-
// Then a single cd into the stage dir, then the prpm call.
|
|
532
284
|
assert.match(cmd, / && cd '?\/tmp\/agent-workforce\/sessions\/scaffold-test\/claude\/plugin'? && /);
|
|
533
285
|
assert.match(cmd, /npx -y prpm install @prpm\/npm-trusted-publishing --as claude/);
|
|
534
286
|
});
|
|
535
287
|
test('useSelection with installRoot collapses cleanup to a single rm -rf of the stage dir', () => {
|
|
536
288
|
const installRoot = '/tmp/agent-workforce/sessions/cleanup-test/claude/plugin';
|
|
537
|
-
const
|
|
538
|
-
const context = useSelection(selection, { harness: 'claude', installRoot });
|
|
539
|
-
// shellEscape leaves paths made of [A-Za-z0-9_./:@%+=,-] unquoted.
|
|
289
|
+
const context = useSelection(syntheticSelection({ skills: [prpmSkill] }), { harness: 'claude', installRoot });
|
|
540
290
|
assert.equal(context.install.cleanupCommandString, `rm -rf /tmp/agent-workforce/sessions/cleanup-test/claude/plugin`);
|
|
541
291
|
});
|
|
542
|
-
test('materializeSkills with installRoot
|
|
292
|
+
test('materializeSkills with installRoot and no skills still reports the sessionInstallRoot', () => {
|
|
543
293
|
const installRoot = '/tmp/agent-workforce/sessions/empty/claude/plugin';
|
|
544
294
|
const plan = materializeSkills([], 'claude', { installRoot });
|
|
545
295
|
assert.equal(plan.sessionInstallRoot, installRoot);
|
|
546
296
|
assert.equal(plan.installs.length, 0);
|
|
547
297
|
});
|
|
548
|
-
test('useSelection with installRoot
|
|
549
|
-
// Skill-less claude personas (e.g. posthog) still need the stage dir to
|
|
550
|
-
// exist so `claude --plugin-dir <installRoot>` finds a valid plugin.
|
|
298
|
+
test('useSelection with installRoot and no skills emits scaffold so plugin dir exists', () => {
|
|
551
299
|
const installRoot = '/tmp/agent-workforce/sessions/empty-scaffold/claude/plugin';
|
|
552
|
-
const
|
|
553
|
-
const context = useSelection(selection, { harness: 'claude', installRoot });
|
|
300
|
+
const context = useSelection(syntheticSelection(), { harness: 'claude', installRoot });
|
|
554
301
|
assert.equal(context.install.plan.sessionInstallRoot, installRoot);
|
|
555
302
|
assert.equal(context.install.plan.installs.length, 0);
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
assert.notEqual(cmd, ':');
|
|
559
|
-
assert.match(cmd, /^mkdir -p /);
|
|
560
|
-
assert.match(cmd, /\.claude-plugin/);
|
|
561
|
-
assert.match(cmd, /ln -sfn \.claude\/skills /);
|
|
562
|
-
assert.match(cmd, /printf '%s' /);
|
|
563
|
-
// Cleanup always drops the stage dir in session mode, even with zero skills.
|
|
303
|
+
assert.notEqual(context.install.commandString, ':');
|
|
304
|
+
assert.match(context.install.commandString, /^mkdir -p /);
|
|
564
305
|
assert.equal(context.install.cleanupCommandString, `rm -rf ${installRoot}`);
|
|
565
306
|
});
|
|
566
|
-
test('resolves capability-discovery persona carrying both skill.sh and prpm skills', () => {
|
|
567
|
-
const selection = resolvePersona('capability-discovery');
|
|
568
|
-
assert.equal(selection.personaId, 'capability-discoverer');
|
|
569
|
-
assert.equal(selection.tier, 'best-value');
|
|
570
|
-
assert.equal(selection.skills.length, 2);
|
|
571
|
-
const byId = new Map(selection.skills.map((s) => [s.id, s]));
|
|
572
|
-
const skillSh = byId.get('skill.sh/find-skills');
|
|
573
|
-
assert.ok(skillSh, 'missing skill.sh/find-skills skill');
|
|
574
|
-
assert.equal(skillSh.source, 'https://github.com/vercel-labs/skills#find-skills');
|
|
575
|
-
const prpm = byId.get('prpm/self-improving');
|
|
576
|
-
assert.ok(prpm, 'missing prpm/self-improving skill');
|
|
577
|
-
assert.match(prpm.source, /prpm\.dev\/packages\/@prpm\/self-improving/);
|
|
578
|
-
});
|
|
579
|
-
test('materializeSkillsFor capability-discovery plans both installs under one shell chain with cleanup', () => {
|
|
580
|
-
const selection = resolvePersona('capability-discovery');
|
|
581
|
-
const plan = materializeSkillsFor(selection);
|
|
582
|
-
assert.equal(plan.installs.length, 2);
|
|
583
|
-
const byKind = new Map(plan.installs.map((i) => [i.sourceKind, i]));
|
|
584
|
-
const skillShInstall = byKind.get('skill.sh');
|
|
585
|
-
const prpmInstall = byKind.get('prpm');
|
|
586
|
-
assert.ok(skillShInstall, 'missing skill.sh install');
|
|
587
|
-
assert.ok(prpmInstall, 'missing prpm install');
|
|
588
|
-
assert.deepEqual([...skillShInstall.installCommand], ['npx', '-y', 'skills', 'add', 'https://github.com/vercel-labs/skills', '--skill', 'find-skills', '-y']);
|
|
589
|
-
assert.equal(prpmInstall.packageRef, '@prpm/self-improving');
|
|
590
|
-
const context = usePersona('capability-discovery');
|
|
591
|
-
const cmd = context.install.commandString;
|
|
592
|
-
// Both installs should be chained back-to-back with `&&`, with NO inline
|
|
593
|
-
// cleanup — cleanup lives on a separate post-agent step.
|
|
594
|
-
assert.match(cmd, /skills add https:\/\/github\.com\/vercel-labs\/skills --skill find-skills -y && npx -y prpm install @prpm\/self-improving/);
|
|
595
|
-
assert.doesNotMatch(cmd, /rm -rf/);
|
|
596
|
-
});
|
|
597
307
|
test('materializeSkills rejects unknown skill sources', () => {
|
|
598
308
|
assert.throws(() => materializeSkills([
|
|
599
309
|
{
|
|
600
310
|
id: 'x',
|
|
601
311
|
source: 'https://example.com/random',
|
|
602
|
-
description: 'not a
|
|
312
|
+
description: 'not a supported source'
|
|
603
313
|
}
|
|
604
314
|
], 'claude'), /Unsupported skill source/);
|
|
605
315
|
});
|
|
@@ -608,40 +318,20 @@ test('materializeSkills handles personas with no skills', () => {
|
|
|
608
318
|
assert.equal(plan.installs.length, 0);
|
|
609
319
|
});
|
|
610
320
|
test('usePersona combines selection and grouped install metadata into a frozen context', () => {
|
|
611
|
-
const context = usePersona('
|
|
612
|
-
const selection = resolvePersona('
|
|
321
|
+
const context = usePersona('persona-authoring');
|
|
322
|
+
const selection = resolvePersona('persona-authoring');
|
|
613
323
|
const plan = materializeSkillsFor(selection);
|
|
614
324
|
assert.deepEqual(context.selection, selection);
|
|
615
325
|
assert.deepEqual(context.install.plan, plan);
|
|
616
326
|
assert.equal(context.install.command[0], 'sh');
|
|
617
|
-
assert.match(context.install.commandString, /
|
|
327
|
+
assert.match(context.install.commandString, /skills add/);
|
|
618
328
|
assert.ok(Object.isFrozen(context));
|
|
619
329
|
assert.ok(Object.isFrozen(context.selection));
|
|
620
330
|
assert.ok(Object.isFrozen(context.install));
|
|
621
331
|
assert.ok(Object.isFrozen(context.install.plan));
|
|
622
332
|
assert.ok(Object.isFrozen(context.install.command));
|
|
623
333
|
});
|
|
624
|
-
function syntheticSpec(over = {}) {
|
|
625
|
-
const baseRuntime = {
|
|
626
|
-
harness: 'claude',
|
|
627
|
-
model: 'claude-3-5-sonnet',
|
|
628
|
-
systemPrompt: 'base',
|
|
629
|
-
harnessSettings: { reasoning: 'medium', timeoutSeconds: 300 }
|
|
630
|
-
};
|
|
631
|
-
return {
|
|
632
|
-
id: 's',
|
|
633
|
-
intent: 'documentation',
|
|
634
|
-
tags: ['documentation'],
|
|
635
|
-
description: 'd',
|
|
636
|
-
skills: [],
|
|
637
|
-
tiers: { best: baseRuntime, 'best-value': baseRuntime, minimum: baseRuntime },
|
|
638
|
-
...over
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
334
|
test('resolveSidecar: tier path override drops top-level inlined content for the same channel', () => {
|
|
642
|
-
// Regression for channel mixing: a tier-level claudeMd MUST own the
|
|
643
|
-
// channel and exclude top-level claudeMdContent, otherwise downstream
|
|
644
|
-
// selection (which prefers Content) silently discards the override.
|
|
645
335
|
const spec = syntheticSpec({
|
|
646
336
|
claudeMdContent: '# top-level inlined\n',
|
|
647
337
|
claudeMdMode: 'overwrite',
|
|
@@ -657,11 +347,9 @@ test('resolveSidecar: tier path override drops top-level inlined content for the
|
|
|
657
347
|
const resolved = resolveSidecar(spec, 'best');
|
|
658
348
|
assert.equal(resolved.claudeMd, '/abs/persona.md');
|
|
659
349
|
assert.equal(resolved.claudeMdContent, undefined);
|
|
660
|
-
// Mode still falls back to top-level even though path/content didn't.
|
|
661
350
|
assert.equal(resolved.claudeMdMode, 'overwrite');
|
|
662
351
|
});
|
|
663
352
|
test('resolveSidecar: mode cascades independently of path', () => {
|
|
664
|
-
// Top-level claudeMdMode overrides default, tier inherits the path.
|
|
665
353
|
const spec = syntheticSpec({
|
|
666
354
|
claudeMd: '/abs/top.md',
|
|
667
355
|
claudeMdMode: 'extend'
|
|
@@ -670,16 +358,13 @@ test('resolveSidecar: mode cascades independently of path', () => {
|
|
|
670
358
|
assert.equal(resolved.claudeMd, '/abs/top.md');
|
|
671
359
|
assert.equal(resolved.claudeMdMode, 'extend');
|
|
672
360
|
});
|
|
673
|
-
test('resolvePersona populates sidecar selection fields from the catalog', () => {
|
|
674
|
-
|
|
675
|
-
// has no sidecar fields — but the helper must at least never throw and
|
|
676
|
-
// must omit the optional fields cleanly. This is the contract that lets
|
|
677
|
-
// a future built-in with claudeMd flow through usePersona without a
|
|
678
|
-
// separate resolveSidecar call.
|
|
679
|
-
const sel = resolvePersona('documentation');
|
|
361
|
+
test('resolvePersona populates sidecar selection fields from the internal catalog', () => {
|
|
362
|
+
const sel = resolvePersona('persona-authoring');
|
|
680
363
|
assert.equal(sel.claudeMd, undefined);
|
|
681
364
|
assert.equal(sel.claudeMdContent, undefined);
|
|
682
365
|
assert.equal(sel.claudeMdMode, undefined);
|
|
683
366
|
assert.equal(sel.agentsMd, undefined);
|
|
367
|
+
assert.match(sel.agentsMdContent ?? '', /Persona author/);
|
|
368
|
+
assert.equal(sel.agentsMdMode, 'overwrite');
|
|
684
369
|
});
|
|
685
370
|
//# sourceMappingURL=index.test.js.map
|