@agentworkforce/cli 2.1.4 → 3.0.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/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +186 -236
- package/dist/cli.js.map +1 -1
- package/dist/cli.test.js +60 -79
- package/dist/cli.test.js.map +1 -1
- package/dist/deploy-command.d.ts +10 -0
- package/dist/deploy-command.d.ts.map +1 -0
- package/dist/deploy-command.js +164 -0
- package/dist/deploy-command.js.map +1 -0
- package/dist/launch-metadata.d.ts +2 -2
- package/dist/launch-metadata.d.ts.map +1 -1
- package/dist/launch-metadata.js +3 -4
- package/dist/launch-metadata.js.map +1 -1
- package/dist/launch-metadata.test.js +5 -32
- package/dist/launch-metadata.test.js.map +1 -1
- package/dist/local-personas.d.ts +8 -10
- package/dist/local-personas.d.ts.map +1 -1
- package/dist/local-personas.js +63 -163
- package/dist/local-personas.js.map +1 -1
- package/dist/local-personas.test.js +59 -332
- package/dist/local-personas.test.js.map +1 -1
- package/dist/persona-install.d.ts.map +1 -1
- package/dist/persona-install.js +4 -25
- package/dist/persona-install.js.map +1 -1
- package/dist/persona-install.test.js +4 -11
- package/dist/persona-install.test.js.map +1 -1
- package/package.json +4 -3
|
@@ -72,8 +72,8 @@ test('implicit same-id extends: cwd file with id=persona-maker inherits from lib
|
|
|
72
72
|
const spec = loaded.byId.get('persona-maker');
|
|
73
73
|
assert.ok(spec);
|
|
74
74
|
assert.equal(loaded.sources.get('persona-maker'), 'cwd');
|
|
75
|
-
// Library fields still flow through (
|
|
76
|
-
assert.equal(spec.
|
|
75
|
+
// Library fields still flow through (runtime, description, inputs).
|
|
76
|
+
assert.equal(spec.harness, 'opencode');
|
|
77
77
|
assert.equal(spec.inputs?.CREATE_MODE.default, 'local');
|
|
78
78
|
assert.equal(spec.env?.POSTHOG_API_KEY, '$POSTHOG_API_KEY');
|
|
79
79
|
});
|
|
@@ -148,26 +148,21 @@ test('cwd workforce config file is not scanned as a persona', () => {
|
|
|
148
148
|
assert.ok(loaded.byId.has('ph'));
|
|
149
149
|
});
|
|
150
150
|
});
|
|
151
|
-
test('
|
|
151
|
+
test('top-level runtime fields override the inherited base', () => {
|
|
152
152
|
withLayers(({ cwd, homeDir }) => {
|
|
153
153
|
writeJson(join(homeDir, 'ph.json'), {
|
|
154
154
|
id: 'ph',
|
|
155
155
|
extends: 'persona-maker',
|
|
156
|
-
|
|
157
|
-
best: { model: 'claude-sonnet-4-6' }
|
|
158
|
-
}
|
|
156
|
+
model: 'claude-sonnet-4-6'
|
|
159
157
|
});
|
|
160
158
|
const loaded = loadLocalPersonas({ cwd, homeDir });
|
|
161
159
|
const spec = loaded.byId.get('ph');
|
|
162
|
-
assert.equal(spec?.
|
|
163
|
-
// systemPrompt is inherited
|
|
164
|
-
assert.equal(spec?.
|
|
165
|
-
// Other tiers untouched.
|
|
166
|
-
assert.equal(spec?.tiers['best-value'].model, 'opencode/gpt-5-nano');
|
|
167
|
-
assert.equal(spec?.tiers.minimum.model, 'opencode/minimax-m2.5-free');
|
|
160
|
+
assert.equal(spec?.model, 'claude-sonnet-4-6');
|
|
161
|
+
// systemPrompt is inherited when not overridden.
|
|
162
|
+
assert.equal(spec?.systemPrompt, '$TASK_DESCRIPTION');
|
|
168
163
|
});
|
|
169
164
|
});
|
|
170
|
-
test('top-level systemPrompt replaces
|
|
165
|
+
test('top-level systemPrompt replaces the inherited prompt', () => {
|
|
171
166
|
withLayers(({ cwd, homeDir }) => {
|
|
172
167
|
writeJson(join(homeDir, 'ph.json'), {
|
|
173
168
|
id: 'ph',
|
|
@@ -176,9 +171,7 @@ test('top-level systemPrompt replaces prompt across all inherited tiers', () =>
|
|
|
176
171
|
});
|
|
177
172
|
const loaded = loadLocalPersonas({ cwd, homeDir });
|
|
178
173
|
const spec = loaded.byId.get('ph');
|
|
179
|
-
assert.equal(spec?.
|
|
180
|
-
assert.equal(spec?.tiers['best-value'].systemPrompt, 'You answer only yes or no.');
|
|
181
|
-
assert.equal(spec?.tiers.minimum.systemPrompt, 'You answer only yes or no.');
|
|
174
|
+
assert.equal(spec?.systemPrompt, 'You answer only yes or no.');
|
|
182
175
|
});
|
|
183
176
|
});
|
|
184
177
|
test('warns when extends base does not exist in lower layers', () => {
|
|
@@ -327,21 +320,18 @@ test('codex harness settings merge across local persona layers', () => {
|
|
|
327
320
|
writeJson(join(homeDir, 'planner.json'), {
|
|
328
321
|
id: 'planner',
|
|
329
322
|
extends: 'persona-maker',
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
workspaceWriteNetworkAccess: true,
|
|
336
|
-
webSearch: true
|
|
337
|
-
}
|
|
338
|
-
}
|
|
323
|
+
harnessSettings: {
|
|
324
|
+
sandboxMode: 'workspace-write',
|
|
325
|
+
approvalPolicy: 'on-request',
|
|
326
|
+
workspaceWriteNetworkAccess: true,
|
|
327
|
+
webSearch: true
|
|
339
328
|
}
|
|
340
329
|
});
|
|
341
330
|
const loaded = loadLocalPersonas({ cwd, homeDir });
|
|
342
331
|
assert.deepEqual(loaded.warnings, []);
|
|
343
|
-
const settings = loaded.byId.get('planner')?.
|
|
344
|
-
|
|
332
|
+
const settings = loaded.byId.get('planner')?.harnessSettings;
|
|
333
|
+
// Inherited reasoning passes through; sandbox+approval+network+webSearch overlay.
|
|
334
|
+
assert.ok(settings);
|
|
345
335
|
assert.equal(settings?.sandboxMode, 'workspace-write');
|
|
346
336
|
assert.equal(settings?.approvalPolicy, 'on-request');
|
|
347
337
|
assert.equal(settings?.workspaceWriteNetworkAccess, true);
|
|
@@ -412,26 +402,10 @@ test('inputs are preserved on standalone local personas', () => {
|
|
|
412
402
|
default: '/tmp/reviews'
|
|
413
403
|
}
|
|
414
404
|
},
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
systemPrompt: 'Write to $TARGET_DIR.',
|
|
420
|
-
harnessSettings: { reasoning: 'high', timeoutSeconds: 30 }
|
|
421
|
-
},
|
|
422
|
-
'best-value': {
|
|
423
|
-
harness: 'opencode',
|
|
424
|
-
model: 'opencode/gpt-5-nano',
|
|
425
|
-
systemPrompt: 'Write to $TARGET_DIR.',
|
|
426
|
-
harnessSettings: { reasoning: 'medium', timeoutSeconds: 30 }
|
|
427
|
-
},
|
|
428
|
-
minimum: {
|
|
429
|
-
harness: 'opencode',
|
|
430
|
-
model: 'opencode/minimax-m2.5-free',
|
|
431
|
-
systemPrompt: 'Write to $TARGET_DIR.',
|
|
432
|
-
harnessSettings: { reasoning: 'low', timeoutSeconds: 30 }
|
|
433
|
-
}
|
|
434
|
-
}
|
|
405
|
+
harness: 'codex',
|
|
406
|
+
model: 'openai-codex/gpt-5.3-codex',
|
|
407
|
+
systemPrompt: 'Write to $TARGET_DIR.',
|
|
408
|
+
harnessSettings: { reasoning: 'high', timeoutSeconds: 30 }
|
|
435
409
|
});
|
|
436
410
|
const loaded = loadLocalPersonas({ cwd, homeDir });
|
|
437
411
|
assert.deepEqual(loaded.warnings, []);
|
|
@@ -446,26 +420,10 @@ test('standalone local personas accept arbitrary intent names', () => {
|
|
|
446
420
|
intent: 'nextjs-web-steward',
|
|
447
421
|
tags: ['implementation'],
|
|
448
422
|
description: 'Stewards Next.js web surfaces.',
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
systemPrompt: 'Implement Next.js UI work carefully.',
|
|
454
|
-
harnessSettings: { reasoning: 'high', timeoutSeconds: 30 }
|
|
455
|
-
},
|
|
456
|
-
'best-value': {
|
|
457
|
-
harness: 'opencode',
|
|
458
|
-
model: 'opencode/gpt-5-nano',
|
|
459
|
-
systemPrompt: 'Implement Next.js UI work carefully.',
|
|
460
|
-
harnessSettings: { reasoning: 'medium', timeoutSeconds: 30 }
|
|
461
|
-
},
|
|
462
|
-
minimum: {
|
|
463
|
-
harness: 'opencode',
|
|
464
|
-
model: 'opencode/minimax-m2.5-free',
|
|
465
|
-
systemPrompt: 'Implement Next.js UI work carefully.',
|
|
466
|
-
harnessSettings: { reasoning: 'low', timeoutSeconds: 30 }
|
|
467
|
-
}
|
|
468
|
-
}
|
|
423
|
+
harness: 'codex',
|
|
424
|
+
model: 'openai-codex/gpt-5.3-codex',
|
|
425
|
+
systemPrompt: 'Implement Next.js UI work carefully.',
|
|
426
|
+
harnessSettings: { reasoning: 'high', timeoutSeconds: 30 }
|
|
469
427
|
});
|
|
470
428
|
const loaded = loadLocalPersonas({ cwd, homeDir });
|
|
471
429
|
assert.deepEqual(loaded.warnings, []);
|
|
@@ -473,111 +431,18 @@ test('standalone local personas accept arbitrary intent names', () => {
|
|
|
473
431
|
assert.equal(spec?.intent, 'nextjs-web-steward');
|
|
474
432
|
});
|
|
475
433
|
});
|
|
476
|
-
test('
|
|
477
|
-
withLayers(({ cwd, homeDir }) => {
|
|
478
|
-
writeJson(join(homeDir, 'nextjs-web-steward.json'), {
|
|
479
|
-
id: 'nextjs-web-steward',
|
|
480
|
-
intent: 'nextjs-web-steward',
|
|
481
|
-
tags: ['implementation'],
|
|
482
|
-
description: 'Stewards Next.js web surfaces.',
|
|
483
|
-
defaultTier: 'best',
|
|
484
|
-
tiers: {
|
|
485
|
-
best: {
|
|
486
|
-
harness: 'codex',
|
|
487
|
-
model: 'openai-codex/gpt-5.3-codex',
|
|
488
|
-
systemPrompt: 'Implement Next.js UI work carefully.',
|
|
489
|
-
harnessSettings: { reasoning: 'high', timeoutSeconds: 30 }
|
|
490
|
-
},
|
|
491
|
-
'best-value': {
|
|
492
|
-
harness: 'opencode',
|
|
493
|
-
model: 'opencode/gpt-5-nano',
|
|
494
|
-
systemPrompt: 'Implement Next.js UI work carefully.',
|
|
495
|
-
harnessSettings: { reasoning: 'medium', timeoutSeconds: 30 }
|
|
496
|
-
},
|
|
497
|
-
minimum: {
|
|
498
|
-
harness: 'opencode',
|
|
499
|
-
model: 'opencode/minimax-m2.5-free',
|
|
500
|
-
systemPrompt: 'Implement Next.js UI work carefully.',
|
|
501
|
-
harnessSettings: { reasoning: 'low', timeoutSeconds: 30 }
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
});
|
|
505
|
-
const loaded = loadLocalPersonas({ cwd, homeDir });
|
|
506
|
-
assert.deepEqual(loaded.warnings, []);
|
|
507
|
-
const spec = loaded.byId.get('nextjs-web-steward');
|
|
508
|
-
assert.equal(spec?.defaultTier, 'best');
|
|
509
|
-
});
|
|
510
|
-
});
|
|
511
|
-
test('rejects an invalid defaultTier value with a parse warning', () => {
|
|
434
|
+
test('rejects an override that still declares a tiers field', () => {
|
|
512
435
|
withLayers(({ cwd, homeDir }) => {
|
|
513
|
-
writeJson(join(homeDir, '
|
|
514
|
-
id: '
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
description: 'Has an invalid defaultTier.',
|
|
518
|
-
defaultTier: 'gold',
|
|
519
|
-
tiers: {
|
|
520
|
-
best: {
|
|
521
|
-
harness: 'codex',
|
|
522
|
-
model: 'm',
|
|
523
|
-
systemPrompt: 'p',
|
|
524
|
-
harnessSettings: { reasoning: 'high', timeoutSeconds: 30 }
|
|
525
|
-
},
|
|
526
|
-
'best-value': {
|
|
527
|
-
harness: 'opencode',
|
|
528
|
-
model: 'm',
|
|
529
|
-
systemPrompt: 'p',
|
|
530
|
-
harnessSettings: { reasoning: 'medium', timeoutSeconds: 30 }
|
|
531
|
-
},
|
|
532
|
-
minimum: {
|
|
533
|
-
harness: 'opencode',
|
|
534
|
-
model: 'm',
|
|
535
|
-
systemPrompt: 'p',
|
|
536
|
-
harnessSettings: { reasoning: 'low', timeoutSeconds: 30 }
|
|
537
|
-
}
|
|
538
|
-
}
|
|
436
|
+
writeJson(join(homeDir, 'legacy.json'), {
|
|
437
|
+
id: 'legacy',
|
|
438
|
+
extends: 'persona-maker',
|
|
439
|
+
tiers: { best: { model: 'x' } }
|
|
539
440
|
});
|
|
540
441
|
const loaded = loadLocalPersonas({ cwd, homeDir });
|
|
541
|
-
assert.equal(loaded.byId.has('
|
|
542
|
-
assert.match(loaded.warnings.join('\n'), /
|
|
442
|
+
assert.equal(loaded.byId.has('legacy'), false);
|
|
443
|
+
assert.match(loaded.warnings.join('\n'), /tiers is no longer supported/);
|
|
543
444
|
});
|
|
544
445
|
});
|
|
545
|
-
test('overlay defaultTier replaces the base value during merge', () => {
|
|
546
|
-
const base = {
|
|
547
|
-
id: 'b',
|
|
548
|
-
intent: 'review',
|
|
549
|
-
tags: ['review'],
|
|
550
|
-
description: 'Base persona with a defaultTier.',
|
|
551
|
-
skills: [],
|
|
552
|
-
defaultTier: 'minimum',
|
|
553
|
-
tiers: {
|
|
554
|
-
best: {
|
|
555
|
-
harness: 'codex',
|
|
556
|
-
model: 'm',
|
|
557
|
-
systemPrompt: 'p',
|
|
558
|
-
harnessSettings: { reasoning: 'high', timeoutSeconds: 30 }
|
|
559
|
-
},
|
|
560
|
-
'best-value': {
|
|
561
|
-
harness: 'opencode',
|
|
562
|
-
model: 'm',
|
|
563
|
-
systemPrompt: 'p',
|
|
564
|
-
harnessSettings: { reasoning: 'medium', timeoutSeconds: 30 }
|
|
565
|
-
},
|
|
566
|
-
minimum: {
|
|
567
|
-
harness: 'opencode',
|
|
568
|
-
model: 'm',
|
|
569
|
-
systemPrompt: 'p',
|
|
570
|
-
harnessSettings: { reasoning: 'low', timeoutSeconds: 30 }
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
};
|
|
574
|
-
const override = { id: 'b', defaultTier: 'best' };
|
|
575
|
-
const merged = __mergeOverrideForTests(base, override);
|
|
576
|
-
assert.equal(merged.defaultTier, 'best');
|
|
577
|
-
const inheritOverride = { id: 'b' };
|
|
578
|
-
const inherited = __mergeOverrideForTests(base, inheritOverride);
|
|
579
|
-
assert.equal(inherited.defaultTier, 'minimum');
|
|
580
|
-
});
|
|
581
446
|
test('standalone local personas can use inlined AGENTS content as prompt fallback', () => {
|
|
582
447
|
withLayers(({ cwd, homeDir }) => {
|
|
583
448
|
writeJson(join(homeDir, 'nextjs-web-steward.json'), {
|
|
@@ -587,73 +452,19 @@ test('standalone local personas can use inlined AGENTS content as prompt fallbac
|
|
|
587
452
|
description: 'Stewards Next.js web surfaces.',
|
|
588
453
|
agentsMd: 'AGENTS.md',
|
|
589
454
|
agentsMdContent: '# Next.js Web Steward\n\nOwn implementation work in web/.\n',
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
systemPrompt: '',
|
|
595
|
-
harnessSettings: { reasoning: 'high', timeoutSeconds: 30 }
|
|
596
|
-
},
|
|
597
|
-
'best-value': {
|
|
598
|
-
harness: 'opencode',
|
|
599
|
-
model: 'opencode/gpt-5-nano',
|
|
600
|
-
systemPrompt: '',
|
|
601
|
-
harnessSettings: { reasoning: 'medium', timeoutSeconds: 30 }
|
|
602
|
-
},
|
|
603
|
-
minimum: {
|
|
604
|
-
harness: 'opencode',
|
|
605
|
-
model: 'opencode/minimax-m2.5-free',
|
|
606
|
-
systemPrompt: '',
|
|
607
|
-
harnessSettings: { reasoning: 'low', timeoutSeconds: 30 }
|
|
608
|
-
}
|
|
609
|
-
}
|
|
455
|
+
harness: 'codex',
|
|
456
|
+
model: 'openai-codex/gpt-5.3-codex',
|
|
457
|
+
systemPrompt: '',
|
|
458
|
+
harnessSettings: { reasoning: 'high', timeoutSeconds: 30 }
|
|
610
459
|
});
|
|
611
460
|
const loaded = loadLocalPersonas({ cwd, homeDir });
|
|
612
461
|
assert.deepEqual(loaded.warnings, []);
|
|
613
462
|
const spec = loaded.byId.get('nextjs-web-steward');
|
|
614
|
-
assert.match(spec?.
|
|
463
|
+
assert.match(spec?.systemPrompt ?? '', /Next\.js Web Steward/);
|
|
615
464
|
assert.match(spec?.agentsMdContent ?? '', /implementation work/);
|
|
616
465
|
assert.equal(spec?.agentsMd, undefined);
|
|
617
466
|
});
|
|
618
467
|
});
|
|
619
|
-
test('standalone local personas can use tier-level inlined AGENTS content as prompt fallback', () => {
|
|
620
|
-
withLayers(({ cwd, homeDir }) => {
|
|
621
|
-
writeJson(join(homeDir, 'nextjs-web-steward.json'), {
|
|
622
|
-
id: 'nextjs-web-steward',
|
|
623
|
-
intent: 'nextjs-web-stewardship',
|
|
624
|
-
tags: ['implementation'],
|
|
625
|
-
description: 'Stewards Next.js web surfaces.',
|
|
626
|
-
agentsMdContent: '# Default steward prompt\n',
|
|
627
|
-
tiers: {
|
|
628
|
-
best: {
|
|
629
|
-
harness: 'codex',
|
|
630
|
-
model: 'openai-codex/gpt-5.3-codex',
|
|
631
|
-
systemPrompt: '',
|
|
632
|
-
agentsMdContent: '# Best steward prompt\n',
|
|
633
|
-
harnessSettings: { reasoning: 'high', timeoutSeconds: 30 }
|
|
634
|
-
},
|
|
635
|
-
'best-value': {
|
|
636
|
-
harness: 'opencode',
|
|
637
|
-
model: 'opencode/gpt-5-nano',
|
|
638
|
-
systemPrompt: '',
|
|
639
|
-
harnessSettings: { reasoning: 'medium', timeoutSeconds: 30 }
|
|
640
|
-
},
|
|
641
|
-
minimum: {
|
|
642
|
-
harness: 'opencode',
|
|
643
|
-
model: 'opencode/minimax-m2.5-free',
|
|
644
|
-
systemPrompt: '',
|
|
645
|
-
harnessSettings: { reasoning: 'low', timeoutSeconds: 30 }
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
});
|
|
649
|
-
const loaded = loadLocalPersonas({ cwd, homeDir });
|
|
650
|
-
assert.deepEqual(loaded.warnings, []);
|
|
651
|
-
const spec = loaded.byId.get('nextjs-web-steward');
|
|
652
|
-
assert.match(spec?.tiers.best.systemPrompt ?? '', /Best steward prompt/);
|
|
653
|
-
assert.match(spec?.tiers.best.agentsMdContent ?? '', /Best steward prompt/);
|
|
654
|
-
assert.match(spec?.tiers.minimum.systemPrompt ?? '', /Default steward prompt/);
|
|
655
|
-
});
|
|
656
|
-
});
|
|
657
468
|
test('rejects whitespace-only inlined sidecar content', () => {
|
|
658
469
|
withLayers(({ cwd, homeDir }) => {
|
|
659
470
|
writeJson(join(homeDir, 'blank-top-level.json'), {
|
|
@@ -662,59 +473,14 @@ test('rejects whitespace-only inlined sidecar content', () => {
|
|
|
662
473
|
tags: ['implementation'],
|
|
663
474
|
description: 'Invalid blank sidecar content.',
|
|
664
475
|
agentsMdContent: ' ',
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
systemPrompt: 'Prompt.',
|
|
670
|
-
harnessSettings: { reasoning: 'high', timeoutSeconds: 30 }
|
|
671
|
-
},
|
|
672
|
-
'best-value': {
|
|
673
|
-
harness: 'opencode',
|
|
674
|
-
model: 'opencode/gpt-5-nano',
|
|
675
|
-
systemPrompt: 'Prompt.',
|
|
676
|
-
harnessSettings: { reasoning: 'medium', timeoutSeconds: 30 }
|
|
677
|
-
},
|
|
678
|
-
minimum: {
|
|
679
|
-
harness: 'opencode',
|
|
680
|
-
model: 'opencode/minimax-m2.5-free',
|
|
681
|
-
systemPrompt: 'Prompt.',
|
|
682
|
-
harnessSettings: { reasoning: 'low', timeoutSeconds: 30 }
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
});
|
|
686
|
-
writeJson(join(homeDir, 'blank-tier.json'), {
|
|
687
|
-
id: 'blank-tier',
|
|
688
|
-
intent: 'blank-tier',
|
|
689
|
-
tags: ['implementation'],
|
|
690
|
-
description: 'Invalid blank tier sidecar content.',
|
|
691
|
-
tiers: {
|
|
692
|
-
best: {
|
|
693
|
-
harness: 'codex',
|
|
694
|
-
model: 'openai-codex/gpt-5.3-codex',
|
|
695
|
-
systemPrompt: 'Prompt.',
|
|
696
|
-
agentsMdContent: ' ',
|
|
697
|
-
harnessSettings: { reasoning: 'high', timeoutSeconds: 30 }
|
|
698
|
-
},
|
|
699
|
-
'best-value': {
|
|
700
|
-
harness: 'opencode',
|
|
701
|
-
model: 'opencode/gpt-5-nano',
|
|
702
|
-
systemPrompt: 'Prompt.',
|
|
703
|
-
harnessSettings: { reasoning: 'medium', timeoutSeconds: 30 }
|
|
704
|
-
},
|
|
705
|
-
minimum: {
|
|
706
|
-
harness: 'opencode',
|
|
707
|
-
model: 'opencode/minimax-m2.5-free',
|
|
708
|
-
systemPrompt: 'Prompt.',
|
|
709
|
-
harnessSettings: { reasoning: 'low', timeoutSeconds: 30 }
|
|
710
|
-
}
|
|
711
|
-
}
|
|
476
|
+
harness: 'codex',
|
|
477
|
+
model: 'openai-codex/gpt-5.3-codex',
|
|
478
|
+
systemPrompt: 'Prompt.',
|
|
479
|
+
harnessSettings: { reasoning: 'high', timeoutSeconds: 30 }
|
|
712
480
|
});
|
|
713
481
|
const loaded = loadLocalPersonas({ cwd, homeDir });
|
|
714
482
|
assert.equal(loaded.byId.has('blank-top-level'), false);
|
|
715
|
-
assert.equal(loaded.byId.has('blank-tier'), false);
|
|
716
483
|
assert.match(loaded.warnings.join('\n'), /blank-top-level\.json.*agentsMdContent must be a non-empty string/);
|
|
717
|
-
assert.match(loaded.warnings.join('\n'), /blank-tier\.json.*agentsMdContent must be a non-empty string/);
|
|
718
484
|
});
|
|
719
485
|
});
|
|
720
486
|
test('extends can resolve a lower-layer standalone persona by intent', () => {
|
|
@@ -724,26 +490,10 @@ test('extends can resolve a lower-layer standalone persona by intent', () => {
|
|
|
724
490
|
intent: 'nextjs-web-stewardship',
|
|
725
491
|
tags: ['implementation'],
|
|
726
492
|
description: 'Base steward persona.',
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
systemPrompt: 'Base prompt.',
|
|
732
|
-
harnessSettings: { reasoning: 'high', timeoutSeconds: 30 }
|
|
733
|
-
},
|
|
734
|
-
'best-value': {
|
|
735
|
-
harness: 'opencode',
|
|
736
|
-
model: 'opencode/gpt-5-nano',
|
|
737
|
-
systemPrompt: 'Base prompt.',
|
|
738
|
-
harnessSettings: { reasoning: 'medium', timeoutSeconds: 30 }
|
|
739
|
-
},
|
|
740
|
-
minimum: {
|
|
741
|
-
harness: 'opencode',
|
|
742
|
-
model: 'opencode/minimax-m2.5-free',
|
|
743
|
-
systemPrompt: 'Base prompt.',
|
|
744
|
-
harnessSettings: { reasoning: 'low', timeoutSeconds: 30 }
|
|
745
|
-
}
|
|
746
|
-
}
|
|
493
|
+
harness: 'codex',
|
|
494
|
+
model: 'openai-codex/gpt-5.3-codex',
|
|
495
|
+
systemPrompt: 'Base prompt.',
|
|
496
|
+
harnessSettings: { reasoning: 'high', timeoutSeconds: 30 }
|
|
747
497
|
});
|
|
748
498
|
writeJson(join(pwdDir, 'project-steward.json'), {
|
|
749
499
|
id: 'project-steward',
|
|
@@ -783,27 +533,19 @@ test('top-level claudeMd resolves to absolute path anchored to its layer dir', (
|
|
|
783
533
|
assert.equal(spec?.claudeMdMode, 'extend');
|
|
784
534
|
});
|
|
785
535
|
});
|
|
786
|
-
test('
|
|
536
|
+
test('top-level claudeMd + mode round-trip through merge', () => {
|
|
787
537
|
withLayers(({ cwd, homeDir }) => {
|
|
788
538
|
writeFileSync(join(homeDir, 'top.md'), '# top\n');
|
|
789
|
-
writeFileSync(join(homeDir, 'best.md'), '# best\n');
|
|
790
539
|
writeJson(join(homeDir, 'p.json'), {
|
|
791
540
|
id: 'p',
|
|
792
541
|
extends: 'persona-maker',
|
|
793
542
|
claudeMd: 'top.md',
|
|
794
|
-
claudeMdMode: 'extend'
|
|
795
|
-
tiers: {
|
|
796
|
-
best: { claudeMd: 'best.md' }
|
|
797
|
-
}
|
|
543
|
+
claudeMdMode: 'extend'
|
|
798
544
|
});
|
|
799
545
|
const loaded = loadLocalPersonas({ cwd, homeDir });
|
|
800
546
|
assert.deepEqual(loaded.warnings, []);
|
|
801
547
|
const spec = loaded.byId.get('p');
|
|
802
|
-
// top-level resolves to top.md
|
|
803
548
|
assert.equal(spec?.claudeMd, join(homeDir, 'top.md'));
|
|
804
|
-
// per-tier `best` resolves to best.md
|
|
805
|
-
assert.equal(spec?.tiers.best.claudeMd, join(homeDir, 'best.md'));
|
|
806
|
-
// mode is independent of path — top-level mode applies, tier inherits.
|
|
807
549
|
assert.equal(spec?.claudeMdMode, 'extend');
|
|
808
550
|
});
|
|
809
551
|
});
|
|
@@ -847,11 +589,7 @@ test('rejects non-md sidecar path', () => {
|
|
|
847
589
|
assert.match(loaded.warnings.join('\n'), /\.md/);
|
|
848
590
|
});
|
|
849
591
|
});
|
|
850
|
-
test('
|
|
851
|
-
// A common pattern from the issue's design notes: "Mode independence:
|
|
852
|
-
// tier overrides path, inherits top-level mode (and vice versa)."
|
|
853
|
-
// The cwd-layer override here only sets a tier-level mode, expecting
|
|
854
|
-
// the top-level path declared in a lower layer to flow through.
|
|
592
|
+
test('overlay claudeMdMode flips while inheriting the path from a lower layer', () => {
|
|
855
593
|
withLayers(({ cwd, homeDir, pwdDir }) => {
|
|
856
594
|
writeFileSync(join(homeDir, 'top.md'), '# top\n');
|
|
857
595
|
writeJson(join(homeDir, 'sidecar-base.json'), {
|
|
@@ -860,22 +598,17 @@ test('mode-only override: tier mode flips while inheriting top-level path', () =
|
|
|
860
598
|
claudeMd: 'top.md',
|
|
861
599
|
claudeMdMode: 'overwrite'
|
|
862
600
|
});
|
|
863
|
-
// cwd-level overlay flips ONLY the
|
|
864
|
-
// from sidecar-base in the user layer.
|
|
601
|
+
// cwd-level overlay flips ONLY the mode; the path inherits from below.
|
|
865
602
|
writeJson(join(pwdDir, 'sidecar-base.json'), {
|
|
866
603
|
id: 'sidecar-base',
|
|
867
604
|
extends: 'sidecar-base',
|
|
868
|
-
|
|
869
|
-
best: { claudeMdMode: 'extend' }
|
|
870
|
-
}
|
|
605
|
+
claudeMdMode: 'extend'
|
|
871
606
|
});
|
|
872
607
|
const loaded = loadLocalPersonas({ cwd, homeDir });
|
|
873
608
|
assert.deepEqual(loaded.warnings, []);
|
|
874
609
|
const spec = loaded.byId.get('sidecar-base');
|
|
875
610
|
assert.equal(spec?.claudeMd, join(homeDir, 'top.md'));
|
|
876
|
-
assert.equal(spec?.
|
|
877
|
-
// Other tiers still inherit the top-level mode.
|
|
878
|
-
assert.equal(spec?.claudeMdMode, 'overwrite');
|
|
611
|
+
assert.equal(spec?.claudeMdMode, 'extend');
|
|
879
612
|
});
|
|
880
613
|
});
|
|
881
614
|
test('missing sidecar file produces a warning, not a throw', () => {
|
|
@@ -902,19 +635,16 @@ test('override path clears inherited claudeMdContent so the override is not shad
|
|
|
902
635
|
// schema accepts only `claudeMd` paths), so this exercises the merge
|
|
903
636
|
// directly via the test seam to construct a base with content and an
|
|
904
637
|
// override with a path. Same for agentsMdContent.
|
|
905
|
-
const baseRuntime = {
|
|
906
|
-
harness: 'claude',
|
|
907
|
-
model: 'claude-3-5-sonnet',
|
|
908
|
-
systemPrompt: 'base',
|
|
909
|
-
harnessSettings: { reasoning: 'medium', timeoutSeconds: 300 }
|
|
910
|
-
};
|
|
911
638
|
const base = {
|
|
912
639
|
id: 'documentation',
|
|
913
640
|
intent: 'documentation',
|
|
914
641
|
tags: ['documentation'],
|
|
915
642
|
description: 'd',
|
|
916
643
|
skills: [],
|
|
917
|
-
|
|
644
|
+
harness: 'claude',
|
|
645
|
+
model: 'claude-3-5-sonnet',
|
|
646
|
+
systemPrompt: 'base',
|
|
647
|
+
harnessSettings: { reasoning: 'medium', timeoutSeconds: 300 },
|
|
918
648
|
claudeMdContent: '# inlined from build-time\n',
|
|
919
649
|
agentsMdContent: '# agents inlined from build-time\n'
|
|
920
650
|
};
|
|
@@ -945,19 +675,16 @@ test('override leaves channel alone: inherited claudeMdContent flows through', (
|
|
|
945
675
|
// Sanity counterpart: when the override does NOT set a new path, the
|
|
946
676
|
// inherited content must NOT be cleared. Otherwise we'd over-correct
|
|
947
677
|
// and drop legitimate built-in sidecars.
|
|
948
|
-
const baseRuntime = {
|
|
949
|
-
harness: 'claude',
|
|
950
|
-
model: 'claude-3-5-sonnet',
|
|
951
|
-
systemPrompt: 'base',
|
|
952
|
-
harnessSettings: { reasoning: 'medium', timeoutSeconds: 300 }
|
|
953
|
-
};
|
|
954
678
|
const base = {
|
|
955
679
|
id: 'documentation',
|
|
956
680
|
intent: 'documentation',
|
|
957
681
|
tags: ['documentation'],
|
|
958
682
|
description: 'd',
|
|
959
683
|
skills: [],
|
|
960
|
-
|
|
684
|
+
harness: 'claude',
|
|
685
|
+
model: 'claude-3-5-sonnet',
|
|
686
|
+
systemPrompt: 'base',
|
|
687
|
+
harnessSettings: { reasoning: 'medium', timeoutSeconds: 300 },
|
|
961
688
|
claudeMdContent: '# keep me\n'
|
|
962
689
|
};
|
|
963
690
|
const override = {
|