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,205 @@
|
|
|
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
|
+
exports.REGISTRY_MANIFEST_NAME = exports.CONTENT_DIR_NAMES = exports.REGISTRIES_CONFIG_PATH = exports.REGISTRIES_DIR = void 0;
|
|
7
|
+
exports.registryContentRoot = registryContentRoot;
|
|
8
|
+
exports.readRegistriesConfig = readRegistriesConfig;
|
|
9
|
+
exports.writeRegistriesConfig = writeRegistriesConfig;
|
|
10
|
+
exports.seedBaselineRegistry = seedBaselineRegistry;
|
|
11
|
+
exports.listRegistries = listRegistries;
|
|
12
|
+
exports.contentRoots = contentRoots;
|
|
13
|
+
exports.capabilityRoot = capabilityRoot;
|
|
14
|
+
exports.validateRegistryLayout = validateRegistryLayout;
|
|
15
|
+
exports.syncRegistries = syncRegistries;
|
|
16
|
+
exports.readRegistryManifest = readRegistryManifest;
|
|
17
|
+
exports.registryNameForPath = registryNameForPath;
|
|
18
|
+
exports.verifyMinCliVersions = verifyMinCliVersions;
|
|
19
|
+
// src/core/registries.ts
|
|
20
|
+
const fs_1 = __importDefault(require("fs"));
|
|
21
|
+
const path_1 = __importDefault(require("path"));
|
|
22
|
+
const os_1 = __importDefault(require("os"));
|
|
23
|
+
const simple_git_1 = __importDefault(require("simple-git"));
|
|
24
|
+
const registry_1 = require("./registry");
|
|
25
|
+
const versioning_1 = require("./versioning");
|
|
26
|
+
const cli_version_1 = require("./cli-version");
|
|
27
|
+
// Evaluated at require-time — tests must use jest.resetModules() + late require() to pick up env overrides.
|
|
28
|
+
const AWM_HOME = process.env.AWM_HOME || path_1.default.join(process.env.HOME || os_1.default.homedir(), '.awm');
|
|
29
|
+
exports.REGISTRIES_DIR = path_1.default.join(AWM_HOME, 'registries');
|
|
30
|
+
exports.REGISTRIES_CONFIG_PATH = path_1.default.join(AWM_HOME, 'registries.json');
|
|
31
|
+
exports.CONTENT_DIR_NAMES = ['skills', 'bundles', 'workflows', 'agents'];
|
|
32
|
+
function registryContentRoot(name) {
|
|
33
|
+
const root = path_1.default.join(exports.REGISTRIES_DIR, name);
|
|
34
|
+
if (!path_1.default.resolve(root).startsWith(path_1.default.resolve(exports.REGISTRIES_DIR) + path_1.default.sep)) {
|
|
35
|
+
throw new Error(`Invalid registry name "${name}" — must not contain path separators`);
|
|
36
|
+
}
|
|
37
|
+
return root;
|
|
38
|
+
}
|
|
39
|
+
function readRegistriesConfig() {
|
|
40
|
+
if (!fs_1.default.existsSync(exports.REGISTRIES_CONFIG_PATH))
|
|
41
|
+
return [];
|
|
42
|
+
let raw;
|
|
43
|
+
try {
|
|
44
|
+
raw = JSON.parse(fs_1.default.readFileSync(exports.REGISTRIES_CONFIG_PATH, 'utf-8'));
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
throw new Error(`Invalid registries config at ${exports.REGISTRIES_CONFIG_PATH}: ${e instanceof Error ? e.message : String(e)}`);
|
|
48
|
+
}
|
|
49
|
+
if (!Array.isArray(raw)) {
|
|
50
|
+
throw new Error(`Invalid registries config at ${exports.REGISTRIES_CONFIG_PATH}: expected a JSON array`);
|
|
51
|
+
}
|
|
52
|
+
for (const entry of raw) {
|
|
53
|
+
if (typeof entry?.name !== 'string' || typeof entry?.remote !== 'string') {
|
|
54
|
+
throw new Error(`Invalid registries config at ${exports.REGISTRIES_CONFIG_PATH}: malformed entry ${JSON.stringify(entry)}`);
|
|
55
|
+
}
|
|
56
|
+
if (entry.name === '.' || entry.name.includes('/') || entry.name.includes('\\') || entry.name.includes('..')) {
|
|
57
|
+
throw new Error(`Invalid registries config at ${exports.REGISTRIES_CONFIG_PATH}: malformed entry name "${entry.name}" (path traversal)`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return raw;
|
|
61
|
+
}
|
|
62
|
+
function writeRegistriesConfig(entries) {
|
|
63
|
+
fs_1.default.mkdirSync(AWM_HOME, { recursive: true });
|
|
64
|
+
fs_1.default.writeFileSync(exports.REGISTRIES_CONFIG_PATH, JSON.stringify(entries, null, 2) + '\n', 'utf-8');
|
|
65
|
+
}
|
|
66
|
+
/** Bootstrap de máquina (awm init): si no hay registries.json, lo crea con la
|
|
67
|
+
* entrada baseline (remote por cadena WS-2: env > prefs > default).
|
|
68
|
+
* Devuelve true si sembró. Nunca toca un registries.json existente. */
|
|
69
|
+
function seedBaselineRegistry() {
|
|
70
|
+
if (fs_1.default.existsSync(exports.REGISTRIES_CONFIG_PATH))
|
|
71
|
+
return false;
|
|
72
|
+
writeRegistriesConfig([{ name: 'baseline', remote: (0, registry_1.resolveBaseRemote)() }]);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
function listRegistries() {
|
|
76
|
+
return readRegistriesConfig().map((e) => ({ ...e, contentRoot: registryContentRoot(e.name) }));
|
|
77
|
+
}
|
|
78
|
+
/** Roots de contenido en orden de registries.json.
|
|
79
|
+
* Un registry configurado pero ausente se omite (awm update lo re-clona). */
|
|
80
|
+
function contentRoots() {
|
|
81
|
+
const roots = [];
|
|
82
|
+
for (const reg of listRegistries()) {
|
|
83
|
+
if (fs_1.default.existsSync(reg.contentRoot))
|
|
84
|
+
roots.push(reg.contentRoot);
|
|
85
|
+
}
|
|
86
|
+
return roots;
|
|
87
|
+
}
|
|
88
|
+
/** Primer content root configurado (en orden de registries.json) que contiene el
|
|
89
|
+
* directorio pedido ('hooks', 'sensor-packs', 'skills'…). null si ninguno. */
|
|
90
|
+
function capabilityRoot(dirName) {
|
|
91
|
+
for (const root of contentRoots()) {
|
|
92
|
+
if (fs_1.default.existsSync(path_1.default.join(root, dirName)))
|
|
93
|
+
return root;
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
/** Un registry válido tiene ≥1 dir de contenido en su raíz. */
|
|
98
|
+
function validateRegistryLayout(root) {
|
|
99
|
+
return exports.CONTENT_DIR_NAMES.some((d) => fs_1.default.existsSync(path_1.default.join(root, d)));
|
|
100
|
+
}
|
|
101
|
+
/** Sincroniza cada registry configurado al ref resuelto (pin > último tag > HEAD);
|
|
102
|
+
* re-clona si falta el dir. Errores por-registry NO fatales: se reportan en el resultado. */
|
|
103
|
+
async function syncRegistries() {
|
|
104
|
+
const results = [];
|
|
105
|
+
for (const reg of listRegistries()) {
|
|
106
|
+
try {
|
|
107
|
+
const freshClone = !fs_1.default.existsSync(reg.contentRoot);
|
|
108
|
+
if (freshClone) {
|
|
109
|
+
fs_1.default.mkdirSync(exports.REGISTRIES_DIR, { recursive: true });
|
|
110
|
+
try {
|
|
111
|
+
await (0, simple_git_1.default)().clone(reg.remote, reg.contentRoot);
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
fs_1.default.rmSync(reg.contentRoot, { recursive: true, force: true });
|
|
115
|
+
throw e;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
await (0, simple_git_1.default)(reg.contentRoot).reset(['--hard']);
|
|
120
|
+
}
|
|
121
|
+
const git = (0, simple_git_1.default)(reg.contentRoot);
|
|
122
|
+
let resolved;
|
|
123
|
+
try {
|
|
124
|
+
resolved = await (0, versioning_1.resolveTargetRef)(reg.contentRoot, (0, versioning_1.machineVersionOpts)(reg.name));
|
|
125
|
+
await git.checkout(resolved.ref);
|
|
126
|
+
if (resolved.kind !== 'tag')
|
|
127
|
+
await git.pull('origin', resolved.ref);
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
if (freshClone)
|
|
131
|
+
fs_1.default.rmSync(reg.contentRoot, { recursive: true, force: true });
|
|
132
|
+
throw e;
|
|
133
|
+
}
|
|
134
|
+
results.push({
|
|
135
|
+
name: reg.name,
|
|
136
|
+
action: freshClone ? 'recloned' : 'pulled',
|
|
137
|
+
version: resolved.kind === 'tag' ? `v${resolved.version}` : 'HEAD',
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
results.push({ name: reg.name, action: 'error', error: e instanceof Error ? e.message : String(e) });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return results;
|
|
145
|
+
}
|
|
146
|
+
exports.REGISTRY_MANIFEST_NAME = 'awm-registry.json';
|
|
147
|
+
function readRegistryManifest(root) {
|
|
148
|
+
const file = path_1.default.join(root, exports.REGISTRY_MANIFEST_NAME);
|
|
149
|
+
if (!fs_1.default.existsSync(file))
|
|
150
|
+
return { overrides: new Set() };
|
|
151
|
+
let raw;
|
|
152
|
+
try {
|
|
153
|
+
raw = JSON.parse(fs_1.default.readFileSync(file, 'utf-8'));
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
throw new Error(`Invalid registry manifest at ${file}: ${e instanceof Error ? e.message : String(e)}`);
|
|
157
|
+
}
|
|
158
|
+
const overrides = raw?.overrides ?? [];
|
|
159
|
+
if (!Array.isArray(overrides) || overrides.some((n) => typeof n !== 'string')) {
|
|
160
|
+
throw new Error(`Invalid registry manifest at ${file}: "overrides" must be an array of strings`);
|
|
161
|
+
}
|
|
162
|
+
for (const name of overrides) {
|
|
163
|
+
if (!name || name === '.' || name.includes('..') || /[/\\]/.test(name)) {
|
|
164
|
+
throw new Error(`Invalid registry manifest at ${file}: override name "${name}" (path traversal)`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
let minCliVersion;
|
|
168
|
+
const rawMin = raw?.minCliVersion;
|
|
169
|
+
if (rawMin !== undefined) {
|
|
170
|
+
if (typeof rawMin !== 'string' || !/^v?\d+\.\d+\.\d+$/.test(rawMin)) {
|
|
171
|
+
throw new Error(`Invalid registry manifest at ${file}: "minCliVersion" must be "X.Y.Z", got ${JSON.stringify(rawMin)}`);
|
|
172
|
+
}
|
|
173
|
+
minCliVersion = rawMin.replace(/^v/, '');
|
|
174
|
+
}
|
|
175
|
+
return { overrides: new Set(overrides), minCliVersion };
|
|
176
|
+
}
|
|
177
|
+
/** Nombre del registry dueño de un path: el nombre del clone bajo REGISTRIES_DIR,
|
|
178
|
+
* o null si no pertenece a ninguno. */
|
|
179
|
+
function registryNameForPath(p) {
|
|
180
|
+
const resolved = path_1.default.resolve(p);
|
|
181
|
+
const regsRoot = path_1.default.resolve(exports.REGISTRIES_DIR) + path_1.default.sep;
|
|
182
|
+
if (resolved.startsWith(regsRoot)) {
|
|
183
|
+
const first = resolved.slice(regsRoot.length).split(path_1.default.sep)[0];
|
|
184
|
+
return first || null;
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
/** Registries cuyo manifest exige un CLI más nuevo que el actual (capa 3, WS-4). */
|
|
189
|
+
function verifyMinCliVersions(current = (0, cli_version_1.cliVersion)()) {
|
|
190
|
+
const failures = [];
|
|
191
|
+
for (const reg of listRegistries()) {
|
|
192
|
+
if (!fs_1.default.existsSync(reg.contentRoot))
|
|
193
|
+
continue;
|
|
194
|
+
let min;
|
|
195
|
+
try {
|
|
196
|
+
min = readRegistryManifest(reg.contentRoot).minCliVersion;
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (min && (0, versioning_1.compareSemver)(current, min) < 0)
|
|
202
|
+
failures.push({ name: reg.name, min });
|
|
203
|
+
}
|
|
204
|
+
return failures;
|
|
205
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_REMOTE = void 0;
|
|
4
|
+
exports.resolveBaseRemoteInfo = resolveBaseRemoteInfo;
|
|
5
|
+
exports.resolveBaseRemote = resolveBaseRemote;
|
|
6
|
+
// src/core/registry.ts
|
|
7
|
+
//
|
|
8
|
+
// Identidad del registry base (WS-4): el CLI no clona ni buildea nada acá —
|
|
9
|
+
// solo conoce el remote default que `awm init` siembra en registries.json.
|
|
10
|
+
const config_1 = require("../utils/config");
|
|
11
|
+
exports.DEFAULT_REMOTE = "https://github.com/Kodria/awm-baseline-registry.git";
|
|
12
|
+
/** Remote efectivo del registry base y su origen: env AWM_BASE_REMOTE > preferences.baseRemote > DEFAULT_REMOTE. */
|
|
13
|
+
function resolveBaseRemoteInfo() {
|
|
14
|
+
if (process.env.AWM_BASE_REMOTE)
|
|
15
|
+
return { remote: process.env.AWM_BASE_REMOTE, source: 'env' };
|
|
16
|
+
try {
|
|
17
|
+
const prefs = (0, config_1.getPreferences)();
|
|
18
|
+
if (prefs.baseRemote)
|
|
19
|
+
return { remote: prefs.baseRemote, source: 'prefs' };
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// preferencias ilegibles no deben bloquear — cae al default
|
|
23
|
+
}
|
|
24
|
+
return { remote: exports.DEFAULT_REMOTE, source: 'default' };
|
|
25
|
+
}
|
|
26
|
+
function resolveBaseRemote() {
|
|
27
|
+
return resolveBaseRemoteInfo().remote;
|
|
28
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
exports.classifyGlobalSkills = classifyGlobalSkills;
|
|
7
|
+
exports.repairGlobalSkills = repairGlobalSkills;
|
|
8
|
+
exports.reconcileAllSkillLinks = reconcileAllSkillLinks;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const providers_1 = require("../providers");
|
|
12
|
+
function findRegistrySkillPath(registryContentDirs, name) {
|
|
13
|
+
for (const root of registryContentDirs) {
|
|
14
|
+
const p = path_1.default.join(root, 'skills', name);
|
|
15
|
+
if (fs_1.default.existsSync(p))
|
|
16
|
+
return p;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
/** Clasifica cada entrada de `skillsDir` (read-only, no muta nada). */
|
|
21
|
+
function classifyGlobalSkills(skillsDir, registryContentDirs) {
|
|
22
|
+
const out = { valid: [], repairable: [], dead: [] };
|
|
23
|
+
let entries;
|
|
24
|
+
try {
|
|
25
|
+
entries = fs_1.default.readdirSync(skillsDir);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return out;
|
|
29
|
+
} // dir ausente → nada que clasificar
|
|
30
|
+
for (const name of entries) {
|
|
31
|
+
const p = path_1.default.join(skillsDir, name);
|
|
32
|
+
let lst;
|
|
33
|
+
try {
|
|
34
|
+
lst = fs_1.default.lstatSync(p);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (!lst.isSymbolicLink())
|
|
40
|
+
continue; // dirs/archivos reales no son nuestro problema
|
|
41
|
+
if (fs_1.default.existsSync(p)) {
|
|
42
|
+
out.valid.push(name);
|
|
43
|
+
continue;
|
|
44
|
+
} // target vivo
|
|
45
|
+
// symlink colgante → ¿reparable o muerto?
|
|
46
|
+
if (findRegistrySkillPath(registryContentDirs, name))
|
|
47
|
+
out.repairable.push(name);
|
|
48
|
+
else
|
|
49
|
+
out.dead.push(name);
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
/** Re-linkea los repairable al primer root que tenga la skill y poda los dead. Idempotente. Cada
|
|
54
|
+
* symlink aislado en try/catch — una falla no aborta el resto. */
|
|
55
|
+
function repairGlobalSkills(skillsDir, registryContentDirs) {
|
|
56
|
+
const result = { relinked: [], pruned: [], failed: [] };
|
|
57
|
+
const { repairable, dead } = classifyGlobalSkills(skillsDir, registryContentDirs);
|
|
58
|
+
for (const name of repairable) {
|
|
59
|
+
const p = path_1.default.join(skillsDir, name);
|
|
60
|
+
try {
|
|
61
|
+
const target = findRegistrySkillPath(registryContentDirs, name);
|
|
62
|
+
if (!target) {
|
|
63
|
+
result.failed.push(name);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
fs_1.default.rmSync(p, { force: true });
|
|
67
|
+
fs_1.default.symlinkSync(target, p, 'dir');
|
|
68
|
+
result.relinked.push(name);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
result.failed.push(name);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const name of dead) {
|
|
75
|
+
const p = path_1.default.join(skillsDir, name);
|
|
76
|
+
try {
|
|
77
|
+
fs_1.default.rmSync(p, { force: true });
|
|
78
|
+
result.pruned.push(name);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
result.failed.push(name);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
/** Reconcilia los symlinks de skills de TODOS los providers con soporte de skills
|
|
87
|
+
* cuyo dir global existe. Es mantenimiento machine-global (awm update): no hay un
|
|
88
|
+
* único agente target. Cada provider en su propio path; un dir ausente se omite. */
|
|
89
|
+
function reconcileAllSkillLinks(registryContentDirs) {
|
|
90
|
+
const out = [];
|
|
91
|
+
for (const agent of Object.keys(providers_1.PROVIDERS)) {
|
|
92
|
+
if (!fs_1.default.existsSync(providers_1.PROVIDERS[agent].skill.global))
|
|
93
|
+
continue;
|
|
94
|
+
out.push({ agent, result: repairGlobalSkills(providers_1.PROVIDERS[agent].skill.global, registryContentDirs) });
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseStoryMap = parseStoryMap;
|
|
4
|
+
exports.updateMiroFrameId = updateMiroFrameId;
|
|
5
|
+
function parseFrontmatter(content) {
|
|
6
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
7
|
+
if (!match)
|
|
8
|
+
return { meta: {}, body: content };
|
|
9
|
+
const meta = {};
|
|
10
|
+
for (const line of match[1].split('\n')) {
|
|
11
|
+
const colonIdx = line.indexOf(':');
|
|
12
|
+
if (colonIdx === -1)
|
|
13
|
+
continue;
|
|
14
|
+
const key = line.slice(0, colonIdx).trim();
|
|
15
|
+
const value = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, '');
|
|
16
|
+
// Empty value is intentionally skipped — keys without values are not meaningful metadata
|
|
17
|
+
if (key && value)
|
|
18
|
+
meta[key] = value;
|
|
19
|
+
}
|
|
20
|
+
return { meta, body: match[2] };
|
|
21
|
+
}
|
|
22
|
+
function extractBackbone(body) {
|
|
23
|
+
const match = body.match(/## Backbone\n([\s\S]*?)(?=\n## |$)/);
|
|
24
|
+
return match ? match[1].trim() : '';
|
|
25
|
+
}
|
|
26
|
+
function parseBackbone(backbone) {
|
|
27
|
+
const activities = [];
|
|
28
|
+
const activityBlocks = backbone.split(/(?=^### 🟡 )/m).filter(Boolean);
|
|
29
|
+
for (const block of activityBlocks) {
|
|
30
|
+
const lines = block.split('\n');
|
|
31
|
+
const activityMatch = lines[0].match(/^### 🟡 (.+)$/);
|
|
32
|
+
if (!activityMatch)
|
|
33
|
+
continue;
|
|
34
|
+
const activity = { title: activityMatch[1].trim(), tasks: [] };
|
|
35
|
+
const taskContent = lines.slice(1).join('\n');
|
|
36
|
+
const taskBlocks = taskContent.split(/(?=^#### 🔵 Task: )/m).filter(s => s.trim());
|
|
37
|
+
for (const taskBlock of taskBlocks) {
|
|
38
|
+
const taskLines = taskBlock.split('\n');
|
|
39
|
+
const taskMatch = taskLines[0].match(/^#### 🔵 Task: (.+)$/);
|
|
40
|
+
if (!taskMatch)
|
|
41
|
+
continue;
|
|
42
|
+
const task = { title: taskMatch[1].trim(), stories: [] };
|
|
43
|
+
for (const line of taskLines.slice(1)) {
|
|
44
|
+
const storyMatch = line.match(/^- \*\*\[(.+?)\]\*\* (.+)$/);
|
|
45
|
+
if (storyMatch) {
|
|
46
|
+
task.stories.push({ release: storyMatch[1].trim(), title: storyMatch[2].trim() });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
activity.tasks.push(task);
|
|
50
|
+
}
|
|
51
|
+
activities.push(activity);
|
|
52
|
+
}
|
|
53
|
+
return activities;
|
|
54
|
+
}
|
|
55
|
+
function parseStoryMap(content) {
|
|
56
|
+
// Normalize CRLF to LF so all downstream regexes can assume Unix line endings
|
|
57
|
+
const normalized = content.replace(/\r\n/g, '\n');
|
|
58
|
+
const { meta, body } = parseFrontmatter(normalized);
|
|
59
|
+
const titleMatch = body.match(/^# Story Map — (.+)$/m);
|
|
60
|
+
const project = meta['project'] || (titleMatch ? titleMatch[1].trim() : 'Unknown');
|
|
61
|
+
const goalMatch = body.match(/## Goal\n>\s*(.+)/);
|
|
62
|
+
const goal = goalMatch ? goalMatch[1].trim() : '';
|
|
63
|
+
const backbone = extractBackbone(body);
|
|
64
|
+
const activities = parseBackbone(backbone);
|
|
65
|
+
return {
|
|
66
|
+
project,
|
|
67
|
+
goal,
|
|
68
|
+
miro_frame_id: meta['miro_frame_id'] || undefined,
|
|
69
|
+
activities,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function updateMiroFrameId(content, frameId) {
|
|
73
|
+
const match = content.match(/^(---\n)([\s\S]*?)(\n---\n?)([\s\S]*)$/);
|
|
74
|
+
if (!match) {
|
|
75
|
+
return `---\nmiro_frame_id: "${frameId}"\n---\n${content}`;
|
|
76
|
+
}
|
|
77
|
+
const [, open, frontmatter, close, body] = match;
|
|
78
|
+
if (frontmatter.includes('miro_frame_id:')) {
|
|
79
|
+
const updated = frontmatter.replace(/miro_frame_id:.*/, `miro_frame_id: "${frameId}"`);
|
|
80
|
+
return `${open}${updated}${close}${body}`;
|
|
81
|
+
}
|
|
82
|
+
return `${open}${frontmatter}\nmiro_frame_id: "${frameId}"${close}${body}`;
|
|
83
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
// cli/src/core/update-check-worker.ts
|
|
4
|
+
//
|
|
5
|
+
// Proceso detached lanzado por maybeNotifyUpdate: refresca el cache y muere.
|
|
6
|
+
const update_check_1 = require("./update-check");
|
|
7
|
+
(async () => {
|
|
8
|
+
const latest = await (0, update_check_1.fetchLatestVersion)();
|
|
9
|
+
(0, update_check_1.writeUpdateCache)({ lastCheck: Date.now(), latest });
|
|
10
|
+
})();
|
|
@@ -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
|
+
exports.readUpdateCache = readUpdateCache;
|
|
7
|
+
exports.writeUpdateCache = writeUpdateCache;
|
|
8
|
+
exports.fetchLatestVersion = fetchLatestVersion;
|
|
9
|
+
exports.spawnRefreshWorker = spawnRefreshWorker;
|
|
10
|
+
exports.maybeNotifyUpdate = maybeNotifyUpdate;
|
|
11
|
+
exports.offerSelfUpdate = offerSelfUpdate;
|
|
12
|
+
// cli/src/core/update-check.ts
|
|
13
|
+
//
|
|
14
|
+
// Actualización del CLI en capas (WS-4): capa 1 = aviso pasivo con cache de 24h
|
|
15
|
+
// refrescado por un worker detached; capa 2 = self-update con confirmación en
|
|
16
|
+
// `awm update`. AWM_NO_UPDATE_CHECK=1 desactiva ambas (tests, CI).
|
|
17
|
+
const fs_1 = __importDefault(require("fs"));
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
19
|
+
const os_1 = __importDefault(require("os"));
|
|
20
|
+
const child_process_1 = require("child_process");
|
|
21
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
22
|
+
const prompts_1 = require("@clack/prompts");
|
|
23
|
+
const cli_version_1 = require("./cli-version");
|
|
24
|
+
const versioning_1 = require("./versioning");
|
|
25
|
+
const TTL_MS = 24 * 60 * 60 * 1000;
|
|
26
|
+
const REGISTRY_URL = `https://registry.npmjs.org/${cli_version_1.CLI_PACKAGE_NAME}/latest`;
|
|
27
|
+
function cacheFile() {
|
|
28
|
+
const home = process.env.AWM_HOME || path_1.default.join(process.env.HOME || os_1.default.homedir(), '.awm');
|
|
29
|
+
return path_1.default.join(home, 'update-check.json');
|
|
30
|
+
}
|
|
31
|
+
function readUpdateCache() {
|
|
32
|
+
try {
|
|
33
|
+
const raw = JSON.parse(fs_1.default.readFileSync(cacheFile(), 'utf-8'));
|
|
34
|
+
if (typeof raw.lastCheck === 'number')
|
|
35
|
+
return raw;
|
|
36
|
+
}
|
|
37
|
+
catch { /* ausente o corrupto → null */ }
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function writeUpdateCache(c) {
|
|
41
|
+
fs_1.default.mkdirSync(path_1.default.dirname(cacheFile()), { recursive: true });
|
|
42
|
+
fs_1.default.writeFileSync(cacheFile(), JSON.stringify(c), 'utf-8');
|
|
43
|
+
}
|
|
44
|
+
/** Última versión publicada en npm, o null ante cualquier falla (timeout 2s). */
|
|
45
|
+
async function fetchLatestVersion(fetchImpl = fetch) {
|
|
46
|
+
try {
|
|
47
|
+
const ctl = new AbortController();
|
|
48
|
+
const t = setTimeout(() => ctl.abort(), 2000).unref();
|
|
49
|
+
const res = await fetchImpl(REGISTRY_URL, { signal: ctl.signal });
|
|
50
|
+
clearTimeout(t);
|
|
51
|
+
if (!res.ok)
|
|
52
|
+
return null;
|
|
53
|
+
const body = (await res.json());
|
|
54
|
+
return typeof body.version === 'string' ? body.version : null;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Worker detached que refresca el cache sin bloquear el comando actual. */
|
|
61
|
+
function spawnRefreshWorker() {
|
|
62
|
+
const worker = path_1.default.join(__dirname, 'update-check-worker.js');
|
|
63
|
+
if (!fs_1.default.existsSync(worker))
|
|
64
|
+
return; // ts-node / dev: sin worker compilado, skip
|
|
65
|
+
(0, child_process_1.spawn)(process.execPath, [worker], { detached: true, stdio: 'ignore', env: process.env }).unref();
|
|
66
|
+
}
|
|
67
|
+
/** Capa 1 — llamado al final de cualquier comando (hook postAction de Commander). */
|
|
68
|
+
function maybeNotifyUpdate(opts) {
|
|
69
|
+
if (process.env.AWM_NO_UPDATE_CHECK)
|
|
70
|
+
return;
|
|
71
|
+
const now = opts?.now ?? Date.now();
|
|
72
|
+
const spawnWorker = opts?.spawnWorker ?? spawnRefreshWorker;
|
|
73
|
+
const cache = readUpdateCache();
|
|
74
|
+
if (cache?.latest && (0, versioning_1.compareSemver)(cache.latest, (0, cli_version_1.cliVersion)()) > 0) {
|
|
75
|
+
console.log(picocolors_1.default.dim(`\n⬆ awm v${cache.latest} disponible → npm i -g ${cli_version_1.CLI_PACKAGE_NAME}`));
|
|
76
|
+
}
|
|
77
|
+
if (!cache || now - cache.lastCheck > TTL_MS)
|
|
78
|
+
spawnWorker();
|
|
79
|
+
}
|
|
80
|
+
/** Capa 2 — en `awm update`: detecta, pregunta, ejecuta npm i -g; degrada a aviso. */
|
|
81
|
+
async function offerSelfUpdate(deps = {}) {
|
|
82
|
+
if (process.env.AWM_NO_UPDATE_CHECK)
|
|
83
|
+
return;
|
|
84
|
+
const current = deps.current ?? (0, cli_version_1.cliVersion)();
|
|
85
|
+
const latest = deps.latest !== undefined ? deps.latest : await fetchLatestVersion(deps.fetchImpl ?? fetch);
|
|
86
|
+
writeUpdateCache({ lastCheck: Date.now(), latest: latest ?? null });
|
|
87
|
+
if (!latest || (0, versioning_1.compareSemver)(latest, current) <= 0)
|
|
88
|
+
return;
|
|
89
|
+
const confirmImpl = deps.confirmImpl ?? (async (message) => {
|
|
90
|
+
const r = await (0, prompts_1.confirm)({ message });
|
|
91
|
+
return !(0, prompts_1.isCancel)(r) && r === true;
|
|
92
|
+
});
|
|
93
|
+
const yes = await confirmImpl(`¿Actualizar awm v${current} → v${latest} ahora?`);
|
|
94
|
+
if (!yes) {
|
|
95
|
+
console.log(picocolors_1.default.dim(` Para actualizar después: npm i -g ${cli_version_1.CLI_PACKAGE_NAME}`));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const runner = deps.runner ?? ((cmd, args) => (0, child_process_1.spawnSync)(cmd, args, { stdio: 'inherit', shell: true }));
|
|
99
|
+
const r = runner('npm', ['i', '-g', `${cli_version_1.CLI_PACKAGE_NAME}@latest`]);
|
|
100
|
+
if (r.status === 0) {
|
|
101
|
+
console.log(picocolors_1.default.green(` ✓ CLI actualizado a v${latest} (aplica desde el próximo comando)`));
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
console.warn(picocolors_1.default.yellow(` ⚠ No se pudo actualizar automáticamente — corré: npm i -g ${cli_version_1.CLI_PACKAGE_NAME}`));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
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
|
+
exports.normalizePin = normalizePin;
|
|
7
|
+
exports.resolveTargetRef = resolveTargetRef;
|
|
8
|
+
exports.currentVersion = currentVersion;
|
|
9
|
+
exports.machineVersionOpts = machineVersionOpts;
|
|
10
|
+
exports.compareSemver = compareSemver;
|
|
11
|
+
// src/core/versioning.ts
|
|
12
|
+
//
|
|
13
|
+
// Resolver de versiones por tags semver — WS-3. Sin estado propio: git es la
|
|
14
|
+
// única fuente de verdad (la versión vigente se deriva de `git describe`).
|
|
15
|
+
const simple_git_1 = __importDefault(require("simple-git"));
|
|
16
|
+
const config_1 = require("../utils/config");
|
|
17
|
+
const TAG_RE = /^v(\d+)\.(\d+)\.(\d+)$/;
|
|
18
|
+
function normalizePin(pin) {
|
|
19
|
+
return pin.replace(/^v/, '');
|
|
20
|
+
}
|
|
21
|
+
function semverKey(tag) {
|
|
22
|
+
const m = TAG_RE.exec(tag);
|
|
23
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
24
|
+
}
|
|
25
|
+
function bySemverAsc(a, b) {
|
|
26
|
+
const [a1, a2, a3] = semverKey(a);
|
|
27
|
+
const [b1, b2, b3] = semverKey(b);
|
|
28
|
+
return a1 - b1 || a2 - b2 || a3 - b3;
|
|
29
|
+
}
|
|
30
|
+
async function defaultBranch(git) {
|
|
31
|
+
try {
|
|
32
|
+
const ref = (await git.raw(['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'])).trim();
|
|
33
|
+
const name = ref.split('/').slice(1).join('/');
|
|
34
|
+
if (name)
|
|
35
|
+
return name;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// origin/HEAD puede no estar seteado — probar candidatos
|
|
39
|
+
}
|
|
40
|
+
for (const cand of ['main', 'master']) {
|
|
41
|
+
try {
|
|
42
|
+
await git.raw(['rev-parse', '--verify', `origin/${cand}`]);
|
|
43
|
+
return cand;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// siguiente candidato
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return 'main';
|
|
50
|
+
}
|
|
51
|
+
/** Tags semver vX.Y.Z del repo, orden ascendente. Hace fetch --tags --prune primero. */
|
|
52
|
+
async function fetchVersionTags(git) {
|
|
53
|
+
await git.raw(['fetch', '--tags', '--prune', 'origin']);
|
|
54
|
+
const tags = (await git.tags()).all.filter((t) => TAG_RE.test(t));
|
|
55
|
+
return tags.sort(bySemverAsc);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resuelve a qué ref debe quedar checkouteado un registry clonado:
|
|
59
|
+
* pin declarado > último tag semver (canal stable) > HEAD (canal dev o repo sin tags).
|
|
60
|
+
*/
|
|
61
|
+
async function resolveTargetRef(repoDir, opts) {
|
|
62
|
+
const git = (0, simple_git_1.default)(repoDir);
|
|
63
|
+
const tags = await fetchVersionTags(git);
|
|
64
|
+
if (opts.pin) {
|
|
65
|
+
const want = `v${normalizePin(opts.pin)}`;
|
|
66
|
+
if (!tags.includes(want)) {
|
|
67
|
+
const available = tags.length > 0
|
|
68
|
+
? `available versions: ${tags.join(', ')}`
|
|
69
|
+
: 'the registry has no version tags';
|
|
70
|
+
throw new Error(`Pinned version ${want} not found in ${repoDir} — ${available}`);
|
|
71
|
+
}
|
|
72
|
+
return { kind: 'tag', ref: want, version: normalizePin(opts.pin) };
|
|
73
|
+
}
|
|
74
|
+
const branch = await defaultBranch(git);
|
|
75
|
+
if (opts.channel === 'dev')
|
|
76
|
+
return { kind: 'head', ref: branch };
|
|
77
|
+
if (tags.length === 0)
|
|
78
|
+
return { kind: 'head-fallback', ref: branch };
|
|
79
|
+
const latest = tags[tags.length - 1];
|
|
80
|
+
return { kind: 'tag', ref: latest, version: latest.slice(1) };
|
|
81
|
+
}
|
|
82
|
+
/** Versión checkouteada actual ("X.Y.Z") si HEAD coincide exactamente con un tag semver; null si sigue un branch. */
|
|
83
|
+
async function currentVersion(repoDir) {
|
|
84
|
+
try {
|
|
85
|
+
const out = (await (0, simple_git_1.default)(repoDir).raw(['describe', '--tags', '--exact-match', 'HEAD'])).trim();
|
|
86
|
+
return TAG_RE.test(out) ? out.slice(1) : null;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** Opts de versionado de la máquina para un registry, desde preferences.
|
|
93
|
+
* Preferences ilegibles → defaults (stable, sin pin) — patrón de resolveBaseRemoteInfo. */
|
|
94
|
+
function machineVersionOpts(registryName) {
|
|
95
|
+
try {
|
|
96
|
+
const prefs = (0, config_1.getPreferences)();
|
|
97
|
+
const channel = prefs.channel === 'dev' ? 'dev' : 'stable';
|
|
98
|
+
const pin = prefs.pins && typeof prefs.pins[registryName] === 'string'
|
|
99
|
+
? prefs.pins[registryName]
|
|
100
|
+
: undefined;
|
|
101
|
+
return { pin, channel };
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return { pin: undefined, channel: 'stable' };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** Compara "X.Y.Z" vs "X.Y.Z" (sin prefijo v) numéricamente. <0, 0, >0. */
|
|
108
|
+
function compareSemver(a, b) {
|
|
109
|
+
const pa = a.split('.').map(Number);
|
|
110
|
+
const pb = b.split('.').map(Number);
|
|
111
|
+
return (pa[0] - pb[0]) || (pa[1] - pb[1]) || (pa[2] - pb[2]);
|
|
112
|
+
}
|