agentic-workflow-manager 2.1.0 → 3.0.0

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,194 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ALL_SENTINEL = void 0;
4
+ exports.parseKey = parseKey;
5
+ exports.pickerReducer = pickerReducer;
6
+ exports.multiselectPicker = multiselectPicker;
7
+ const picker_view_1 = require("./picker-view");
8
+ const registry_view_1 = require("../utils/registry-view");
9
+ // ALL_SENTINEL: single source of truth lives in registry-view.ts — re-exported here.
10
+ exports.ALL_SENTINEL = registry_view_1.ALL_SENTINEL;
11
+ function parseKey(data) {
12
+ switch (data) {
13
+ case '\x1b[A': return { action: 'up' };
14
+ case '\x1b[B': return { action: 'down' };
15
+ case '\r':
16
+ case '\n': return { action: 'confirm' };
17
+ case ' ': return { action: 'toggle' };
18
+ case '\t': return { action: 'toggleAll' };
19
+ case '\x7f':
20
+ case '\b': return { action: 'backspace' };
21
+ case '\x03': return { action: 'cancel' }; // Ctrl-C
22
+ case '\x1b': return { action: 'clearOrCancel' }; // Esc
23
+ }
24
+ if (data.length === 1 && /[a-zA-Z0-9-]/.test(data))
25
+ return { action: 'char', char: data };
26
+ return { action: 'none' };
27
+ }
28
+ function realValues(state) {
29
+ return state.items.filter((i) => i.value !== exports.ALL_SENTINEL).map((i) => i.value);
30
+ }
31
+ function pickerReducer(state, key) {
32
+ const vis = (0, picker_view_1.visibleItems)(state);
33
+ switch (key.type) {
34
+ case 'up': {
35
+ if (vis.length === 0)
36
+ return state;
37
+ return { ...state, cursor: (state.cursor - 1 + vis.length) % vis.length };
38
+ }
39
+ case 'down': {
40
+ if (vis.length === 0)
41
+ return state;
42
+ return { ...state, cursor: (state.cursor + 1) % vis.length };
43
+ }
44
+ case 'toggle': {
45
+ if (vis.length === 0)
46
+ return state;
47
+ const item = vis[Math.max(0, Math.min(state.cursor, vis.length - 1))];
48
+ const selected = new Set(state.selected);
49
+ if (item.value === exports.ALL_SENTINEL) {
50
+ const reals = realValues(state);
51
+ const allSel = reals.every((v) => selected.has(v));
52
+ if (allSel) {
53
+ reals.forEach((v) => selected.delete(v));
54
+ selected.delete(exports.ALL_SENTINEL);
55
+ }
56
+ else {
57
+ reals.forEach((v) => selected.add(v));
58
+ selected.add(exports.ALL_SENTINEL);
59
+ }
60
+ }
61
+ else {
62
+ if (selected.has(item.value)) {
63
+ selected.delete(item.value);
64
+ selected.delete(exports.ALL_SENTINEL); // no longer all-selected
65
+ }
66
+ else {
67
+ selected.add(item.value);
68
+ // if every real item is now selected, also mark the sentinel
69
+ const reals = realValues(state);
70
+ if (reals.every((v) => selected.has(v)))
71
+ selected.add(exports.ALL_SENTINEL);
72
+ }
73
+ }
74
+ return { ...state, selected };
75
+ }
76
+ case 'toggleAll': {
77
+ const reals = vis.filter((i) => i.value !== exports.ALL_SENTINEL).map((i) => i.value);
78
+ const selected = new Set(state.selected);
79
+ const allSel = reals.every((v) => selected.has(v));
80
+ if (allSel) {
81
+ reals.forEach((v) => selected.delete(v));
82
+ selected.delete(exports.ALL_SENTINEL);
83
+ }
84
+ else {
85
+ reals.forEach((v) => selected.add(v));
86
+ selected.add(exports.ALL_SENTINEL);
87
+ }
88
+ return { ...state, selected };
89
+ }
90
+ case 'filterChar':
91
+ return { ...state, filter: state.filter + key.char, cursor: 0 };
92
+ case 'backspace':
93
+ return { ...state, filter: state.filter.slice(0, -1), cursor: 0 };
94
+ }
95
+ }
96
+ function defaultIO() {
97
+ return { input: process.stdin, output: process.stdout };
98
+ }
99
+ /**
100
+ * Interactive multiselect. Returns the selected values, or null if cancelled.
101
+ * Streams are injectable for testing. Always restores terminal state on exit.
102
+ */
103
+ function multiselectPicker(opts, io = defaultIO()) {
104
+ return new Promise((resolve) => {
105
+ let state = {
106
+ title: opts.title,
107
+ items: opts.items,
108
+ selected: new Set(opts.initialSelected ?? []),
109
+ cursor: 0,
110
+ filter: '',
111
+ };
112
+ let lastLineCount = 0;
113
+ const size = () => ({
114
+ columns: io.output.columns ?? 80,
115
+ rows: io.output.rows ?? 24,
116
+ });
117
+ const draw = () => {
118
+ const lines = (0, picker_view_1.renderPicker)(state, size());
119
+ // Move up over the previous frame and clear downward.
120
+ if (lastLineCount > 0)
121
+ io.output.write(`\x1b[${lastLineCount}A`);
122
+ io.output.write('\x1b[0J');
123
+ io.output.write(lines.join('\n') + '\n');
124
+ lastLineCount = lines.length;
125
+ };
126
+ let onSigint;
127
+ const cleanup = () => {
128
+ io.input.removeListener('data', onData);
129
+ if (onSigint)
130
+ process.removeListener('SIGINT', onSigint);
131
+ try {
132
+ io.input.setRawMode?.(false);
133
+ }
134
+ catch { /* best-effort: terminal may not support raw mode */ }
135
+ io.input.pause?.();
136
+ io.output.write('\x1b[?25h'); // show cursor
137
+ };
138
+ const onData = (chunk) => {
139
+ const key = parseKey(chunk.toString());
140
+ switch (key.action) {
141
+ case 'confirm':
142
+ cleanup();
143
+ resolve(Array.from(state.selected));
144
+ return;
145
+ case 'cancel':
146
+ cleanup();
147
+ resolve(null);
148
+ return;
149
+ case 'clearOrCancel':
150
+ if (state.filter) {
151
+ state = { ...state, filter: '', cursor: 0 };
152
+ break;
153
+ }
154
+ cleanup();
155
+ resolve(null);
156
+ return;
157
+ case 'up':
158
+ state = pickerReducer(state, { type: 'up' });
159
+ break;
160
+ case 'down':
161
+ state = pickerReducer(state, { type: 'down' });
162
+ break;
163
+ case 'toggle':
164
+ state = pickerReducer(state, { type: 'toggle' });
165
+ break;
166
+ case 'toggleAll':
167
+ state = pickerReducer(state, { type: 'toggleAll' });
168
+ break;
169
+ case 'backspace':
170
+ state = pickerReducer(state, { type: 'backspace' });
171
+ break;
172
+ case 'char':
173
+ state = pickerReducer(state, { type: 'filterChar', char: key.char });
174
+ break;
175
+ case 'none': return; // ignore, no redraw
176
+ }
177
+ draw();
178
+ };
179
+ onSigint = () => {
180
+ cleanup();
181
+ resolve(null);
182
+ };
183
+ process.once('SIGINT', onSigint);
184
+ try {
185
+ io.input.setRawMode?.(true);
186
+ }
187
+ catch { /* best-effort: degrade if raw mode unsupported */ }
188
+ io.input.resume?.();
189
+ io.input.setEncoding?.('utf8');
190
+ io.output.write('\x1b[?25l'); // hide cursor
191
+ io.input.on('data', onData);
192
+ draw();
193
+ });
194
+ }
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stripAnsi = stripAnsi;
4
+ exports.displayWidth = displayWidth;
5
+ exports.truncate = truncate;
6
+ exports.wrap = wrap;
7
+ const ANSI_RE = /\x1b\[[0-9;]*m/g;
8
+ function stripAnsi(s) {
9
+ return s.replace(ANSI_RE, '');
10
+ }
11
+ function isWide(cp) {
12
+ return ((cp >= 0x1100 && cp <= 0x115f) || // Hangul Jamo
13
+ (cp >= 0x2300 && cp <= 0x23ff) || // misc technical (⏎ etc.)
14
+ (cp >= 0x2600 && cp <= 0x27bf) || // misc symbols / dingbats
15
+ (cp >= 0x4e00 && cp <= 0x9fff) || // CJK Unified Ideographs
16
+ (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul Syllables
17
+ (cp >= 0xff00 && cp <= 0xffef) || // Fullwidth Latin/symbols
18
+ (cp >= 0x1f000 && cp <= 0x1faff) // emoji
19
+ );
20
+ }
21
+ /** Visible terminal cells, ignoring ANSI color and treating known wide glyphs as 2. */
22
+ function displayWidth(s) {
23
+ const plain = stripAnsi(s);
24
+ let w = 0;
25
+ for (const ch of plain) {
26
+ const cp = ch.codePointAt(0);
27
+ if (cp === 0xfe0f)
28
+ continue; // variation selector — zero width
29
+ w += isWide(cp) ? 2 : 1;
30
+ }
31
+ return w;
32
+ }
33
+ /** Truncate plain text to `width` cells, appending '…'. Strips ANSI — color AFTER truncating. */
34
+ function truncate(s, width) {
35
+ if (width <= 0)
36
+ return '';
37
+ const plain = stripAnsi(s);
38
+ if (displayWidth(plain) <= width)
39
+ return plain;
40
+ if (width === 1)
41
+ return '…';
42
+ let out = '';
43
+ let w = 0;
44
+ for (const ch of plain) {
45
+ const cw = displayWidth(ch);
46
+ if (w + cw > width - 1)
47
+ break;
48
+ out += ch;
49
+ w += cw;
50
+ }
51
+ return out + '…';
52
+ }
53
+ /** Word-wrap plain text to `width` cells. Long single words are not split (acceptable). */
54
+ function wrap(s, width) {
55
+ if (width <= 0)
56
+ return [s];
57
+ const words = stripAnsi(s).split(/\s+/).filter(Boolean);
58
+ const lines = [];
59
+ let cur = '';
60
+ for (const word of words) {
61
+ if (cur === '') {
62
+ cur = word;
63
+ continue;
64
+ }
65
+ if (displayWidth(cur) + 1 + displayWidth(word) <= width)
66
+ cur += ' ' + word;
67
+ else {
68
+ lines.push(cur);
69
+ cur = word;
70
+ }
71
+ }
72
+ if (cur)
73
+ lines.push(cur);
74
+ return lines.length ? lines : [''];
75
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isInteractive = isInteractive;
4
+ exports.terminalSize = terminalSize;
5
+ /** True only when both ends are TTYs — the precondition for the interactive picker. */
6
+ function isInteractive() {
7
+ return Boolean(process.stdout.isTTY && process.stdin.isTTY);
8
+ }
9
+ /** Current terminal size, with a safe 80x24 fallback when undefined (non-TTY). */
10
+ function terminalSize() {
11
+ return {
12
+ columns: process.stdout.columns ?? 80,
13
+ rows: process.stdout.rows ?? 24,
14
+ };
15
+ }
@@ -10,11 +10,12 @@ exports.packageSummaryLines = packageSummaryLines;
10
10
  exports.packageDetailLines = packageDetailLines;
11
11
  exports.findPackage = findPackage;
12
12
  exports.artifactValue = artifactValue;
13
- exports.buildLevel1Options = buildLevel1Options;
14
- exports.buildLevel2Options = buildLevel2Options;
13
+ exports.packagePickerItems = packagePickerItems;
14
+ exports.artifactPickerItems = artifactPickerItems;
15
15
  exports.resolveLevel2Selection = resolveLevel2Selection;
16
16
  const picocolors_1 = __importDefault(require("picocolors"));
17
17
  const registries_1 = require("../core/registries");
18
+ const text_1 = require("../ui/text");
18
19
  exports.STANDALONE_NAME = 'standalone';
19
20
  function makePackage(name, description, isStandalone, visibility, artifacts) {
20
21
  return {
@@ -73,7 +74,7 @@ function artifactCountLabel(counts) {
73
74
  function packageIcon(pkg) {
74
75
  return pkg.isStandalone ? '🔹' : '📦';
75
76
  }
76
- function packageSummaryLines(packages) {
77
+ function packageSummaryLines(packages, width) {
77
78
  const uniqueSkillNames = new Set(packages.flatMap((p) => p.artifacts.filter((a) => a.type === 'skill').map((a) => a.name)));
78
79
  const totalSkills = uniqueSkillNames.size;
79
80
  const lines = [`AWM Registry — ${plural(packages.length, 'package')}, ${plural(totalSkills, 'skill')}`, ''];
@@ -83,12 +84,17 @@ function packageSummaryLines(packages) {
83
84
  packages.forEach((p, i) => {
84
85
  const name = p.name.padEnd(nameWidth);
85
86
  const count = countLabels[i].padEnd(countWidth);
86
- const desc = p.isStandalone ? '' : p.description;
87
+ let desc = p.isStandalone ? '' : p.description;
88
+ if (width && desc) {
89
+ const used = 3 + nameWidth + 3 + countWidth + 3; // icon(2)+space(1), name, gap, count, gap
90
+ const avail = width - used;
91
+ desc = avail > 1 ? (0, text_1.truncate)(desc, avail) : '';
92
+ }
87
93
  lines.push(`${packageIcon(p)} ${name} ${count} ${desc}`.trimEnd());
88
94
  });
89
95
  return lines;
90
96
  }
91
- function packageDetailLines(pkg) {
97
+ function packageDetailLines(pkg, width) {
92
98
  const lines = [];
93
99
  const header = pkg.isStandalone
94
100
  ? `${packageIcon(pkg)} ${pkg.name} — ${plural(pkg.artifacts.length, 'artifact')}`
@@ -99,8 +105,10 @@ function packageDetailLines(pkg) {
99
105
  ? picocolors_1.default.yellow(` ← ${(0, registries_1.registryNameForPath)(a.sourcePath) ?? 'unknown'} (override)`)
100
106
  : '';
101
107
  lines.push(` ${TYPE_ICON[a.type]}${a.name}${mark}`);
102
- if (a.description)
103
- lines.push(` ${a.description}`);
108
+ if (a.description) {
109
+ const desc = width ? (0, text_1.truncate)(a.description, width - 5) : a.description;
110
+ lines.push(` ${desc}`);
111
+ }
104
112
  });
105
113
  return lines;
106
114
  }
@@ -130,21 +138,27 @@ exports.ALL_SENTINEL = '__ALL__';
130
138
  function artifactValue(a) {
131
139
  return `${a.type}:${a.name}`;
132
140
  }
133
- function buildLevel1Options(packages) {
134
- return packages.map((p) => {
135
- const count = p.isStandalone ? plural(p.artifacts.length, 'artifact') : artifactCountLabel(p.counts);
136
- const desc = p.isStandalone ? '' : ` · ${p.description}`;
137
- return { value: p.name, label: `${packageIcon(p)} ${p.name} ${picocolors_1.default.dim(`${count}${desc}`)}` };
138
- });
141
+ function packagePickerItems(packages) {
142
+ return packages.map((p) => ({
143
+ value: p.name,
144
+ label: `${packageIcon(p)} ${p.name}`,
145
+ description: p.isStandalone
146
+ ? plural(p.artifacts.length, 'artifact')
147
+ : `${artifactCountLabel(p.counts)} · ${p.description}`,
148
+ }));
139
149
  }
140
- function buildLevel2Options(pkg) {
141
- const installAll = { value: exports.ALL_SENTINEL, label: `✨ Install entire package (${pkg.artifacts.length})` };
142
- const rest = pkg.artifacts.map((a) => {
143
- const title = `${TYPE_ICON[a.type]}${a.name}`;
144
- const label = a.description ? `${title}\n ${picocolors_1.default.dim(a.description)}` : title;
145
- return { value: artifactValue(a), label };
146
- });
147
- return [installAll, ...rest];
150
+ function artifactPickerItems(pkg) {
151
+ const all = {
152
+ value: exports.ALL_SENTINEL,
153
+ label: `✨ Install entire package (${pkg.artifacts.length})`,
154
+ description: 'Select every artifact in this package.',
155
+ };
156
+ const rest = pkg.artifacts.map((a) => ({
157
+ value: artifactValue(a),
158
+ label: `${TYPE_ICON[a.type]}${a.name}`,
159
+ description: a.description || '',
160
+ }));
161
+ return [all, ...rest];
148
162
  }
149
163
  function resolveLevel2Selection(pkg, selectedValues) {
150
164
  if (selectedValues.includes(exports.ALL_SENTINEL))
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("../../src/release/core");
4
+ const rec = (header, body = '') => `${header}${core_1.US}${body}${core_1.RS}`;
5
+ const mk = (type, breaking = false) => ({ type, scope: null, breaking, subject: 's' });
6
+ describe('parseCommits', () => {
7
+ it('parsea type, scope, subject', () => {
8
+ const [c] = (0, core_1.parseCommits)(rec('feat(add): nueva flag'));
9
+ expect(c).toEqual({ type: 'feat', scope: 'add', breaking: false, subject: 'nueva flag' });
10
+ });
11
+ it('scope null cuando no hay paréntesis', () => {
12
+ expect((0, core_1.parseCommits)(rec('fix: corrige bug'))[0].scope).toBeNull();
13
+ });
14
+ it('breaking por bang en el header', () => {
15
+ expect((0, core_1.parseCommits)(rec('feat!: rompe API'))[0].breaking).toBe(true);
16
+ });
17
+ it('breaking por footer BREAKING CHANGE en el body', () => {
18
+ expect((0, core_1.parseCommits)(rec('feat(x): y', 'cuerpo\nBREAKING CHANGE: cambia todo'))[0].breaking).toBe(true);
19
+ });
20
+ it('descarta commits no convencionales sin romper', () => {
21
+ expect((0, core_1.parseCommits)(rec('merge branch foo') + rec('feat: ok'))).toEqual([
22
+ { type: 'feat', scope: null, breaking: false, subject: 'ok' },
23
+ ]);
24
+ });
25
+ it('entrada vacía → arreglo vacío', () => {
26
+ expect((0, core_1.parseCommits)('')).toEqual([]);
27
+ });
28
+ });
29
+ describe('determineBump', () => {
30
+ it('breaking → major (gana sobre feat/fix)', () => {
31
+ expect((0, core_1.determineBump)([mk('fix'), mk('feat', true)])).toBe('major');
32
+ });
33
+ it('feat sin breaking → minor', () => {
34
+ expect((0, core_1.determineBump)([mk('fix'), mk('feat')])).toBe('minor');
35
+ });
36
+ it('fix o perf → patch', () => {
37
+ expect((0, core_1.determineBump)([mk('fix')])).toBe('patch');
38
+ expect((0, core_1.determineBump)([mk('perf')])).toBe('patch');
39
+ });
40
+ it('solo docs/chore/refactor → null (nada releasable)', () => {
41
+ expect((0, core_1.determineBump)([mk('docs'), mk('chore'), mk('refactor')])).toBeNull();
42
+ });
43
+ it('lista vacía → null', () => {
44
+ expect((0, core_1.determineBump)([])).toBeNull();
45
+ });
46
+ });
47
+ describe('nextVersion', () => {
48
+ it('major resetea minor y patch', () => {
49
+ expect((0, core_1.nextVersion)('2.1.1', 'major')).toBe('3.0.0');
50
+ });
51
+ it('minor resetea patch', () => {
52
+ expect((0, core_1.nextVersion)('2.1.1', 'minor')).toBe('2.2.0');
53
+ });
54
+ it('patch incrementa patch', () => {
55
+ expect((0, core_1.nextVersion)('2.1.1', 'patch')).toBe('2.1.2');
56
+ });
57
+ it('tolera espacios alrededor', () => {
58
+ expect((0, core_1.nextVersion)(' 2.1.1 ', 'patch')).toBe('2.1.2');
59
+ });
60
+ it.each(['', '.', '..', 'x.y.z', '2.1', '2.1.1.0'])('rechaza base inválida %p', (bad) => {
61
+ expect(() => (0, core_1.nextVersion)(bad, 'patch')).toThrow(/invalid base version/i);
62
+ });
63
+ });
64
+ describe('selectFloor', () => {
65
+ it('sin tag → usa la versión del package.json', () => {
66
+ expect((0, core_1.selectFloor)('2.1.1', null)).toBe('2.1.1');
67
+ });
68
+ it('package.json mayor que el tag → gana package.json (caso drift real)', () => {
69
+ expect((0, core_1.selectFloor)('2.1.1', '1.0.0')).toBe('2.1.1');
70
+ });
71
+ it('tag mayor que package.json → gana el tag', () => {
72
+ expect((0, core_1.selectFloor)('2.1.1', '2.5.0')).toBe('2.5.0');
73
+ });
74
+ it('iguales → ese valor', () => {
75
+ expect((0, core_1.selectFloor)('2.1.1', '2.1.1')).toBe('2.1.1');
76
+ });
77
+ it('rechaza current inválido', () => {
78
+ expect(() => (0, core_1.selectFloor)('', null)).toThrow(/invalid/i);
79
+ });
80
+ });
81
+ describe('renderChangelog', () => {
82
+ const commits = [
83
+ { type: 'feat', scope: 'add', breaking: false, subject: 'flag --all' },
84
+ { type: 'fix', scope: null, breaking: false, subject: 'corrige width' },
85
+ { type: 'chore', scope: null, breaking: true, subject: 'sube node' },
86
+ ];
87
+ it('encabeza con versión y fecha', () => {
88
+ expect((0, core_1.renderChangelog)('2.2.0', '2026-06-25', commits)).toContain('## v2.2.0 - 2026-06-25');
89
+ });
90
+ it('agrupa Features, Fixes y Breaking Changes', () => {
91
+ const md = (0, core_1.renderChangelog)('2.2.0', '2026-06-25', commits);
92
+ expect(md).toContain('### Features');
93
+ expect(md).toContain('- **add:** flag --all');
94
+ expect(md).toContain('### Fixes');
95
+ expect(md).toContain('- corrige width');
96
+ expect(md).toContain('### Breaking Changes');
97
+ expect(md).toContain('- sube node');
98
+ });
99
+ it('omite secciones vacías', () => {
100
+ const md = (0, core_1.renderChangelog)('2.2.1', '2026-06-25', [
101
+ { type: 'fix', scope: null, breaking: false, subject: 'x' },
102
+ ]);
103
+ expect(md).toContain('### Fixes');
104
+ expect(md).not.toContain('### Features');
105
+ });
106
+ });