agentic-workflow-manager 2.0.1 → 2.1.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/src/commands/doctor.js +2 -0
- package/dist/src/commands/hooks/install.js +10 -4
- package/dist/src/commands/hooks/uninstall.js +2 -2
- package/dist/src/commands/init.js +2 -0
- package/dist/src/commands/sensors/formatters/eslint.js +7 -1
- package/dist/src/commands/sensors/install.js +3 -4
- package/dist/src/core/context/materializer.js +2 -5
- package/dist/src/core/diagnostics/context.js +2 -4
- package/dist/src/core/paths.js +56 -0
- package/dist/src/core/registries.js +3 -3
- package/dist/src/core/update-check.js +2 -3
- package/dist/src/index.js +132 -17
- package/dist/src/providers/index.js +4 -4
- package/dist/src/ui/picker-view.js +77 -0
- package/dist/src/ui/picker.js +194 -0
- package/dist/src/ui/text.js +75 -0
- package/dist/src/ui/tty.js +15 -0
- package/dist/src/utils/config.js +2 -2
- package/dist/src/utils/registry-view.js +35 -21
- package/dist/tests/commands/doctor-platform.test.js +22 -0
- package/dist/tests/commands/hooks/install-symlink-fallback.test.js +59 -0
- package/dist/tests/commands/sensors/eslint-formatter.test.js +34 -0
- package/dist/tests/core/paths.test.js +78 -0
- package/dist/tests/ui/picker-reducer.test.js +92 -0
- package/dist/tests/ui/picker-shell.test.js +59 -0
- package/dist/tests/ui/picker-view.test.js +57 -0
- package/dist/tests/ui/text.test.js +55 -0
- package/dist/tests/ui/tty.test.js +39 -0
- package/dist/tests/utils/registry-view.test.js +39 -36
- package/package.json +4 -2
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const picker_1 = require("../../src/ui/picker");
|
|
4
|
+
const items = [
|
|
5
|
+
{ value: picker_1.ALL_SENTINEL, label: '✨ Install entire package (2)', description: '' },
|
|
6
|
+
{ value: 'skill:a', label: 'alpha', description: '' },
|
|
7
|
+
{ value: 'skill:b', label: 'beta', description: '' },
|
|
8
|
+
];
|
|
9
|
+
const base = (over = {}) => ({
|
|
10
|
+
title: 't', items, selected: new Set(), cursor: 0, filter: '', ...over,
|
|
11
|
+
});
|
|
12
|
+
describe('parseKey', () => {
|
|
13
|
+
it('maps arrow and control sequences', () => {
|
|
14
|
+
expect((0, picker_1.parseKey)('\x1b[A')).toEqual({ action: 'up' });
|
|
15
|
+
expect((0, picker_1.parseKey)('\x1b[B')).toEqual({ action: 'down' });
|
|
16
|
+
expect((0, picker_1.parseKey)('\r')).toEqual({ action: 'confirm' });
|
|
17
|
+
expect((0, picker_1.parseKey)(' ')).toEqual({ action: 'toggle' });
|
|
18
|
+
expect((0, picker_1.parseKey)('\t')).toEqual({ action: 'toggleAll' });
|
|
19
|
+
expect((0, picker_1.parseKey)('\x03')).toEqual({ action: 'cancel' });
|
|
20
|
+
expect((0, picker_1.parseKey)('\x1b')).toEqual({ action: 'clearOrCancel' });
|
|
21
|
+
expect((0, picker_1.parseKey)('\x7f')).toEqual({ action: 'backspace' });
|
|
22
|
+
});
|
|
23
|
+
it('maps filter characters and ignores the rest', () => {
|
|
24
|
+
expect((0, picker_1.parseKey)('c')).toEqual({ action: 'char', char: 'c' });
|
|
25
|
+
expect((0, picker_1.parseKey)('-')).toEqual({ action: 'char', char: '-' });
|
|
26
|
+
expect((0, picker_1.parseKey)('?')).toEqual({ action: 'none' });
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe('pickerReducer', () => {
|
|
30
|
+
it('moves the cursor and wraps around', () => {
|
|
31
|
+
expect((0, picker_1.pickerReducer)(base({ cursor: 0 }), { type: 'up' }).cursor).toBe(2);
|
|
32
|
+
expect((0, picker_1.pickerReducer)(base({ cursor: 2 }), { type: 'down' }).cursor).toBe(0);
|
|
33
|
+
});
|
|
34
|
+
it('toggles the current item', () => {
|
|
35
|
+
const s = (0, picker_1.pickerReducer)(base({ cursor: 1 }), { type: 'toggle' });
|
|
36
|
+
expect(s.selected.has('skill:a')).toBe(true);
|
|
37
|
+
const s2 = (0, picker_1.pickerReducer)(s, { type: 'toggle' });
|
|
38
|
+
expect(s2.selected.has('skill:a')).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
it('toggling the ALL sentinel selects every real item plus the sentinel', () => {
|
|
41
|
+
const s = (0, picker_1.pickerReducer)(base({ cursor: 0 }), { type: 'toggle' });
|
|
42
|
+
expect(s.selected.has('skill:a')).toBe(true);
|
|
43
|
+
expect(s.selected.has('skill:b')).toBe(true);
|
|
44
|
+
expect(s.selected.has(picker_1.ALL_SENTINEL)).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
it('Tab toggles all real items without needing the sentinel row', () => {
|
|
47
|
+
const s = (0, picker_1.pickerReducer)(base(), { type: 'toggleAll' });
|
|
48
|
+
expect(s.selected.has('skill:a')).toBe(true);
|
|
49
|
+
expect(s.selected.has('skill:b')).toBe(true);
|
|
50
|
+
expect(s.selected.has(picker_1.ALL_SENTINEL)).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
it('toggleAll via Tab syncs ALL_SENTINEL when all become selected', () => {
|
|
53
|
+
const s = (0, picker_1.pickerReducer)(base(), { type: 'toggleAll' });
|
|
54
|
+
expect(s.selected.has('skill:a')).toBe(true);
|
|
55
|
+
expect(s.selected.has('skill:b')).toBe(true);
|
|
56
|
+
expect(s.selected.has(picker_1.ALL_SENTINEL)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
it('typing filters and resets the cursor to 0', () => {
|
|
59
|
+
const s = (0, picker_1.pickerReducer)(base({ cursor: 2 }), { type: 'filterChar', char: 'b' });
|
|
60
|
+
expect(s.filter).toBe('b');
|
|
61
|
+
expect(s.cursor).toBe(0);
|
|
62
|
+
});
|
|
63
|
+
it('backspace edits the filter', () => {
|
|
64
|
+
expect((0, picker_1.pickerReducer)(base({ filter: 'be' }), { type: 'backspace' }).filter).toBe('b');
|
|
65
|
+
});
|
|
66
|
+
it('does not move the cursor when the filtered list is empty', () => {
|
|
67
|
+
const s = base({ filter: 'zzz', cursor: 0 });
|
|
68
|
+
expect((0, picker_1.pickerReducer)(s, { type: 'down' }).cursor).toBe(0);
|
|
69
|
+
});
|
|
70
|
+
it('toggling an individual item off removes ALL_SENTINEL from selected', () => {
|
|
71
|
+
// Start with all items pre-selected (the initialSelected scenario)
|
|
72
|
+
const all = new Set([picker_1.ALL_SENTINEL, 'skill:a', 'skill:b']);
|
|
73
|
+
const s = (0, picker_1.pickerReducer)(base({ cursor: 1, selected: all }), { type: 'toggle' });
|
|
74
|
+
// Unchecked skill:a — sentinel should be gone
|
|
75
|
+
expect(s.selected.has('skill:a')).toBe(false);
|
|
76
|
+
expect(s.selected.has(picker_1.ALL_SENTINEL)).toBe(false);
|
|
77
|
+
expect(s.selected.has('skill:b')).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
it('toggling the last missing item on syncs the ALL_SENTINEL', () => {
|
|
80
|
+
// skill:a is missing, skill:b is selected — select skill:a and sentinel should appear
|
|
81
|
+
const partial = new Set(['skill:b']);
|
|
82
|
+
const s = (0, picker_1.pickerReducer)(base({ cursor: 1, selected: partial }), { type: 'toggle' });
|
|
83
|
+
expect(s.selected.has('skill:a')).toBe(true);
|
|
84
|
+
expect(s.selected.has(picker_1.ALL_SENTINEL)).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
it('toggleAll with active filter only toggles visible items', () => {
|
|
87
|
+
// filter to 'beta' — only skill:b is visible (ALL_SENTINEL label '✨ Install entire package (2)' does not match)
|
|
88
|
+
const s = (0, picker_1.pickerReducer)(base({ filter: 'beta' }), { type: 'toggleAll' });
|
|
89
|
+
expect(s.selected.has('skill:b')).toBe(true);
|
|
90
|
+
expect(s.selected.has('skill:a')).toBe(false); // not visible, should NOT be selected
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const events_1 = require("events");
|
|
4
|
+
const picker_1 = require("../../src/ui/picker");
|
|
5
|
+
function fakeIO() {
|
|
6
|
+
const input = new events_1.EventEmitter();
|
|
7
|
+
input.setRawMode = jest.fn();
|
|
8
|
+
input.resume = jest.fn();
|
|
9
|
+
input.pause = jest.fn();
|
|
10
|
+
input.setEncoding = jest.fn();
|
|
11
|
+
const writes = [];
|
|
12
|
+
const output = { write: (s) => { writes.push(s); return true; }, columns: 100, rows: 20 };
|
|
13
|
+
return { io: { input, output }, writes, send: (s) => input.emit('data', s) };
|
|
14
|
+
}
|
|
15
|
+
const items = [
|
|
16
|
+
{ value: picker_1.ALL_SENTINEL, label: '✨ all', description: '' },
|
|
17
|
+
{ value: 'skill:a', label: 'alpha', description: 'A.' },
|
|
18
|
+
{ value: 'skill:b', label: 'beta', description: 'B.' },
|
|
19
|
+
];
|
|
20
|
+
it('resolves with the toggled selection on Enter', async () => {
|
|
21
|
+
const { io, send } = fakeIO();
|
|
22
|
+
const p = (0, picker_1.multiselectPicker)({ title: 't', items }, io);
|
|
23
|
+
send('\x1b[B'); // down → cursor on alpha
|
|
24
|
+
send(' '); // toggle alpha
|
|
25
|
+
send('\r'); // confirm
|
|
26
|
+
await expect(p).resolves.toEqual(['skill:a']);
|
|
27
|
+
expect(io.input.setRawMode).toHaveBeenLastCalledWith(false); // restored
|
|
28
|
+
});
|
|
29
|
+
it('resolves null on Ctrl-C and restores raw mode', async () => {
|
|
30
|
+
const { io, send } = fakeIO();
|
|
31
|
+
const p = (0, picker_1.multiselectPicker)({ title: 't', items }, io);
|
|
32
|
+
send('\x03');
|
|
33
|
+
await expect(p).resolves.toBeNull();
|
|
34
|
+
expect(io.input.setRawMode).toHaveBeenLastCalledWith(false);
|
|
35
|
+
});
|
|
36
|
+
it('seeds the initial selection', async () => {
|
|
37
|
+
const { io, send } = fakeIO();
|
|
38
|
+
const p = (0, picker_1.multiselectPicker)({ title: 't', items, initialSelected: [picker_1.ALL_SENTINEL, 'skill:a', 'skill:b'] }, io);
|
|
39
|
+
send('\r');
|
|
40
|
+
const result = await p;
|
|
41
|
+
expect(new Set(result)).toEqual(new Set([picker_1.ALL_SENTINEL, 'skill:a', 'skill:b']));
|
|
42
|
+
});
|
|
43
|
+
it('Esc with empty filter cancels and returns null', async () => {
|
|
44
|
+
const { io, send } = fakeIO();
|
|
45
|
+
const p = (0, picker_1.multiselectPicker)({ title: 't', items }, io);
|
|
46
|
+
send('\x1b'); // Esc with no active filter → cancel
|
|
47
|
+
await expect(p).resolves.toBeNull();
|
|
48
|
+
expect(io.input.setRawMode).toHaveBeenLastCalledWith(false);
|
|
49
|
+
});
|
|
50
|
+
it('Esc with active filter clears it instead of cancelling', async () => {
|
|
51
|
+
const { io, send } = fakeIO();
|
|
52
|
+
const p = (0, picker_1.multiselectPicker)({ title: 't', items }, io);
|
|
53
|
+
send('a'); // type 'a' → filter becomes 'a'
|
|
54
|
+
send('\x1b'); // Esc → should clear filter, NOT cancel
|
|
55
|
+
send('\r'); // Enter → confirm (with no selection)
|
|
56
|
+
await expect(p).resolves.toEqual([]);
|
|
57
|
+
// setRawMode(false) called on confirm, not on Esc
|
|
58
|
+
expect(io.input.setRawMode).toHaveBeenLastCalledWith(false);
|
|
59
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const picker_view_1 = require("../../src/ui/picker-view");
|
|
4
|
+
const items = [
|
|
5
|
+
{ value: 'skill:architecture-advisor', label: 'architecture-advisor', description: 'Define and review system architecture.' },
|
|
6
|
+
{ value: 'skill:brainstorming', label: 'brainstorming', description: 'Turn ideas into designs.' },
|
|
7
|
+
{ value: 'skill:cicd-proposal-builder', label: 'cicd-proposal-builder', description: 'Specialist in CI/CD pipeline design. Use when defining a pipeline.' },
|
|
8
|
+
];
|
|
9
|
+
const base = (over = {}) => ({
|
|
10
|
+
title: 'dev — select artifacts',
|
|
11
|
+
items,
|
|
12
|
+
selected: new Set(),
|
|
13
|
+
cursor: 0,
|
|
14
|
+
filter: '',
|
|
15
|
+
...over,
|
|
16
|
+
});
|
|
17
|
+
describe('visibleItems', () => {
|
|
18
|
+
it('returns all items with an empty filter', () => {
|
|
19
|
+
expect((0, picker_view_1.visibleItems)(base())).toHaveLength(3);
|
|
20
|
+
});
|
|
21
|
+
it('filters by label substring, case-insensitive', () => {
|
|
22
|
+
expect((0, picker_view_1.visibleItems)(base({ filter: 'CIC' })).map((i) => i.value)).toEqual(['skill:cicd-proposal-builder']);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe('renderPicker', () => {
|
|
26
|
+
it('shows the title in the header', () => {
|
|
27
|
+
const lines = (0, picker_view_1.renderPicker)(base(), { columns: 100, rows: 20 });
|
|
28
|
+
expect(lines[0]).toContain('dev — select artifacts');
|
|
29
|
+
});
|
|
30
|
+
it('marks the cursor row and selected rows', () => {
|
|
31
|
+
const lines = (0, picker_view_1.renderPicker)(base({ cursor: 1, selected: new Set(['skill:brainstorming']) }), { columns: 100, rows: 20 });
|
|
32
|
+
const body = lines.join('\n');
|
|
33
|
+
expect(body).toContain('❯'); // cursor marker
|
|
34
|
+
expect(body).toContain('◼'); // a selected checkbox
|
|
35
|
+
expect(body).toContain('◻'); // an unselected checkbox
|
|
36
|
+
});
|
|
37
|
+
it('uses two panes (a gutter) when wide', () => {
|
|
38
|
+
const lines = (0, picker_view_1.renderPicker)(base(), { columns: picker_view_1.TWO_PANE_MIN_COLUMNS + 10, rows: 20 });
|
|
39
|
+
expect(lines.some((l) => l.includes(' │ '))).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
it('collapses to one pane (no gutter) when narrow', () => {
|
|
42
|
+
const lines = (0, picker_view_1.renderPicker)(base(), { columns: 50, rows: 20 });
|
|
43
|
+
expect(lines.some((l) => l.includes(' │ '))).toBe(false);
|
|
44
|
+
// the highlighted item's description still appears below the list
|
|
45
|
+
expect(lines.join('\n')).toContain('Define and review');
|
|
46
|
+
});
|
|
47
|
+
it('shows the filter text in the header when filtering', () => {
|
|
48
|
+
expect((0, picker_view_1.renderPicker)(base({ filter: 'cic' }), { columns: 100, rows: 20 })[0]).toContain('filter: cic');
|
|
49
|
+
});
|
|
50
|
+
it('reports no matches when the filter excludes everything', () => {
|
|
51
|
+
expect((0, picker_view_1.renderPicker)(base({ filter: 'zzz' }), { columns: 100, rows: 20 }).join('\n').toLowerCase()).toContain('no matches');
|
|
52
|
+
});
|
|
53
|
+
it('always ends with the key hint footer', () => {
|
|
54
|
+
const lines = (0, picker_view_1.renderPicker)(base(), { columns: 100, rows: 20 });
|
|
55
|
+
expect(lines[lines.length - 1]).toContain('confirm');
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const text_1 = require("../../src/ui/text");
|
|
7
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
8
|
+
describe('stripAnsi', () => {
|
|
9
|
+
it('removes color escape sequences', () => {
|
|
10
|
+
expect((0, text_1.stripAnsi)(picocolors_1.default.green('hi'))).toBe('hi');
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
describe('displayWidth', () => {
|
|
14
|
+
it('counts plain ASCII one cell each', () => {
|
|
15
|
+
expect((0, text_1.displayWidth)('hello')).toBe(5);
|
|
16
|
+
});
|
|
17
|
+
it('ignores color codes', () => {
|
|
18
|
+
expect((0, text_1.displayWidth)(picocolors_1.default.red('abc'))).toBe(3);
|
|
19
|
+
});
|
|
20
|
+
it('counts an emoji as two cells and ignores the VS16 selector', () => {
|
|
21
|
+
expect((0, text_1.displayWidth)('📦')).toBe(2);
|
|
22
|
+
expect((0, text_1.displayWidth)('✍️')).toBe(2);
|
|
23
|
+
});
|
|
24
|
+
it('counts CJK ideographs as 2 cells', () => {
|
|
25
|
+
expect((0, text_1.displayWidth)('你好')).toBe(4);
|
|
26
|
+
});
|
|
27
|
+
it('counts Hangul syllables as 2 cells', () => {
|
|
28
|
+
expect((0, text_1.displayWidth)('안녕')).toBe(4);
|
|
29
|
+
});
|
|
30
|
+
it('counts fullwidth Latin as 2 cells', () => {
|
|
31
|
+
// U+FF21 = A (fullwidth A)
|
|
32
|
+
expect((0, text_1.displayWidth)('AB')).toBe(4);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
describe('truncate', () => {
|
|
36
|
+
it('returns the string unchanged when it fits', () => {
|
|
37
|
+
expect((0, text_1.truncate)('abc', 5)).toBe('abc');
|
|
38
|
+
});
|
|
39
|
+
it('truncates and appends an ellipsis, never exceeding width', () => {
|
|
40
|
+
const out = (0, text_1.truncate)('abcdefgh', 5);
|
|
41
|
+
expect(out).toBe('abcd…');
|
|
42
|
+
expect((0, text_1.displayWidth)(out)).toBeLessThanOrEqual(5);
|
|
43
|
+
});
|
|
44
|
+
it('returns empty for non-positive width', () => {
|
|
45
|
+
expect((0, text_1.truncate)('abc', 0)).toBe('');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe('wrap', () => {
|
|
49
|
+
it('breaks text at word boundaries within width', () => {
|
|
50
|
+
expect((0, text_1.wrap)('the quick brown fox', 9)).toEqual(['the quick', 'brown fox']);
|
|
51
|
+
});
|
|
52
|
+
it('returns a single empty line for empty input', () => {
|
|
53
|
+
expect((0, text_1.wrap)('', 10)).toEqual(['']);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tty_1 = require("../../src/ui/tty");
|
|
4
|
+
describe('isInteractive', () => {
|
|
5
|
+
const outTTY = process.stdout.isTTY;
|
|
6
|
+
const inTTY = process.stdin.isTTY;
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: outTTY, configurable: true });
|
|
9
|
+
Object.defineProperty(process.stdin, 'isTTY', { value: inTTY, configurable: true });
|
|
10
|
+
});
|
|
11
|
+
it('is true only when both stdin and stdout are TTYs', () => {
|
|
12
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: true, configurable: true });
|
|
13
|
+
Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true });
|
|
14
|
+
expect((0, tty_1.isInteractive)()).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
it('is false when stdout is not a TTY (piped output)', () => {
|
|
17
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: false, configurable: true });
|
|
18
|
+
Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true });
|
|
19
|
+
expect((0, tty_1.isInteractive)()).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
describe('terminalSize', () => {
|
|
23
|
+
const cols = process.stdout.columns;
|
|
24
|
+
const rows = process.stdout.rows;
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
Object.defineProperty(process.stdout, 'columns', { value: cols, configurable: true });
|
|
27
|
+
Object.defineProperty(process.stdout, 'rows', { value: rows, configurable: true });
|
|
28
|
+
});
|
|
29
|
+
it('reports the stdout dimensions', () => {
|
|
30
|
+
Object.defineProperty(process.stdout, 'columns', { value: 120, configurable: true });
|
|
31
|
+
Object.defineProperty(process.stdout, 'rows', { value: 40, configurable: true });
|
|
32
|
+
expect((0, tty_1.terminalSize)()).toEqual({ columns: 120, rows: 40 });
|
|
33
|
+
});
|
|
34
|
+
it('falls back to 80x24 when dimensions are undefined', () => {
|
|
35
|
+
Object.defineProperty(process.stdout, 'columns', { value: undefined, configurable: true });
|
|
36
|
+
Object.defineProperty(process.stdout, 'rows', { value: undefined, configurable: true });
|
|
37
|
+
expect((0, tty_1.terminalSize)()).toEqual({ columns: 80, rows: 24 });
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -89,42 +89,6 @@ describe('findPackage', () => {
|
|
|
89
89
|
expect(res.suggestion).toBe('core-dev');
|
|
90
90
|
});
|
|
91
91
|
});
|
|
92
|
-
describe('buildLevel1Options', () => {
|
|
93
|
-
it('produces one option per package keyed by package name', () => {
|
|
94
|
-
const view = (0, registry_view_1.buildPackageView)([skill('brainstorming'), skill('shared')], [], [], processes);
|
|
95
|
-
const opts = (0, registry_view_1.buildLevel1Options)(view);
|
|
96
|
-
expect(opts.map((o) => o.value).sort()).toEqual(['core-dev', 'docs']);
|
|
97
|
-
expect(opts.find((o) => o.value === 'core-dev').label).toContain('core-dev');
|
|
98
|
-
expect(opts.find((o) => o.value === 'core-dev').label).toContain('Dev lifecycle');
|
|
99
|
-
});
|
|
100
|
-
it('uses artifact-count label for standalone packages', () => {
|
|
101
|
-
const view = (0, registry_view_1.buildPackageView)([skill('orphan')], [], [], []);
|
|
102
|
-
const opts = (0, registry_view_1.buildLevel1Options)(view);
|
|
103
|
-
expect(opts[0].value).toBe(registry_view_1.STANDALONE_NAME);
|
|
104
|
-
expect(opts[0].label).toContain('1 artifact');
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
describe('buildLevel2Options', () => {
|
|
108
|
-
it('puts an "install entire package" sentinel first, then one option per artifact', () => {
|
|
109
|
-
const view = (0, registry_view_1.buildPackageView)([skill('brainstorming', 'explore')], [wf('exec')], [agent('plan')], processes);
|
|
110
|
-
const core = view.find((p) => p.name === 'core-dev');
|
|
111
|
-
const opts = (0, registry_view_1.buildLevel2Options)(core);
|
|
112
|
-
expect(opts[0].value).toBe(registry_view_1.ALL_SENTINEL);
|
|
113
|
-
expect(opts[0].label).toContain('Install entire package');
|
|
114
|
-
const values = opts.slice(1).map((o) => o.value);
|
|
115
|
-
expect(values).toContain('skill:brainstorming');
|
|
116
|
-
expect(values).toContain('workflow:exec');
|
|
117
|
-
expect(values).toContain('agent:plan');
|
|
118
|
-
expect(opts.find((o) => o.value === 'skill:brainstorming').label).toContain('explore');
|
|
119
|
-
});
|
|
120
|
-
it('uses plain title for artifact with no description', () => {
|
|
121
|
-
const view = (0, registry_view_1.buildPackageView)([skill('brainstorming', 'explore')], [wf('exec')], [agent('plan')], processes);
|
|
122
|
-
const core = view.find((p) => p.name === 'core-dev');
|
|
123
|
-
const opts = (0, registry_view_1.buildLevel2Options)(core);
|
|
124
|
-
const wfOpt = opts.find((o) => o.value === 'workflow:exec');
|
|
125
|
-
expect(wfOpt.label).toBe('⚡ exec');
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
92
|
describe('resolveLevel2Selection', () => {
|
|
129
93
|
const view = (0, registry_view_1.buildPackageView)([skill('brainstorming'), skill('shared')], [], [], processes);
|
|
130
94
|
const core = view.find((p) => p.name === 'core-dev');
|
|
@@ -154,3 +118,42 @@ describe('visibility', () => {
|
|
|
154
118
|
expect(view.find((p) => p.name === 'core-dev').visibility).toBe('public');
|
|
155
119
|
});
|
|
156
120
|
});
|
|
121
|
+
describe('packageSummaryLines width-awareness', () => {
|
|
122
|
+
it('truncates the description column when a width is given', () => {
|
|
123
|
+
const view = (0, registry_view_1.buildPackageView)([skill('brainstorming', 'x'.repeat(200))], [], [], [
|
|
124
|
+
bundle({ name: 'core-dev', description: 'x'.repeat(200), skills: [{ name: 'brainstorming', onSignal: false }] }),
|
|
125
|
+
]);
|
|
126
|
+
const lines = (0, registry_view_1.packageSummaryLines)(view, 60);
|
|
127
|
+
for (const l of lines)
|
|
128
|
+
expect(l.length).toBeLessThanOrEqual(60);
|
|
129
|
+
expect(lines.some((l) => l.includes('…'))).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
it('does not truncate when no width is given (piped output)', () => {
|
|
132
|
+
const view = (0, registry_view_1.buildPackageView)([skill('brainstorming', 'y'.repeat(200))], [], [], [
|
|
133
|
+
bundle({ name: 'core-dev', description: 'z'.repeat(200), skills: [{ name: 'brainstorming', onSignal: false }] }),
|
|
134
|
+
]);
|
|
135
|
+
const lines = (0, registry_view_1.packageSummaryLines)(view); // no width
|
|
136
|
+
expect(lines.some((l) => l.includes('z'.repeat(200)))).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
describe('artifactPickerItems', () => {
|
|
140
|
+
it('prepends an "install entire package" sentinel item, then one per artifact', () => {
|
|
141
|
+
const view = (0, registry_view_1.buildPackageView)([skill('a', 'desc a'), skill('b', 'desc b')], [], [], [
|
|
142
|
+
bundle({ name: 'p', description: 'pkg', skills: [{ name: 'a', onSignal: false }, { name: 'b', onSignal: false }] }),
|
|
143
|
+
]);
|
|
144
|
+
const items = (0, registry_view_1.artifactPickerItems)(view.find((p) => p.name === 'p'));
|
|
145
|
+
expect(items[0].value).toBe(registry_view_1.ALL_SENTINEL);
|
|
146
|
+
expect(items.slice(1).map((i) => i.label)).toEqual(['a', 'b']);
|
|
147
|
+
expect(items.find((i) => i.label === 'a').description).toBe('desc a');
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
describe('packagePickerItems', () => {
|
|
151
|
+
it('builds one item per package with a count+description summary', () => {
|
|
152
|
+
const view = (0, registry_view_1.buildPackageView)([skill('a')], [], [], [
|
|
153
|
+
bundle({ name: 'p', description: 'pkg desc', skills: [{ name: 'a', onSignal: false }] }),
|
|
154
|
+
]);
|
|
155
|
+
const items = (0, registry_view_1.packagePickerItems)(view);
|
|
156
|
+
expect(items[0].value).toBe('p');
|
|
157
|
+
expect(items[0].description).toContain('pkg desc');
|
|
158
|
+
});
|
|
159
|
+
});
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-workflow-manager",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"main": "dist/src/index.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"awm": "./dist/src/index.js"
|
|
7
7
|
},
|
|
8
|
-
"files": [
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
9
11
|
"repository": {
|
|
10
12
|
"type": "git",
|
|
11
13
|
"url": "git+https://github.com/Kodria/agentic-workflow.git",
|