agentic-workflow-manager 3.13.3 → 3.13.5

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,117 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // Regresion: `description: >-` (YAML block scalar) rompia `awm add <bundle>
4
+ // -a copilot|cursor` contra un skill REAL del registry baseline
5
+ // (skills/extract-design-md/SKILL.md, que usa exactamente esta forma).
6
+ //
7
+ // Un block scalar es YAML perfectamente valido: el indicador (`>-`, `|`, ...)
8
+ // va en la linea de la clave y el texto real vive en las lineas indentadas
9
+ // que siguen. El parser trataba el indicador como si fuera el valor, lo
10
+ // detectaba como "no es una descripcion de verdad", y lo colapsaba a '' —
11
+ // que en `parseSkillSource` significa throw ("requires a non-empty
12
+ // description") y en `readArtifactDescription` significa degradar en
13
+ // silencio a descripcion vacia. Ninguno de los dos leia jamas las lineas
14
+ // siguientes, que es donde estaba el texto todo el tiempo.
15
+ const skill_source_1 = require("../../../src/core/renderers/skill-source");
16
+ const cursor_mdc_1 = require("../../../src/core/renderers/cursor-mdc");
17
+ const copilot_instructions_1 = require("../../../src/core/renderers/copilot-instructions");
18
+ // Copia fiel del frontmatter de skills/extract-design-md/SKILL.md en
19
+ // awm-baseline-registry — el caso real que dispara el bug, no un fixture
20
+ // inventado.
21
+ const realBlockScalarSkill = `---
22
+ name: extract-design-md
23
+ version: "1.0.1"
24
+ description: >-
25
+ Extract a comprehensive design system (DESIGN.md) directly from frontend source
26
+ code — React, Vue, Svelte, Angular, plain HTML/CSS, or any web framework.
27
+ ---
28
+
29
+ # Extract Design MD
30
+
31
+ Body content.
32
+ `;
33
+ describe('parseSkillSource: descripciones en block scalar (regresion real del registry)', () => {
34
+ it('lee el texto de las lineas indentadas de un `>-`, en vez de tratar el indicador como el valor', () => {
35
+ const { description } = (0, skill_source_1.parseSkillSource)(realBlockScalarSkill);
36
+ expect(description).toBe('Extract a comprehensive design system (DESIGN.md) directly from frontend source ' +
37
+ 'code — React, Vue, Svelte, Angular, plain HTML/CSS, or any web framework.');
38
+ });
39
+ it('pliega (folded, `>`) las lineas en espacios — no conserva los saltos de linea del fuente', () => {
40
+ const source = `---
41
+ name: folded
42
+ description: >
43
+ primera linea
44
+ segunda linea
45
+ ---
46
+
47
+ Body.
48
+ `;
49
+ expect((0, skill_source_1.parseSkillSource)(source).description).toBe('primera linea segunda linea');
50
+ });
51
+ it('conserva los saltos de linea de un literal (`|`) — semantica YAML distinta de `>`', () => {
52
+ const source = `---
53
+ name: literal
54
+ description: |
55
+ primera linea
56
+ segunda linea
57
+ ---
58
+
59
+ Body.
60
+ `;
61
+ expect((0, skill_source_1.parseSkillSource)(source).description).toBe('primera linea\nsegunda linea');
62
+ });
63
+ it('trata una linea en blanco dentro de un folded como separador de parrafo, no como espacio', () => {
64
+ const source = `---
65
+ name: folded-parrafos
66
+ description: >-
67
+ parrafo uno
68
+
69
+ parrafo dos
70
+ ---
71
+
72
+ Body.
73
+ `;
74
+ expect((0, skill_source_1.parseSkillSource)(source).description).toBe('parrafo uno\nparrafo dos');
75
+ });
76
+ it('no absorbe las claves siguientes del frontmatter como parte del bloque (corta en la primera linea no indentada)', () => {
77
+ const source = `---
78
+ description: >-
79
+ solo esto pertenece al bloque
80
+ name: no-soy-parte-del-bloque
81
+ version: "9.9.9"
82
+ ---
83
+
84
+ Body.
85
+ `;
86
+ expect((0, skill_source_1.parseSkillSource)(source).description).toBe('solo esto pertenece al bloque');
87
+ });
88
+ it('resuelve el bloque igual con line endings CRLF (el repo es CRLF-tolerante y corre CI en windows-latest)', () => {
89
+ const source = '---\r\nname: crlf\r\ndescription: >-\r\n linea uno\r\n linea dos\r\n---\r\n\r\nBody aqui.\r\n';
90
+ expect((0, skill_source_1.parseSkillSource)(source).description).toBe('linea uno linea dos');
91
+ });
92
+ it('sigue lanzando si el bloque esta genuinamente vacio — un indicador sin lineas indentadas NO es una descripcion', () => {
93
+ const source = `---
94
+ name: bloque-vacio
95
+ description: >-
96
+ name: otra-clave
97
+ ---
98
+
99
+ Body.
100
+ `;
101
+ expect(() => (0, skill_source_1.parseSkillSource)(source)).toThrow(/non-empty description/);
102
+ });
103
+ });
104
+ describe('renderers: el skill real del registry se renderiza para ambos providers', () => {
105
+ it('renderCursorMdc emite la descripcion real, nunca el indicador `>-` literal', () => {
106
+ const rendered = (0, cursor_mdc_1.renderCursorMdc)(realBlockScalarSkill);
107
+ expect(rendered).toContain('Extract a comprehensive design system');
108
+ // El modo de fallo que este test ancla: emitir el indicador crudo.
109
+ expect(rendered).not.toContain('description: >-');
110
+ expect(rendered).not.toContain('description: ""');
111
+ });
112
+ it('renderCopilotInstructions no crashea (no necesita la descripcion, pero parseSkillSource la exigia igual)', () => {
113
+ const rendered = (0, copilot_instructions_1.renderCopilotInstructions)(realBlockScalarSkill);
114
+ expect(rendered).toContain('applyTo: "**"');
115
+ expect(rendered).toContain('# Extract Design MD');
116
+ });
117
+ });
@@ -14,6 +14,31 @@ const path_1 = __importDefault(require("path"));
14
14
  const os_1 = __importDefault(require("os"));
15
15
  const child_process_1 = require("child_process");
16
16
  const GIT = (cwd, cmd) => (0, child_process_1.execSync)(`git -c user.email=t@t.t -c user.name=t -c tag.gpgSign=false ${cmd}`, { cwd, stdio: 'pipe' });
17
+ // Real `git init`/clone-adjacent work per test, several times over — on
18
+ // windows-latest CI this is measurably slower than on POSIX and the jest
19
+ // default of 5000ms proved too tight in real CI (regression: real
20
+ // windows-latest run, "Exceeded timeout of 5000 ms"). Same class of issue
21
+ // already fixed in registries-sync.test.ts earlier this session — this
22
+ // sibling file has the identical real-git-fixture pattern and was missed.
23
+ jest.setTimeout(30000);
24
+ /** git en win32 puede mantener un handle abierto sobre `.git/hooks`/`.git/objects`
25
+ * por un instante despues de que el proceso `git` retorna — rmSync inmediato
26
+ * entonces produce EBUSY/ENOTEMPTY (regresion: real windows-latest CI). Mismo
27
+ * patron ya aplicado en registries-sync.test.ts y runner.test.ts. */
28
+ async function rmSyncRetryingBusy(target, attempts = 10, delayMs = 100) {
29
+ for (let i = 0; i < attempts; i++) {
30
+ try {
31
+ fs_1.default.rmSync(target, { recursive: true, force: true });
32
+ return;
33
+ }
34
+ catch (error) {
35
+ const code = error.code;
36
+ if ((code !== 'EBUSY' && code !== 'ENOTEMPTY') || i === attempts - 1)
37
+ throw error;
38
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
39
+ }
40
+ }
41
+ }
17
42
  function makeRegistryWithManifest(base, name, manifest) {
18
43
  const dir = path_1.default.join(base, name);
19
44
  fs_1.default.mkdirSync(dir, { recursive: true });
@@ -37,9 +62,9 @@ describe('verifyMinCliVersions (gate de awm sync / awm update)', () => {
37
62
  process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
38
63
  jest.resetModules();
39
64
  });
40
- afterEach(() => {
41
- fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
42
- fs_1.default.rmSync(tmpWork, { recursive: true, force: true });
65
+ afterEach(async () => {
66
+ await rmSyncRetryingBusy(tmpHome);
67
+ await rmSyncRetryingBusy(tmpWork);
43
68
  if (originalHome === undefined)
44
69
  delete process.env.HOME;
45
70
  else
@@ -0,0 +1,203 @@
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
+ // cli/tests/integration/copilot-init-isolated.test.ts
7
+ //
8
+ // Regression E2E for a confirmed, 100%-reproducible production bug:
9
+ // `awm init --agent copilot` crashed deterministically with
10
+ // "machine.devCore: skill global scope is not supported by Copilot..." and
11
+ // rolled back the ENTIRE init transaction (even project-local artifacts like
12
+ // AGENTS.md).
13
+ //
14
+ // Root cause: Copilot has no global skill directory
15
+ // (providers/index.ts's `skill.global === null`), so
16
+ // diagnostics/context.ts's `gatherMachine` computed `devCore.present` as
17
+ // permanently `false` for Copilot (no way to ever satisfy it). That made
18
+ // `stepDevCore` (init/steps.ts) fall through on every run and call
19
+ // `installBundle` at GLOBAL scope, which throws for a provider with no
20
+ // global skill mechanism — an install that was always guaranteed to fail.
21
+ //
22
+ // Fixed in diagnostics/context.ts: when `skillsDir === null`, there is no
23
+ // global-scope devCore/baseline-bundle concept to satisfy for this agent at
24
+ // all, so `devCorePresent` is now reported `true` (N/A treated as
25
+ // satisfied) — mirroring how `globalSkills` already treats the same
26
+ // null-skillsDir case as trivially healthy. `stepDevCore`'s existing
27
+ // `present && brokenLinks.length === 0 -> skip` guard then naturally never
28
+ // falls through to the doomed install, with no redundant guard needed in
29
+ // steps.ts itself.
30
+ //
31
+ // This test lets `awm init --agent copilot` run its REAL, unstubbed
32
+ // pipeline end-to-end against a hand-seeded registry fixture (same pattern
33
+ // as codex-provider-isolated.test.ts) — never against the real `~/.awm`
34
+ // (CLAUDE.md's "never touch ~/.awm" rule; HOME/AWM_HOME are isolated
35
+ // tmpdirs for the whole test).
36
+ const fs_1 = __importDefault(require("fs"));
37
+ const os_1 = __importDefault(require("os"));
38
+ const path_1 = __importDefault(require("path"));
39
+ describe('copilot provider — isolated home E2E (devCore global-scope guard regression)', () => {
40
+ let tmpHome;
41
+ let tmpWork;
42
+ let originalHome;
43
+ let originalAwmHome;
44
+ let writeSpy;
45
+ beforeEach(() => {
46
+ tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-copilot-e2e-home-'));
47
+ tmpWork = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-copilot-e2e-work-'));
48
+ originalHome = process.env.HOME;
49
+ originalAwmHome = process.env.AWM_HOME;
50
+ process.env.HOME = tmpHome;
51
+ process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
52
+ jest.resetModules();
53
+ writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
54
+ });
55
+ afterEach(() => {
56
+ writeSpy.mockRestore();
57
+ fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
58
+ fs_1.default.rmSync(tmpWork, { recursive: true, force: true });
59
+ if (originalHome === undefined)
60
+ delete process.env.HOME;
61
+ else
62
+ process.env.HOME = originalHome;
63
+ if (originalAwmHome === undefined)
64
+ delete process.env.AWM_HOME;
65
+ else
66
+ process.env.AWM_HOME = originalAwmHome;
67
+ });
68
+ /** Same fixture shape as codex-provider-isolated.test.ts's seedPublicRegistryFixture:
69
+ * a real content-root registry (no git repo needed) with the bootstrap skill and one
70
+ * baseline bundle. Copilot needs no hooks (providers/index.ts has no `hooks` config
71
+ * for it) and no native agent renderer (`agent: null`), so the fixture is intentionally
72
+ * smaller than Codex's. */
73
+ function seedPublicRegistryFixture(root) {
74
+ fs_1.default.mkdirSync(path_1.default.join(root, 'hooks'), { recursive: true });
75
+ fs_1.default.writeFileSync(path_1.default.join(root, 'hooks/session-start'), '#!/bin/sh\necho "{}"\n', { mode: 0o755 });
76
+ fs_1.default.writeFileSync(path_1.default.join(root, 'hooks/run-hook.cmd'), '#!/bin/sh\nexec sh "$1"\n', { mode: 0o755 });
77
+ fs_1.default.mkdirSync(path_1.default.join(root, 'skills/using-awm'), { recursive: true });
78
+ fs_1.default.writeFileSync(path_1.default.join(root, 'skills/using-awm/SKILL.md'), '---\nname: using-awm\n---\nMUST invoke skills.');
79
+ fs_1.default.mkdirSync(path_1.default.join(root, 'skills/development-process'), { recursive: true });
80
+ fs_1.default.writeFileSync(path_1.default.join(root, 'skills/development-process/SKILL.md'), '---\nname: development-process\n---\nOrchestrates the dev lifecycle.');
81
+ fs_1.default.writeFileSync(path_1.default.join(root, 'catalog.json'), JSON.stringify({
82
+ version: 1,
83
+ bundles: [{ name: 'dev-core', source: 'bundles/dev-core', version: '1.0.0', scope: 'baseline', visibility: 'public' }],
84
+ }));
85
+ fs_1.default.mkdirSync(path_1.default.join(root, 'bundles/dev-core'), { recursive: true });
86
+ fs_1.default.writeFileSync(path_1.default.join(root, 'bundles/dev-core/bundle.json'), JSON.stringify({
87
+ name: 'dev-core', description: '', version: '1.0.0', scope: 'baseline', visibility: 'public',
88
+ dependsOn: [], skills: ['development-process'], workflows: [], agents: [],
89
+ }));
90
+ fs_1.default.mkdirSync(path_1.default.join(tmpHome, '.awm'), { recursive: true });
91
+ fs_1.default.writeFileSync(path_1.default.join(tmpHome, '.awm/registries.json'), JSON.stringify([{ name: 'baseline', remote: 'https://example.invalid/baseline.git' }], null, 2));
92
+ }
93
+ function readPrefs() {
94
+ return JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpHome, '.awm/preferences.json'), 'utf8'));
95
+ }
96
+ it('completes without crashing/rolling back (real bug repro) and delivers content via AGENTS.md, not a doomed global-scope install', async () => {
97
+ seedPublicRegistryFixture(path_1.default.join(tmpHome, '.awm/registries/baseline'));
98
+ const { runInit } = require('../../src/commands/init');
99
+ // Real, full, unstubbed init for copilot. Before the fix, this threw
100
+ // "machine.devCore: skill global scope is not supported by Copilot: GitHub
101
+ // Copilot has no user-level skill discovery mechanism — skills must be
102
+ // installed per-project." and rolled back the whole transaction (exit 2,
103
+ // AGENTS.md reverted, preferences never committed).
104
+ const code = await runInit({ cwd: tmpWork, yes: true, agent: 'copilot' });
105
+ // 0 (healthy) or 1 (degraded-but-completed) — NOT 2 (failed/rolled back).
106
+ expect(code).toBeLessThanOrEqual(1);
107
+ // The transaction actually committed: copilot is now a real enabled agent.
108
+ expect(readPrefs().enabledAgents).toEqual(['copilot']);
109
+ // Copilot has no global skill directory at all — confirm nothing was
110
+ // (wrongly) written there, and no ~/.github tree was invented.
111
+ expect(fs_1.default.existsSync(path_1.default.join(tmpHome, '.github'))).toBe(false);
112
+ // Content delivery for Copilot happens via its managed-agents-md injection
113
+ // mechanism at PROJECT scope (stepContextInjection, globalPath === null ->
114
+ // scope 'local') — confirm that path is unaffected by this fix and still
115
+ // really delivers the using-awm skill content into the project AGENTS.md.
116
+ const agentsMd = fs_1.default.readFileSync(path_1.default.join(tmpWork, 'AGENTS.md'), 'utf8');
117
+ expect(agentsMd).toContain('MUST invoke skills.');
118
+ });
119
+ // Regression for the SAME structural bug as the devCore one above, in the
120
+ // sibling `ambient` computation (diagnostics/context.ts's `gatherMachine`,
121
+ // a few lines below devCorePresent): before the fix, `installed` was
122
+ // forced to `[]` unconditionally whenever `skillsDir === null` (Copilot),
123
+ // regardless of what `~/.awm/config.json`'s `ambient` array wanted. That
124
+ // made stepAmbient (init/steps.ts) treat every wanted ambient bundle as
125
+ // permanently missing and call installBundle at GLOBAL scope for
126
+ // Copilot — throwing "skill global scope is not supported by Copilot"
127
+ // and rolling back the whole init transaction, exactly like the devCore
128
+ // bug. Nothing in the current CLI writes `ambient` into
129
+ // `~/.awm/config.json` automatically, but it IS read unconditionally by
130
+ // production code and is a real, documented mechanism a user/script can
131
+ // populate — so this hand-seeds it, same as a real machine config would.
132
+ it('with a machine-level ambient bundle configured, completes without crashing/rolling back (ambient sibling of the devCore bug)', async () => {
133
+ const registryRoot = path_1.default.join(tmpHome, '.awm/registries/baseline');
134
+ seedPublicRegistryFixture(registryRoot);
135
+ // Add an ambient-scope bundle to the registry fixture so `wanted`
136
+ // resolves to real skills (resolveBundleSkills needs the bundle to
137
+ // actually exist in `bundles`, discovered via discoverAllBundles()).
138
+ fs_1.default.mkdirSync(path_1.default.join(registryRoot, 'skills/ambient-skill'), { recursive: true });
139
+ fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'skills/ambient-skill/SKILL.md'), '---\nname: ambient-skill\ndescription: An ambient bundle skill.\n---\nAmbient.');
140
+ const catalog = JSON.parse(fs_1.default.readFileSync(path_1.default.join(registryRoot, 'catalog.json'), 'utf8'));
141
+ catalog.bundles.push({ name: 'personal-notion', source: 'bundles/personal-notion', version: '1.0.0', scope: 'ambient', visibility: 'public' });
142
+ fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'catalog.json'), JSON.stringify(catalog));
143
+ fs_1.default.mkdirSync(path_1.default.join(registryRoot, 'bundles/personal-notion'), { recursive: true });
144
+ fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'bundles/personal-notion/bundle.json'), JSON.stringify({
145
+ name: 'personal-notion', description: '', version: '1.0.0', scope: 'ambient', visibility: 'public',
146
+ dependsOn: [], skills: ['ambient-skill'], workflows: [], agents: [],
147
+ }));
148
+ // Seed the machine-level ambient config exactly as a user/script would.
149
+ fs_1.default.writeFileSync(path_1.default.join(tmpHome, '.awm/config.json'), JSON.stringify({ ambient: ['personal-notion'] }));
150
+ const { runInit } = require('../../src/commands/init');
151
+ // Before the fix, this threw "machine.ambient: skill global scope is
152
+ // not supported by Copilot..." (same class as the devCore crash) and
153
+ // rolled back the whole transaction (exit 2, AGENTS.md reverted,
154
+ // preferences never committed).
155
+ const code = await runInit({ cwd: tmpWork, yes: true, agent: 'copilot' });
156
+ // 0 (healthy) or 1 (degraded-but-completed) — NOT 2 (failed/rolled back).
157
+ expect(code).toBeLessThanOrEqual(1);
158
+ // The transaction actually committed.
159
+ expect(readPrefs().enabledAgents).toEqual(['copilot']);
160
+ // Copilot has no global skill directory at all — confirm the ambient
161
+ // bundle was NOT (wrongly) installed at global scope either.
162
+ expect(fs_1.default.existsSync(path_1.default.join(tmpHome, '.github'))).toBe(false);
163
+ });
164
+ it('a subsequent `awm add <bundle> -a copilot` works once init has actually committed', async () => {
165
+ seedPublicRegistryFixture(path_1.default.join(tmpHome, '.awm/registries/baseline'));
166
+ // Add a second, non-baseline bundle the project can explicitly activate
167
+ // locally (Copilot only supports local-scope skill delivery — providers/
168
+ // index.ts's `skill.local: '.github/instructions'`).
169
+ const registryRoot = path_1.default.join(tmpHome, '.awm/registries/baseline');
170
+ fs_1.default.mkdirSync(path_1.default.join(registryRoot, 'skills/extra-skill'), { recursive: true });
171
+ fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'skills/extra-skill/SKILL.md'), '---\nname: extra-skill\ndescription: An extra project skill.\n---\nExtra.');
172
+ fs_1.default.mkdirSync(path_1.default.join(registryRoot, 'bundles/extra'), { recursive: true });
173
+ fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'bundles/extra/bundle.json'), JSON.stringify({
174
+ name: 'extra', description: '', version: '1.0.0', scope: 'project', visibility: 'public',
175
+ dependsOn: [], skills: ['extra-skill'], workflows: [], agents: [],
176
+ }));
177
+ const catalog = JSON.parse(fs_1.default.readFileSync(path_1.default.join(registryRoot, 'catalog.json'), 'utf8'));
178
+ catalog.bundles.push({ name: 'extra', source: 'bundles/extra', version: '1.0.0', scope: 'project', visibility: 'public' });
179
+ fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'catalog.json'), JSON.stringify(catalog));
180
+ // findProjectRoot needs a real project marker (.git/, package.json, or
181
+ // .awm/profile.json) — a bare tmpdir isn't one (same pattern as
182
+ // tests/commands/add.test.ts).
183
+ fs_1.default.writeFileSync(path_1.default.join(tmpWork, 'package.json'), JSON.stringify({ name: 'fixture' }));
184
+ const { runInit } = require('../../src/commands/init');
185
+ const initCode = await runInit({ cwd: tmpWork, yes: true, agent: 'copilot' });
186
+ expect(initCode).toBeLessThanOrEqual(1);
187
+ expect(readPrefs().enabledAgents).toEqual(['copilot']);
188
+ // This was "permanently blocked" per the bug report — it requires a prior
189
+ // COMMITTED `awm init --agent copilot` (resolveAgentTargetsOrError rejects
190
+ // any agent not in enabledAgents), which never happened before the fix
191
+ // because every copilot init rolled back before saving preferences.
192
+ const { runAddBundleCore } = require('../../src/commands/add');
193
+ const { loadPreferences } = require('../../src/utils/config');
194
+ const { discoverAllBundles } = require('../../src/core/bundles');
195
+ const { prefs } = loadPreferences('copilot');
196
+ const bundles = discoverAllBundles();
197
+ const result = runAddBundleCore({ name: 'extra', agent: 'copilot', cwd: tmpWork }, prefs, bundles);
198
+ expect(result.code).toBe(0);
199
+ expect(result.result?.installed.length).toBeGreaterThan(0);
200
+ const instructionsDir = path_1.default.join(tmpWork, '.github/instructions');
201
+ expect(fs_1.default.existsSync(instructionsDir)).toBe(true);
202
+ });
203
+ });
@@ -44,6 +44,15 @@ describe('truncate', () => {
44
44
  it('returns empty for non-positive width', () => {
45
45
  expect((0, text_1.truncate)('abc', 0)).toBe('');
46
46
  });
47
+ it('colapsa saltos de linea a un espacio — la celda es de UNA linea', () => {
48
+ // Regresion: `displayWidth` cuenta `\n` como ancho 1, asi que una string
49
+ // multilinea corta pasaba el chequeo y se emitia cruda, partiendo la fila
50
+ // de `awm list` y desalineando todo lo siguiente. Alcanzable desde que el
51
+ // lector de frontmatter resuelve block scalars literales (`|`).
52
+ expect((0, text_1.truncate)('linea uno\nlinea dos', 80)).toBe('linea uno linea dos');
53
+ expect((0, text_1.truncate)('a\r\n b', 80)).toBe('a b');
54
+ expect((0, text_1.truncate)('linea uno\nlinea dos', 12)).toBe('linea uno l…');
55
+ });
47
56
  });
48
57
  describe('wrap', () => {
49
58
  it('breaks text at word boundaries within width', () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-workflow-manager",
3
- "version": "3.13.3",
3
+ "version": "3.13.5",
4
4
  "main": "dist/src/index.js",
5
5
  "bin": {
6
6
  "awm": "./dist/src/index.js"
@@ -43,10 +43,12 @@
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/jest": "^30.0.0",
46
+ "@types/js-yaml": "4.0.9",
46
47
  "@types/node": "^25.3.0",
47
48
  "dependency-cruiser": "^17.4.3",
48
49
  "eslint": "^10.4.1",
49
50
  "jest": "^30.2.0",
51
+ "js-yaml": "4.1.0",
50
52
  "ts-jest": "^29.4.6",
51
53
  "typescript": "^5.9.3"
52
54
  }