agentic-workflow-manager 2.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.
- package/README.md +62 -0
- package/dist/src/commands/doctor.js +77 -0
- package/dist/src/commands/hooks/index.js +118 -0
- package/dist/src/commands/hooks/install.js +113 -0
- package/dist/src/commands/hooks/resync.js +50 -0
- package/dist/src/commands/hooks/status.js +74 -0
- package/dist/src/commands/hooks/uninstall.js +59 -0
- package/dist/src/commands/init.js +181 -0
- package/dist/src/commands/ledger/index.js +73 -0
- package/dist/src/commands/pin.js +75 -0
- package/dist/src/commands/registry/add.js +62 -0
- package/dist/src/commands/registry/index.js +166 -0
- package/dist/src/commands/registry/install-bundles.js +36 -0
- package/dist/src/commands/registry/remove.js +18 -0
- package/dist/src/commands/registry/status.js +31 -0
- package/dist/src/commands/sensors/baseline.js +93 -0
- package/dist/src/commands/sensors/formatters/eslint.js +29 -0
- package/dist/src/commands/sensors/formatters/generic.js +8 -0
- package/dist/src/commands/sensors/formatters/semgrep.js +18 -0
- package/dist/src/commands/sensors/formatters/test.js +12 -0
- package/dist/src/commands/sensors/formatters/tsc.js +23 -0
- package/dist/src/commands/sensors/index.js +94 -0
- package/dist/src/commands/sensors/init.js +112 -0
- package/dist/src/commands/sensors/install.js +78 -0
- package/dist/src/commands/sensors/run.js +217 -0
- package/dist/src/commands/sensors/status.js +85 -0
- package/dist/src/commands/sensors/types.js +2 -0
- package/dist/src/core/bundle-install.js +116 -0
- package/dist/src/core/bundles.js +122 -0
- package/dist/src/core/cli-version.js +30 -0
- package/dist/src/core/context/materializer.js +30 -0
- package/dist/src/core/context/orchestrator.js +75 -0
- package/dist/src/core/context/project-constitution-inject.js +47 -0
- package/dist/src/core/context/provider.js +29 -0
- package/dist/src/core/context/regenerate.js +61 -0
- package/dist/src/core/context/strategies/config-instructions.js +73 -0
- package/dist/src/core/context/strategies/hook-merge.js +23 -0
- package/dist/src/core/context/strategies/strategy.js +2 -0
- package/dist/src/core/context/types.js +2 -0
- package/dist/src/core/diagnostics/checks.js +141 -0
- package/dist/src/core/diagnostics/context.js +181 -0
- package/dist/src/core/diagnostics/types.js +2 -0
- package/dist/src/core/discovery.js +135 -0
- package/dist/src/core/executor.js +41 -0
- package/dist/src/core/init/detector.js +67 -0
- package/dist/src/core/init/orchestrator.js +54 -0
- package/dist/src/core/init/steps.js +279 -0
- package/dist/src/core/init/types.js +2 -0
- package/dist/src/core/ledger/store.js +73 -0
- package/dist/src/core/ledger/types.js +2 -0
- package/dist/src/core/miro.js +314 -0
- package/dist/src/core/profile-pins.js +36 -0
- package/dist/src/core/profile.js +146 -0
- package/dist/src/core/registries.js +205 -0
- package/dist/src/core/registry.js +28 -0
- package/dist/src/core/skill-integrity.js +97 -0
- package/dist/src/core/story-map-parser.js +83 -0
- package/dist/src/core/update-check-worker.js +10 -0
- package/dist/src/core/update-check.js +106 -0
- package/dist/src/core/versioning.js +112 -0
- package/dist/src/index.js +689 -0
- package/dist/src/providers/index.js +63 -0
- package/dist/src/utils/config.js +35 -0
- package/dist/src/utils/grouping.js +51 -0
- package/dist/src/utils/registry-view.js +154 -0
- package/dist/tests/commands/doctor.test.js +98 -0
- package/dist/tests/commands/hooks/install.test.js +149 -0
- package/dist/tests/commands/hooks/resync.test.js +135 -0
- package/dist/tests/commands/hooks/router.test.js +26 -0
- package/dist/tests/commands/hooks/status.test.js +88 -0
- package/dist/tests/commands/hooks/uninstall.test.js +104 -0
- package/dist/tests/commands/init.test.js +87 -0
- package/dist/tests/commands/ledger/index.test.js +64 -0
- package/dist/tests/commands/pin.test.js +72 -0
- package/dist/tests/commands/registry/add.test.js +223 -0
- package/dist/tests/commands/registry/install-bundles.test.js +87 -0
- package/dist/tests/commands/registry/remove.test.js +49 -0
- package/dist/tests/commands/registry/status.test.js +43 -0
- package/dist/tests/commands/sensors/baseline.test.js +106 -0
- package/dist/tests/commands/sensors/formatters/eslint.test.js +32 -0
- package/dist/tests/commands/sensors/formatters/semgrep.test.js +38 -0
- package/dist/tests/commands/sensors/formatters/tsc.test.js +25 -0
- package/dist/tests/commands/sensors/index.test.js +18 -0
- package/dist/tests/commands/sensors/init.test.js +137 -0
- package/dist/tests/commands/sensors/install.test.js +60 -0
- package/dist/tests/commands/sensors/router.test.js +23 -0
- package/dist/tests/commands/sensors/run.test.js +353 -0
- package/dist/tests/commands/sensors/status.test.js +98 -0
- package/dist/tests/core/base-remote.test.js +70 -0
- package/dist/tests/core/bundle-install.test.js +198 -0
- package/dist/tests/core/bundles-multiroot.test.js +68 -0
- package/dist/tests/core/bundles-overrides.test.js +49 -0
- package/dist/tests/core/bundles.test.js +96 -0
- package/dist/tests/core/cli-version.test.js +17 -0
- package/dist/tests/core/context/materializer.test.js +46 -0
- package/dist/tests/core/context/orchestrator.test.js +127 -0
- package/dist/tests/core/context/project-constitution-inject.test.js +82 -0
- package/dist/tests/core/context/provider.test.js +45 -0
- package/dist/tests/core/context/regenerate.test.js +106 -0
- package/dist/tests/core/context/strategies/config-instructions.test.js +88 -0
- package/dist/tests/core/context/strategies/hook-merge.test.js +71 -0
- package/dist/tests/core/context/types.test.js +15 -0
- package/dist/tests/core/diagnostics/checks.test.js +208 -0
- package/dist/tests/core/diagnostics/context.test.js +170 -0
- package/dist/tests/core/discovery-multiroot.test.js +63 -0
- package/dist/tests/core/discovery-overrides.test.js +105 -0
- package/dist/tests/core/discovery.test.js +113 -0
- package/dist/tests/core/executor.test.js +44 -0
- package/dist/tests/core/init/detector.test.js +56 -0
- package/dist/tests/core/init/orchestrator.test.js +122 -0
- package/dist/tests/core/init/steps.test.js +363 -0
- package/dist/tests/core/ledger/gitignore.test.js +13 -0
- package/dist/tests/core/ledger/store.test.js +122 -0
- package/dist/tests/core/miro-layout.test.js +150 -0
- package/dist/tests/core/profile-pins.test.js +131 -0
- package/dist/tests/core/profile-registries.test.js +59 -0
- package/dist/tests/core/profile.test.js +110 -0
- package/dist/tests/core/registries-capability.test.js +52 -0
- package/dist/tests/core/registries-seed.test.js +66 -0
- package/dist/tests/core/registries-sync.test.js +205 -0
- package/dist/tests/core/registries.test.js +91 -0
- package/dist/tests/core/registry-manifest.test.js +95 -0
- package/dist/tests/core/registry-versioned-sync.test.js +113 -0
- package/dist/tests/core/registry.test.js +51 -0
- package/dist/tests/core/skill-integrity.test.js +126 -0
- package/dist/tests/core/story-map-parser.test.js +136 -0
- package/dist/tests/core/sync-gates.test.js +97 -0
- package/dist/tests/core/update-check.test.js +106 -0
- package/dist/tests/core/versioning.test.js +169 -0
- package/dist/tests/integration/pack-e2e.test.js +50 -0
- package/dist/tests/providers/hooks-config.test.js +45 -0
- package/dist/tests/providers/index.test.js +58 -0
- package/dist/tests/providers/injection-config.test.js +25 -0
- package/dist/tests/registry/b3-ledger-wiring.test.js +74 -0
- package/dist/tests/registry/catalog-consistency.test.js +47 -0
- package/dist/tests/registry/design-skills.test.js +146 -0
- package/dist/tests/registry/prose-agnostic.test.js +18 -0
- package/dist/tests/registry/sensor-packs.test.js +45 -0
- package/dist/tests/registry/skill-versions.test.js +26 -0
- package/dist/tests/registry/using-awm.test.js +39 -0
- package/dist/tests/utils/config.test.js +31 -0
- package/dist/tests/utils/grouping.test.js +43 -0
- package/dist/tests/utils/registry-view-overrides.test.js +41 -0
- package/dist/tests/utils/registry-view.test.js +156 -0
- package/package.json +49 -0
|
@@ -0,0 +1,126 @@
|
|
|
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 fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const skill_integrity_1 = require("../../src/core/skill-integrity");
|
|
10
|
+
describe('skill-integrity multi-root', () => {
|
|
11
|
+
let tmp;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
tmp = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-integrity-'));
|
|
14
|
+
jest.resetModules();
|
|
15
|
+
});
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
fs_1.default.rmSync(tmp, { recursive: true, force: true });
|
|
18
|
+
});
|
|
19
|
+
it('repairGlobalSkills re-links a dangling symlink to the FIRST root that has the skill', () => {
|
|
20
|
+
const skillsDir = path_1.default.join(tmp, 'installed');
|
|
21
|
+
const rootA = path_1.default.join(tmp, 'base');
|
|
22
|
+
const rootB = path_1.default.join(tmp, 'personal');
|
|
23
|
+
fs_1.default.mkdirSync(skillsDir, { recursive: true });
|
|
24
|
+
fs_1.default.mkdirSync(path_1.default.join(rootB, 'skills', 'mine'), { recursive: true });
|
|
25
|
+
fs_1.default.writeFileSync(path_1.default.join(rootB, 'skills', 'mine', 'SKILL.md'), '---\nname: mine\n---\n');
|
|
26
|
+
// dangling symlink: points to a deleted target
|
|
27
|
+
fs_1.default.symlinkSync(path_1.default.join(tmp, 'gone', 'mine'), path_1.default.join(skillsDir, 'mine'), 'dir');
|
|
28
|
+
const { repairGlobalSkills: repair } = require('../../src/core/skill-integrity');
|
|
29
|
+
const r = repair(skillsDir, [rootA, rootB]);
|
|
30
|
+
expect(r.relinked).toEqual(['mine']);
|
|
31
|
+
expect(fs_1.default.realpathSync(path_1.default.join(skillsDir, 'mine'))).toBe(fs_1.default.realpathSync(path_1.default.join(rootB, 'skills', 'mine')));
|
|
32
|
+
});
|
|
33
|
+
it('classifyGlobalSkills marks dead when NO root has the skill', () => {
|
|
34
|
+
const skillsDir = path_1.default.join(tmp, 'installed');
|
|
35
|
+
fs_1.default.mkdirSync(skillsDir, { recursive: true });
|
|
36
|
+
fs_1.default.symlinkSync(path_1.default.join(tmp, 'gone', 'nope'), path_1.default.join(skillsDir, 'nope'), 'dir');
|
|
37
|
+
const { classifyGlobalSkills: classify } = require('../../src/core/skill-integrity');
|
|
38
|
+
const c = classify(skillsDir, [path_1.default.join(tmp, 'base')]);
|
|
39
|
+
expect(c.dead).toEqual(['nope']);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
function setup() {
|
|
43
|
+
const tmp = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-integrity-'));
|
|
44
|
+
const skillsDir = path_1.default.join(tmp, 'claude-skills');
|
|
45
|
+
const registryContentDir = path_1.default.join(tmp, 'registry');
|
|
46
|
+
fs_1.default.mkdirSync(skillsDir, { recursive: true });
|
|
47
|
+
fs_1.default.mkdirSync(path_1.default.join(registryContentDir, 'skills'), { recursive: true });
|
|
48
|
+
return { skillsDir, registryContentDir };
|
|
49
|
+
}
|
|
50
|
+
function makeRegistrySkill(registryContentDir, name) {
|
|
51
|
+
const dir = path_1.default.join(registryContentDir, 'skills', name);
|
|
52
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
53
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, 'SKILL.md'), `# ${name}\n`);
|
|
54
|
+
return dir;
|
|
55
|
+
}
|
|
56
|
+
describe('classifyGlobalSkills', () => {
|
|
57
|
+
it('classifies valid / repairable / dead', () => {
|
|
58
|
+
const { skillsDir, registryContentDir } = setup();
|
|
59
|
+
// valid: symlink a una skill que existe en el registry
|
|
60
|
+
const okTarget = makeRegistrySkill(registryContentDir, 'alpha');
|
|
61
|
+
fs_1.default.symlinkSync(okTarget, path_1.default.join(skillsDir, 'alpha'), 'dir');
|
|
62
|
+
// repairable: symlink colgante, pero la skill SÍ existe en el registry
|
|
63
|
+
makeRegistrySkill(registryContentDir, 'beta');
|
|
64
|
+
fs_1.default.symlinkSync(path_1.default.join('/nonexistent/old-root/beta'), path_1.default.join(skillsDir, 'beta'), 'dir');
|
|
65
|
+
// dead: symlink colgante y la skill NO existe en el registry
|
|
66
|
+
fs_1.default.symlinkSync(path_1.default.join('/nonexistent/old-root/gamma'), path_1.default.join(skillsDir, 'gamma'), 'dir');
|
|
67
|
+
const result = (0, skill_integrity_1.classifyGlobalSkills)(skillsDir, [registryContentDir]);
|
|
68
|
+
expect(result.valid).toEqual(['alpha']);
|
|
69
|
+
expect(result.repairable).toEqual(['beta']);
|
|
70
|
+
expect(result.dead).toEqual(['gamma']);
|
|
71
|
+
});
|
|
72
|
+
it('returns empty arrays when the skills dir does not exist', () => {
|
|
73
|
+
const result = (0, skill_integrity_1.classifyGlobalSkills)('/nonexistent/dir', ['/also/nonexistent']);
|
|
74
|
+
expect(result).toEqual({ valid: [], repairable: [], dead: [] });
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
describe('reconcileAllSkillLinks (#4 — awm update, all providers)', () => {
|
|
78
|
+
it('repairs every provider dir that exists and skips absent ones', () => {
|
|
79
|
+
const home = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-home-'));
|
|
80
|
+
const prevHome = process.env.HOME;
|
|
81
|
+
process.env.HOME = home;
|
|
82
|
+
try {
|
|
83
|
+
jest.resetModules();
|
|
84
|
+
const { reconcileAllSkillLinks } = require('../../src/core/skill-integrity');
|
|
85
|
+
// OpenCode dir exists with a dead orphan; Claude dir does NOT exist.
|
|
86
|
+
const ocSkills = path_1.default.join(home, '.agents/skills');
|
|
87
|
+
fs_1.default.mkdirSync(ocSkills, { recursive: true });
|
|
88
|
+
fs_1.default.symlinkSync(path_1.default.join(home, 'no-such-target'), path_1.default.join(ocSkills, 'ghost'), 'dir');
|
|
89
|
+
const res = reconcileAllSkillLinks([path_1.default.join(home, 'no-registry')]);
|
|
90
|
+
const oc = res.find((r) => r.agent === 'opencode');
|
|
91
|
+
const cc = res.find((r) => r.agent === 'claude-code');
|
|
92
|
+
expect(oc).toBeTruthy();
|
|
93
|
+
expect(oc.result.pruned).toContain('ghost');
|
|
94
|
+
expect(cc).toBeFalsy(); // claude dir absent → not in results
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
process.env.HOME = prevHome;
|
|
98
|
+
fs_1.default.rmSync(home, { recursive: true, force: true });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
describe('repairGlobalSkills', () => {
|
|
103
|
+
it('re-links repairable to registry content dir and prunes dead; valid untouched; idempotent', () => {
|
|
104
|
+
const { skillsDir, registryContentDir } = setup();
|
|
105
|
+
const okTarget = makeRegistrySkill(registryContentDir, 'alpha');
|
|
106
|
+
fs_1.default.symlinkSync(okTarget, path_1.default.join(skillsDir, 'alpha'), 'dir');
|
|
107
|
+
makeRegistrySkill(registryContentDir, 'beta');
|
|
108
|
+
fs_1.default.symlinkSync(path_1.default.join('/nonexistent/old-root/beta'), path_1.default.join(skillsDir, 'beta'), 'dir');
|
|
109
|
+
fs_1.default.symlinkSync(path_1.default.join('/nonexistent/old-root/gamma'), path_1.default.join(skillsDir, 'gamma'), 'dir');
|
|
110
|
+
const r1 = (0, skill_integrity_1.repairGlobalSkills)(skillsDir, [registryContentDir]);
|
|
111
|
+
expect(r1.relinked).toEqual(['beta']);
|
|
112
|
+
expect(r1.pruned).toEqual(['gamma']);
|
|
113
|
+
// beta ahora apunta al registry content dir y resuelve
|
|
114
|
+
expect(fs_1.default.existsSync(path_1.default.join(skillsDir, 'beta'))).toBe(true);
|
|
115
|
+
expect(fs_1.default.realpathSync(path_1.default.join(skillsDir, 'beta')))
|
|
116
|
+
.toBe(fs_1.default.realpathSync(path_1.default.join(registryContentDir, 'skills', 'beta')));
|
|
117
|
+
// gamma podado
|
|
118
|
+
expect(fs_1.default.existsSync(path_1.default.join(skillsDir, 'gamma'))).toBe(false);
|
|
119
|
+
// alpha intacto
|
|
120
|
+
expect(fs_1.default.existsSync(path_1.default.join(skillsDir, 'alpha'))).toBe(true);
|
|
121
|
+
// idempotente: segunda corrida no cambia nada
|
|
122
|
+
const r2 = (0, skill_integrity_1.repairGlobalSkills)(skillsDir, [registryContentDir]);
|
|
123
|
+
expect(r2.relinked).toEqual([]);
|
|
124
|
+
expect(r2.pruned).toEqual([]);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const story_map_parser_1 = require("../../src/core/story-map-parser");
|
|
4
|
+
const SAMPLE_MARKDOWN = `---
|
|
5
|
+
project: Portal B2B
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Story Map — Portal B2B
|
|
9
|
+
|
|
10
|
+
## Goal
|
|
11
|
+
> Permitir a proveedores gestionar su relación con Cencosud
|
|
12
|
+
|
|
13
|
+
## Personas
|
|
14
|
+
|
|
15
|
+
### Proveedor
|
|
16
|
+
- **Rol:** Externo
|
|
17
|
+
|
|
18
|
+
## Backbone
|
|
19
|
+
|
|
20
|
+
### 🟡 Acceder al portal
|
|
21
|
+
|
|
22
|
+
#### 🔵 Task: Iniciar sesión
|
|
23
|
+
- **[MVP]** Como proveedor, quiero iniciar sesión con mi usuario y contraseña
|
|
24
|
+
- **[Release 2]** Como proveedor, quiero autenticarme con SSO
|
|
25
|
+
|
|
26
|
+
#### 🔵 Task: Seleccionar país y UN
|
|
27
|
+
- **[MVP]** Como proveedor, quiero seleccionar mi país al ingresar
|
|
28
|
+
|
|
29
|
+
### 🟡 Consultar mis datos
|
|
30
|
+
|
|
31
|
+
#### 🔵 Task: Ver dashboard
|
|
32
|
+
- **[MVP]** Como proveedor, quiero ver mis indicadores principales
|
|
33
|
+
|
|
34
|
+
## Release Summary
|
|
35
|
+
|
|
36
|
+
| Release | Stories |
|
|
37
|
+
|---------|---------|
|
|
38
|
+
| MVP | 3 |
|
|
39
|
+
|
|
40
|
+
## Changelog
|
|
41
|
+
|
|
42
|
+
- [2026-03-31] Sesión 1
|
|
43
|
+
`;
|
|
44
|
+
const MARKDOWN_WITH_FRAME_ID = `---
|
|
45
|
+
project: Portal B2B
|
|
46
|
+
miro_frame_id: "existing-id"
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
# Story Map — Portal B2B
|
|
50
|
+
`;
|
|
51
|
+
describe('parseStoryMap', () => {
|
|
52
|
+
it('extracts project name from frontmatter', () => {
|
|
53
|
+
const result = (0, story_map_parser_1.parseStoryMap)(SAMPLE_MARKDOWN);
|
|
54
|
+
expect(result.project).toBe('Portal B2B');
|
|
55
|
+
});
|
|
56
|
+
it('extracts goal text stripping the > prefix', () => {
|
|
57
|
+
const result = (0, story_map_parser_1.parseStoryMap)(SAMPLE_MARKDOWN);
|
|
58
|
+
expect(result.goal).toBe('Permitir a proveedores gestionar su relación con Cencosud');
|
|
59
|
+
});
|
|
60
|
+
it('returns undefined miro_frame_id when not in frontmatter', () => {
|
|
61
|
+
const result = (0, story_map_parser_1.parseStoryMap)(SAMPLE_MARKDOWN);
|
|
62
|
+
expect(result.miro_frame_id).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
it('extracts miro_frame_id from frontmatter when present', () => {
|
|
65
|
+
const result = (0, story_map_parser_1.parseStoryMap)(MARKDOWN_WITH_FRAME_ID);
|
|
66
|
+
expect(result.miro_frame_id).toBe('existing-id');
|
|
67
|
+
});
|
|
68
|
+
it('extracts activities in order', () => {
|
|
69
|
+
const result = (0, story_map_parser_1.parseStoryMap)(SAMPLE_MARKDOWN);
|
|
70
|
+
expect(result.activities).toHaveLength(2);
|
|
71
|
+
expect(result.activities[0].title).toBe('Acceder al portal');
|
|
72
|
+
expect(result.activities[1].title).toBe('Consultar mis datos');
|
|
73
|
+
});
|
|
74
|
+
it('extracts tasks under each activity', () => {
|
|
75
|
+
const result = (0, story_map_parser_1.parseStoryMap)(SAMPLE_MARKDOWN);
|
|
76
|
+
expect(result.activities[0].tasks).toHaveLength(2);
|
|
77
|
+
expect(result.activities[0].tasks[0].title).toBe('Iniciar sesión');
|
|
78
|
+
expect(result.activities[0].tasks[1].title).toBe('Seleccionar país y UN');
|
|
79
|
+
});
|
|
80
|
+
it('extracts stories with their release labels', () => {
|
|
81
|
+
const result = (0, story_map_parser_1.parseStoryMap)(SAMPLE_MARKDOWN);
|
|
82
|
+
const stories = result.activities[0].tasks[0].stories;
|
|
83
|
+
expect(stories).toHaveLength(2);
|
|
84
|
+
expect(stories[0]).toEqual({ release: 'MVP', title: 'Como proveedor, quiero iniciar sesión con mi usuario y contraseña' });
|
|
85
|
+
expect(stories[1]).toEqual({ release: 'Release 2', title: 'Como proveedor, quiero autenticarme con SSO' });
|
|
86
|
+
});
|
|
87
|
+
it('ignores content outside Backbone section', () => {
|
|
88
|
+
const result = (0, story_map_parser_1.parseStoryMap)(SAMPLE_MARKDOWN);
|
|
89
|
+
expect(result.activities.every(a => !a.title.includes('Release') && !a.title.includes('Changelog'))).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
it('falls back to title when project is absent from frontmatter', () => {
|
|
92
|
+
const content = `---
|
|
93
|
+
goal: Some goal
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
# Story Map — Derived From Title
|
|
97
|
+
|
|
98
|
+
## Goal
|
|
99
|
+
> Some goal
|
|
100
|
+
`;
|
|
101
|
+
const result = (0, story_map_parser_1.parseStoryMap)(content);
|
|
102
|
+
expect(result.project).toBe('Derived From Title');
|
|
103
|
+
});
|
|
104
|
+
it('falls back to Unknown when project and title are both absent', () => {
|
|
105
|
+
const content = `---
|
|
106
|
+
goal: Some goal
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Goal
|
|
110
|
+
> Some goal
|
|
111
|
+
`;
|
|
112
|
+
const result = (0, story_map_parser_1.parseStoryMap)(content);
|
|
113
|
+
expect(result.project).toBe('Unknown');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
describe('updateMiroFrameId', () => {
|
|
117
|
+
it('adds miro_frame_id to existing frontmatter and result parses correctly', () => {
|
|
118
|
+
const result = (0, story_map_parser_1.updateMiroFrameId)(SAMPLE_MARKDOWN, 'new-frame-123');
|
|
119
|
+
expect(result).toContain('miro_frame_id: "new-frame-123"');
|
|
120
|
+
// Round-trip: the updated content should parse back to a valid StoryMap with the new id
|
|
121
|
+
const parsed = (0, story_map_parser_1.parseStoryMap)(result);
|
|
122
|
+
expect(parsed.miro_frame_id).toBe('new-frame-123');
|
|
123
|
+
expect(parsed.project).toBe('Portal B2B');
|
|
124
|
+
expect(parsed.activities).toHaveLength(2);
|
|
125
|
+
});
|
|
126
|
+
it('replaces existing miro_frame_id', () => {
|
|
127
|
+
const result = (0, story_map_parser_1.updateMiroFrameId)(MARKDOWN_WITH_FRAME_ID, 'new-frame-456');
|
|
128
|
+
expect(result).toContain('miro_frame_id: "new-frame-456"');
|
|
129
|
+
expect(result).not.toContain('existing-id');
|
|
130
|
+
});
|
|
131
|
+
it('preserves the rest of the frontmatter and body', () => {
|
|
132
|
+
const result = (0, story_map_parser_1.updateMiroFrameId)(SAMPLE_MARKDOWN, 'new-frame-123');
|
|
133
|
+
expect(result).toContain('project: Portal B2B');
|
|
134
|
+
expect(result).toContain('# Story Map — Portal B2B');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// cli/tests/core/sync-gates.test.ts
|
|
3
|
+
//
|
|
4
|
+
// Gate por unidad para verifyMinCliVersions (WS-4).
|
|
5
|
+
// Patrón replicado de profile-pins.test.ts: tmpHome aislado, jest.resetModules(), require tardío.
|
|
6
|
+
// El exit-1 del handler NO se testea aquí — el gate de pins (B1) estableció el patrón
|
|
7
|
+
// de testear la unidad pura; los handlers se testean por integración.
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const os_1 = __importDefault(require("os"));
|
|
15
|
+
const child_process_1 = require("child_process");
|
|
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
|
+
function makeRegistryWithManifest(base, name, manifest) {
|
|
18
|
+
const dir = path_1.default.join(base, name);
|
|
19
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
20
|
+
GIT(dir, 'init -q -b main');
|
|
21
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, 'awm-registry.json'), JSON.stringify(manifest));
|
|
22
|
+
GIT(dir, 'add -A');
|
|
23
|
+
GIT(dir, 'commit -qm init');
|
|
24
|
+
return dir;
|
|
25
|
+
}
|
|
26
|
+
describe('verifyMinCliVersions (gate de awm sync / awm update)', () => {
|
|
27
|
+
let tmpHome;
|
|
28
|
+
let tmpWork;
|
|
29
|
+
let originalHome;
|
|
30
|
+
let originalAwmHome;
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-minCli-home-'));
|
|
33
|
+
tmpWork = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-minCli-work-'));
|
|
34
|
+
originalHome = process.env.HOME;
|
|
35
|
+
originalAwmHome = process.env.AWM_HOME;
|
|
36
|
+
process.env.HOME = tmpHome;
|
|
37
|
+
process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
|
|
38
|
+
jest.resetModules();
|
|
39
|
+
});
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
|
|
42
|
+
fs_1.default.rmSync(tmpWork, { recursive: true, force: true });
|
|
43
|
+
if (originalHome === undefined)
|
|
44
|
+
delete process.env.HOME;
|
|
45
|
+
else
|
|
46
|
+
process.env.HOME = originalHome;
|
|
47
|
+
if (originalAwmHome === undefined)
|
|
48
|
+
delete process.env.AWM_HOME;
|
|
49
|
+
else
|
|
50
|
+
process.env.AWM_HOME = originalAwmHome;
|
|
51
|
+
});
|
|
52
|
+
it('CLI igual a minCliVersion → sin failures', async () => {
|
|
53
|
+
const source = makeRegistryWithManifest(tmpWork, 'src', { minCliVersion: '2.0.0' });
|
|
54
|
+
const { writeRegistriesConfig, syncRegistries, verifyMinCliVersions } = require('../../src/core/registries');
|
|
55
|
+
writeRegistriesConfig([{ name: 'baseline', remote: source }]);
|
|
56
|
+
await syncRegistries();
|
|
57
|
+
// Inyectamos la versión actual como igual al mínimo requerido
|
|
58
|
+
expect(verifyMinCliVersions('2.0.0')).toEqual([]);
|
|
59
|
+
});
|
|
60
|
+
it('CLI mayor que minCliVersion → sin failures', async () => {
|
|
61
|
+
const source = makeRegistryWithManifest(tmpWork, 'src', { minCliVersion: '2.0.0' });
|
|
62
|
+
const { writeRegistriesConfig, syncRegistries, verifyMinCliVersions } = require('../../src/core/registries');
|
|
63
|
+
writeRegistriesConfig([{ name: 'baseline', remote: source }]);
|
|
64
|
+
await syncRegistries();
|
|
65
|
+
expect(verifyMinCliVersions('2.1.0')).toEqual([]);
|
|
66
|
+
});
|
|
67
|
+
it('CLI menor que minCliVersion → 1 failure con name y min', async () => {
|
|
68
|
+
const source = makeRegistryWithManifest(tmpWork, 'src', { minCliVersion: '2.0.0' });
|
|
69
|
+
const { writeRegistriesConfig, syncRegistries, verifyMinCliVersions } = require('../../src/core/registries');
|
|
70
|
+
writeRegistriesConfig([{ name: 'baseline', remote: source }]);
|
|
71
|
+
await syncRegistries();
|
|
72
|
+
const failures = verifyMinCliVersions('1.0.0');
|
|
73
|
+
expect(failures).toHaveLength(1);
|
|
74
|
+
expect(failures[0]).toMatchObject({ name: 'baseline', min: '2.0.0' });
|
|
75
|
+
});
|
|
76
|
+
it('registry sin minCliVersion → sin failures independientemente de la versión del CLI', async () => {
|
|
77
|
+
const source = makeRegistryWithManifest(tmpWork, 'src', { overrides: [] });
|
|
78
|
+
const { writeRegistriesConfig, syncRegistries, verifyMinCliVersions } = require('../../src/core/registries');
|
|
79
|
+
writeRegistriesConfig([{ name: 'baseline', remote: source }]);
|
|
80
|
+
await syncRegistries();
|
|
81
|
+
expect(verifyMinCliVersions('0.0.1')).toEqual([]);
|
|
82
|
+
});
|
|
83
|
+
it('múltiples registries: solo el que falla genera un failure', async () => {
|
|
84
|
+
const src1 = makeRegistryWithManifest(tmpWork, 'r1', { minCliVersion: '2.0.0' });
|
|
85
|
+
const src2 = makeRegistryWithManifest(tmpWork, 'r2', { minCliVersion: '1.0.0' });
|
|
86
|
+
const { writeRegistriesConfig, syncRegistries, verifyMinCliVersions } = require('../../src/core/registries');
|
|
87
|
+
writeRegistriesConfig([
|
|
88
|
+
{ name: 'alpha', remote: src1 },
|
|
89
|
+
{ name: 'beta', remote: src2 },
|
|
90
|
+
]);
|
|
91
|
+
await syncRegistries();
|
|
92
|
+
// CLI 1.5.0: cumple con beta (>=1.0.0) pero NO con alpha (>=2.0.0)
|
|
93
|
+
const failures = verifyMinCliVersions('1.5.0');
|
|
94
|
+
expect(failures).toHaveLength(1);
|
|
95
|
+
expect(failures[0]).toMatchObject({ name: 'alpha', min: '2.0.0' });
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
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/core/update-check.test.ts (dual-tmpdir estándar)
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
// @clack/prompts ships as ESM; mock it so Jest (CommonJS mode) can load update-check
|
|
11
|
+
jest.mock('@clack/prompts', () => ({
|
|
12
|
+
confirm: jest.fn(),
|
|
13
|
+
isCancel: jest.fn(),
|
|
14
|
+
}));
|
|
15
|
+
describe('update-check', () => {
|
|
16
|
+
let tmpHome;
|
|
17
|
+
let tmpWork;
|
|
18
|
+
let originalHome;
|
|
19
|
+
let originalAwmHome;
|
|
20
|
+
let originalNoUpdate;
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-uc-home-'));
|
|
23
|
+
tmpWork = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-uc-work-'));
|
|
24
|
+
originalHome = process.env.HOME;
|
|
25
|
+
originalAwmHome = process.env.AWM_HOME;
|
|
26
|
+
originalNoUpdate = process.env.AWM_NO_UPDATE_CHECK;
|
|
27
|
+
process.env.HOME = tmpHome;
|
|
28
|
+
process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
|
|
29
|
+
delete process.env.AWM_NO_UPDATE_CHECK;
|
|
30
|
+
jest.resetModules();
|
|
31
|
+
});
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
|
|
34
|
+
fs_1.default.rmSync(tmpWork, { recursive: true, force: true });
|
|
35
|
+
if (originalHome === undefined)
|
|
36
|
+
delete process.env.HOME;
|
|
37
|
+
else
|
|
38
|
+
process.env.HOME = originalHome;
|
|
39
|
+
if (originalAwmHome === undefined)
|
|
40
|
+
delete process.env.AWM_HOME;
|
|
41
|
+
else
|
|
42
|
+
process.env.AWM_HOME = originalAwmHome;
|
|
43
|
+
if (originalNoUpdate === undefined)
|
|
44
|
+
delete process.env.AWM_NO_UPDATE_CHECK;
|
|
45
|
+
else
|
|
46
|
+
process.env.AWM_NO_UPDATE_CHECK = originalNoUpdate;
|
|
47
|
+
jest.restoreAllMocks();
|
|
48
|
+
});
|
|
49
|
+
it('fetchLatestVersion devuelve la versión del registry npm', async () => {
|
|
50
|
+
const { fetchLatestVersion } = require('../../src/core/update-check');
|
|
51
|
+
const fakeFetch = jest.fn().mockResolvedValue({ ok: true, json: async () => ({ version: '2.3.0' }) });
|
|
52
|
+
await expect(fetchLatestVersion(fakeFetch)).resolves.toBe('2.3.0');
|
|
53
|
+
});
|
|
54
|
+
it('fetchLatestVersion sin red → null en silencio', async () => {
|
|
55
|
+
const { fetchLatestVersion } = require('../../src/core/update-check');
|
|
56
|
+
const fakeFetch = jest.fn().mockRejectedValue(new Error('offline'));
|
|
57
|
+
await expect(fetchLatestVersion(fakeFetch)).resolves.toBeNull();
|
|
58
|
+
});
|
|
59
|
+
it('maybeNotifyUpdate avisa si el cache trae versión más nueva y NO refresca cache fresco', () => {
|
|
60
|
+
const m = require('../../src/core/update-check');
|
|
61
|
+
m.writeUpdateCache({ lastCheck: 1_000_000, latest: '99.0.0' });
|
|
62
|
+
const log = jest.spyOn(console, 'log').mockImplementation(() => { });
|
|
63
|
+
const spawnWorker = jest.fn();
|
|
64
|
+
m.maybeNotifyUpdate({ now: 1_000_000 + 1000, spawnWorker });
|
|
65
|
+
expect(log.mock.calls.flat().join('\n')).toContain('99.0.0');
|
|
66
|
+
expect(spawnWorker).not.toHaveBeenCalled();
|
|
67
|
+
log.mockRestore();
|
|
68
|
+
});
|
|
69
|
+
it('cache viejo (>24h) dispara refresh en background', () => {
|
|
70
|
+
const m = require('../../src/core/update-check');
|
|
71
|
+
m.writeUpdateCache({ lastCheck: 0, latest: null });
|
|
72
|
+
const spawnWorker = jest.fn();
|
|
73
|
+
m.maybeNotifyUpdate({ now: 25 * 60 * 60 * 1000, spawnWorker });
|
|
74
|
+
expect(spawnWorker).toHaveBeenCalledTimes(1);
|
|
75
|
+
});
|
|
76
|
+
it('AWM_NO_UPDATE_CHECK desactiva todo', () => {
|
|
77
|
+
process.env.AWM_NO_UPDATE_CHECK = '1';
|
|
78
|
+
const m = require('../../src/core/update-check');
|
|
79
|
+
m.writeUpdateCache({ lastCheck: 0, latest: '99.0.0' });
|
|
80
|
+
const log = jest.spyOn(console, 'log').mockImplementation(() => { });
|
|
81
|
+
const spawnWorker = jest.fn();
|
|
82
|
+
m.maybeNotifyUpdate({ now: Date.now(), spawnWorker });
|
|
83
|
+
expect(log).not.toHaveBeenCalled();
|
|
84
|
+
expect(spawnWorker).not.toHaveBeenCalled();
|
|
85
|
+
log.mockRestore();
|
|
86
|
+
delete process.env.AWM_NO_UPDATE_CHECK;
|
|
87
|
+
});
|
|
88
|
+
it('offerSelfUpdate corre el runner al confirmar y degrada a aviso si falla', async () => {
|
|
89
|
+
const m = require('../../src/core/update-check');
|
|
90
|
+
const runner = jest.fn().mockReturnValue({ status: 1 });
|
|
91
|
+
const warn = jest.spyOn(console, 'warn').mockImplementation(() => { });
|
|
92
|
+
await m.offerSelfUpdate({ current: '2.0.0', latest: '2.1.0', confirmImpl: async () => true, runner });
|
|
93
|
+
expect(runner).toHaveBeenCalled();
|
|
94
|
+
expect(warn.mock.calls.flat().join('\n')).toContain('npm i -g agentic-workflow-manager');
|
|
95
|
+
warn.mockRestore();
|
|
96
|
+
});
|
|
97
|
+
it('offerSelfUpdate escribe cache cuando ya está actualizado (TTL reset)', async () => {
|
|
98
|
+
const m = require('../../src/core/update-check');
|
|
99
|
+
const fakeFetch = jest.fn().mockResolvedValue({ ok: true, json: async () => ({ version: '2.0.0' }) });
|
|
100
|
+
await m.offerSelfUpdate({ current: '2.0.0', fetchImpl: fakeFetch });
|
|
101
|
+
const cache = m.readUpdateCache();
|
|
102
|
+
expect(cache).not.toBeNull();
|
|
103
|
+
expect(cache.latest).toBe('2.0.0');
|
|
104
|
+
expect(cache.lastCheck).toBeGreaterThan(0);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,169 @@
|
|
|
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/core/versioning.test.ts
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
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' });
|
|
12
|
+
/** Repo fuente con un commit inicial y un commit+tag por cada versión dada (en orden). */
|
|
13
|
+
function makeTaggedRepo(base, name, versions) {
|
|
14
|
+
const dir = path_1.default.join(base, name);
|
|
15
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
16
|
+
GIT(dir, 'init -q -b main');
|
|
17
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, 'VERSION'), 'init');
|
|
18
|
+
GIT(dir, 'add -A');
|
|
19
|
+
GIT(dir, 'commit -qm init');
|
|
20
|
+
for (const v of versions) {
|
|
21
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, 'VERSION'), v);
|
|
22
|
+
GIT(dir, 'add -A');
|
|
23
|
+
GIT(dir, `commit -qm ${v}`);
|
|
24
|
+
GIT(dir, `tag v${v}`);
|
|
25
|
+
}
|
|
26
|
+
return dir;
|
|
27
|
+
}
|
|
28
|
+
function cloneOf(source, base, name) {
|
|
29
|
+
const dir = path_1.default.join(base, name);
|
|
30
|
+
GIT(base, `clone -q ${source} ${name}`);
|
|
31
|
+
return dir;
|
|
32
|
+
}
|
|
33
|
+
describe('versioning core', () => {
|
|
34
|
+
let tmpHome;
|
|
35
|
+
let tmpWork;
|
|
36
|
+
let originalHome;
|
|
37
|
+
let originalAwmHome;
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-ver-home-'));
|
|
40
|
+
tmpWork = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-ver-work-'));
|
|
41
|
+
originalHome = process.env.HOME;
|
|
42
|
+
originalAwmHome = process.env.AWM_HOME;
|
|
43
|
+
process.env.HOME = tmpHome;
|
|
44
|
+
process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
|
|
45
|
+
jest.resetModules();
|
|
46
|
+
});
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
|
|
49
|
+
fs_1.default.rmSync(tmpWork, { recursive: true, force: true });
|
|
50
|
+
if (originalHome === undefined)
|
|
51
|
+
delete process.env.HOME;
|
|
52
|
+
else
|
|
53
|
+
process.env.HOME = originalHome;
|
|
54
|
+
if (originalAwmHome === undefined)
|
|
55
|
+
delete process.env.AWM_HOME;
|
|
56
|
+
else
|
|
57
|
+
process.env.AWM_HOME = originalAwmHome;
|
|
58
|
+
});
|
|
59
|
+
describe('resolveTargetRef', () => {
|
|
60
|
+
it('stable sin pin → último tag con orden semver numérico (v1.10.0 > v1.9.0)', async () => {
|
|
61
|
+
const source = makeTaggedRepo(tmpWork, 'src', ['1.9.0', '1.10.0']);
|
|
62
|
+
const clone = cloneOf(source, tmpWork, 'clone');
|
|
63
|
+
const { resolveTargetRef } = require('../../src/core/versioning');
|
|
64
|
+
const r = await resolveTargetRef(clone, { channel: 'stable' });
|
|
65
|
+
expect(r).toEqual({ kind: 'tag', ref: 'v1.10.0', version: '1.10.0' });
|
|
66
|
+
});
|
|
67
|
+
it('pin exacto gana, con y sin prefijo v', async () => {
|
|
68
|
+
const source = makeTaggedRepo(tmpWork, 'src', ['1.0.0', '1.1.0']);
|
|
69
|
+
const clone = cloneOf(source, tmpWork, 'clone');
|
|
70
|
+
const { resolveTargetRef } = require('../../src/core/versioning');
|
|
71
|
+
expect(await resolveTargetRef(clone, { pin: '1.0.0', channel: 'stable' }))
|
|
72
|
+
.toEqual({ kind: 'tag', ref: 'v1.0.0', version: '1.0.0' });
|
|
73
|
+
expect(await resolveTargetRef(clone, { pin: 'v1.0.0', channel: 'stable' }))
|
|
74
|
+
.toEqual({ kind: 'tag', ref: 'v1.0.0', version: '1.0.0' });
|
|
75
|
+
});
|
|
76
|
+
it('pin inexistente → error que lista las versiones disponibles', async () => {
|
|
77
|
+
const source = makeTaggedRepo(tmpWork, 'src', ['1.0.0']);
|
|
78
|
+
const clone = cloneOf(source, tmpWork, 'clone');
|
|
79
|
+
const { resolveTargetRef } = require('../../src/core/versioning');
|
|
80
|
+
await expect(resolveTargetRef(clone, { pin: '9.9.9', channel: 'stable' }))
|
|
81
|
+
.rejects.toThrow(/v9\.9\.9.*v1\.0\.0/s);
|
|
82
|
+
});
|
|
83
|
+
it('sin tags + stable → head-fallback al default branch', async () => {
|
|
84
|
+
const source = makeTaggedRepo(tmpWork, 'src', []);
|
|
85
|
+
const clone = cloneOf(source, tmpWork, 'clone');
|
|
86
|
+
const { resolveTargetRef } = require('../../src/core/versioning');
|
|
87
|
+
const r = await resolveTargetRef(clone, { channel: 'stable' });
|
|
88
|
+
expect(r).toEqual({ kind: 'head-fallback', ref: 'main' });
|
|
89
|
+
});
|
|
90
|
+
it('canal dev → head del default branch aunque haya tags', async () => {
|
|
91
|
+
const source = makeTaggedRepo(tmpWork, 'src', ['1.0.0']);
|
|
92
|
+
const clone = cloneOf(source, tmpWork, 'clone');
|
|
93
|
+
const { resolveTargetRef } = require('../../src/core/versioning');
|
|
94
|
+
const r = await resolveTargetRef(clone, { channel: 'dev' });
|
|
95
|
+
expect(r).toEqual({ kind: 'head', ref: 'main' });
|
|
96
|
+
});
|
|
97
|
+
it('tags no semver se ignoran', async () => {
|
|
98
|
+
const source = makeTaggedRepo(tmpWork, 'src', ['1.0.0']);
|
|
99
|
+
GIT(source, 'tag latest');
|
|
100
|
+
GIT(source, 'tag v2.0');
|
|
101
|
+
GIT(source, 'tag release-3.0.0');
|
|
102
|
+
const clone = cloneOf(source, tmpWork, 'clone');
|
|
103
|
+
const { resolveTargetRef } = require('../../src/core/versioning');
|
|
104
|
+
const r = await resolveTargetRef(clone, { channel: 'stable' });
|
|
105
|
+
expect(r).toEqual({ kind: 'tag', ref: 'v1.0.0', version: '1.0.0' });
|
|
106
|
+
});
|
|
107
|
+
it('hace fetch: ve tags creados en el remote después del clone', async () => {
|
|
108
|
+
const source = makeTaggedRepo(tmpWork, 'src', ['1.0.0']);
|
|
109
|
+
const clone = cloneOf(source, tmpWork, 'clone');
|
|
110
|
+
// tag nuevo en el remote, post-clone
|
|
111
|
+
fs_1.default.writeFileSync(path_1.default.join(source, 'VERSION'), '1.1.0');
|
|
112
|
+
GIT(source, 'add -A');
|
|
113
|
+
GIT(source, 'commit -qm 1.1.0');
|
|
114
|
+
GIT(source, 'tag v1.1.0');
|
|
115
|
+
const { resolveTargetRef } = require('../../src/core/versioning');
|
|
116
|
+
const r = await resolveTargetRef(clone, { channel: 'stable' });
|
|
117
|
+
expect(r).toEqual({ kind: 'tag', ref: 'v1.1.0', version: '1.1.0' });
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
describe('currentVersion', () => {
|
|
121
|
+
it('en checkout exacto de un tag semver → versión sin prefijo v', async () => {
|
|
122
|
+
const source = makeTaggedRepo(tmpWork, 'src', ['1.2.0']);
|
|
123
|
+
const clone = cloneOf(source, tmpWork, 'clone');
|
|
124
|
+
GIT(clone, 'checkout -q v1.2.0');
|
|
125
|
+
const { currentVersion } = require('../../src/core/versioning');
|
|
126
|
+
expect(await currentVersion(clone)).toBe('1.2.0');
|
|
127
|
+
});
|
|
128
|
+
it('siguiendo un branch (sin tag exacto en HEAD) → null', async () => {
|
|
129
|
+
const source = makeTaggedRepo(tmpWork, 'src', ['1.0.0']);
|
|
130
|
+
// commit posterior al tag para que HEAD no coincida con ningún tag
|
|
131
|
+
fs_1.default.writeFileSync(path_1.default.join(source, 'VERSION'), 'post');
|
|
132
|
+
GIT(source, 'add -A');
|
|
133
|
+
GIT(source, 'commit -qm post');
|
|
134
|
+
const clone = cloneOf(source, tmpWork, 'clone');
|
|
135
|
+
const { currentVersion } = require('../../src/core/versioning');
|
|
136
|
+
expect(await currentVersion(clone)).toBeNull();
|
|
137
|
+
});
|
|
138
|
+
it('tag exacto pero no semver → null', async () => {
|
|
139
|
+
const source = makeTaggedRepo(tmpWork, 'src', []);
|
|
140
|
+
GIT(source, 'tag release-1');
|
|
141
|
+
const clone = cloneOf(source, tmpWork, 'clone');
|
|
142
|
+
GIT(clone, 'checkout -q release-1');
|
|
143
|
+
const { currentVersion } = require('../../src/core/versioning');
|
|
144
|
+
expect(await currentVersion(clone)).toBeNull();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
describe('machineVersionOpts', () => {
|
|
148
|
+
it('sin preferences → channel stable, sin pin', () => {
|
|
149
|
+
const { machineVersionOpts } = require('../../src/core/versioning');
|
|
150
|
+
expect(machineVersionOpts('base')).toEqual({ pin: undefined, channel: 'stable' });
|
|
151
|
+
});
|
|
152
|
+
it('lee channel dev y pin por nombre desde preferences', () => {
|
|
153
|
+
const awmDir = path_1.default.join(tmpHome, '.awm');
|
|
154
|
+
fs_1.default.mkdirSync(awmDir, { recursive: true });
|
|
155
|
+
fs_1.default.writeFileSync(path_1.default.join(awmDir, 'preferences.json'), JSON.stringify({ defaultAgent: 'claude', installMethod: 'symlink', defaultScope: 'local', channel: 'dev', pins: { base: '1.2.0', equipo: '0.3.0' } }));
|
|
156
|
+
const { machineVersionOpts } = require('../../src/core/versioning');
|
|
157
|
+
expect(machineVersionOpts('base')).toEqual({ pin: '1.2.0', channel: 'dev' });
|
|
158
|
+
expect(machineVersionOpts('equipo')).toEqual({ pin: '0.3.0', channel: 'dev' });
|
|
159
|
+
expect(machineVersionOpts('otro')).toEqual({ pin: undefined, channel: 'dev' });
|
|
160
|
+
});
|
|
161
|
+
it('preferences corruptas → defaults (stable, sin pin)', () => {
|
|
162
|
+
const awmDir = path_1.default.join(tmpHome, '.awm');
|
|
163
|
+
fs_1.default.mkdirSync(awmDir, { recursive: true });
|
|
164
|
+
fs_1.default.writeFileSync(path_1.default.join(awmDir, 'preferences.json'), '{not json');
|
|
165
|
+
const { machineVersionOpts } = require('../../src/core/versioning');
|
|
166
|
+
expect(machineVersionOpts('base')).toEqual({ pin: undefined, channel: 'stable' });
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|