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.
- package/dist/src/core/diagnostics/context.js +33 -9
- package/dist/src/core/discovery.js +13 -21
- package/dist/src/core/export/transform.js +30 -25
- package/dist/src/core/frontmatter.js +223 -0
- package/dist/src/core/journal/process.js +22 -5
- package/dist/src/core/renderers/canonical-agent.js +33 -2
- package/dist/src/core/renderers/skill-source.js +11 -19
- package/dist/src/ui/text.js +10 -2
- package/dist/tests/core/diagnostics/context.test.js +44 -0
- package/dist/tests/core/discovery.test.js +10 -1
- package/dist/tests/core/export/transform.test.js +110 -21
- package/dist/tests/core/frontmatter-description-vs-yaml.test.js +118 -0
- package/dist/tests/core/init/steps.test.js +55 -0
- package/dist/tests/core/journal/process.test.js +54 -1
- package/dist/tests/core/renderers/canonical-agent.test.js +36 -0
- package/dist/tests/core/renderers/cursor-mdc.test.js +37 -7
- package/dist/tests/core/renderers/skill-source-block-scalar.test.js +117 -0
- package/dist/tests/core/sync-gates.test.js +28 -3
- package/dist/tests/integration/copilot-init-isolated.test.js +203 -0
- package/dist/tests/ui/text.test.js +9 -0
- package/package.json +3 -1
|
@@ -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
|
-
|
|
42
|
-
|
|
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
|
+
"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
|
}
|