agentic-workflow-manager 3.13.4 → 3.13.6
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/commands/add.js +5 -0
- package/dist/src/commands/doctor.js +1 -0
- package/dist/src/commands/hooks/claude.js +15 -2
- package/dist/src/commands/hooks/shared.js +6 -1
- package/dist/src/commands/init.js +1 -0
- package/dist/src/commands/pin.js +24 -4
- package/dist/src/commands/preflight/checks.js +3 -2
- package/dist/src/commands/registry/add.js +1 -1
- package/dist/src/commands/sensors/index.js +7 -0
- package/dist/src/commands/sensors/init.js +48 -7
- package/dist/src/commands/sensors/install.js +20 -11
- package/dist/src/commands/sensors/run.js +31 -29
- package/dist/src/commands/sync.js +29 -0
- package/dist/src/commands/watch/index.js +11 -1
- package/dist/src/core/artifact-name.js +80 -0
- package/dist/src/core/bundle-install.js +55 -12
- package/dist/src/core/bundles.js +72 -13
- package/dist/src/core/context/regenerate.js +21 -4
- package/dist/src/core/context/strategies/codex-agents.js +18 -2
- package/dist/src/core/diagnostics/checks.js +26 -2
- package/dist/src/core/diagnostics/context.js +91 -25
- package/dist/src/core/diagnostics/provider-checks.js +62 -14
- package/dist/src/core/discovery.js +15 -23
- package/dist/src/core/export/transform.js +30 -25
- package/dist/src/core/frontmatter.js +223 -0
- package/dist/src/core/init/mutation-targets.js +11 -0
- package/dist/src/core/init/steps.js +70 -19
- package/dist/src/core/install-planner.js +62 -17
- package/dist/src/core/install-transaction.js +13 -1
- package/dist/src/core/journal/adapter.js +10 -1
- package/dist/src/core/paths.js +61 -8
- package/dist/src/core/profile.js +12 -0
- package/dist/src/core/provider-artifacts.js +38 -13
- package/dist/src/core/provider-version.js +12 -6
- package/dist/src/core/registries.js +40 -19
- package/dist/src/core/renderers/canonical-agent.js +33 -2
- package/dist/src/core/renderers/registry.js +37 -0
- package/dist/src/core/renderers/skill-source.js +11 -19
- package/dist/src/core/skill-integrity.js +71 -10
- package/dist/src/core/update-check.js +14 -2
- package/dist/src/core/versioning.js +17 -2
- package/dist/src/index.js +31 -23
- package/dist/src/providers/index.js +7 -2
- package/dist/src/ui/text.js +10 -2
- package/dist/src/utils/config.js +7 -8
- package/dist/tests/commands/doctor.test.js +6 -0
- package/dist/tests/commands/pin.test.js +29 -2
- package/dist/tests/commands/preflight/preflight.test.js +31 -17
- package/dist/tests/commands/registry/install-bundles.test.js +6 -6
- package/dist/tests/commands/sensors/init-pack-unavailable.test.js +96 -0
- package/dist/tests/commands/sensors/install-settings-safety.test.js +87 -0
- package/dist/tests/commands/sensors/run-is-read-only.test.js +0 -0
- package/dist/tests/commands/sensors/run.test.js +24 -51
- package/dist/tests/commands/sensors/status-windows.test.js +39 -22
- package/dist/tests/commands/sensors/status.test.js +27 -12
- package/dist/tests/core/artifact-name-containment.test.js +86 -0
- package/dist/tests/core/context/agents-md-single-slot.test.js +69 -0
- package/dist/tests/core/context/regenerate.test.js +39 -8
- package/dist/tests/core/diagnostics/checks.test.js +14 -6
- package/dist/tests/core/diagnostics/provider-tier.test.js +3 -3
- package/dist/tests/core/diagnostics/rendered-artifact-visibility.test.js +69 -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/all-baseline-bundles.test.js +64 -0
- package/dist/tests/core/init/context-injection-no-project.test.js +117 -0
- package/dist/tests/core/init/orchestrator.test.js +1 -1
- package/dist/tests/core/init/steps-registry-sync.test.js +1 -1
- package/dist/tests/core/init/steps.test.js +11 -5
- package/dist/tests/core/path-resolution-no-shell.test.js +136 -0
- package/dist/tests/core/paths.test.js +7 -34
- package/dist/tests/core/project-skill-links.test.js +159 -0
- package/dist/tests/core/provider-artifacts.test.js +66 -14
- package/dist/tests/core/provider-capability-guards.test.js +92 -0
- package/dist/tests/core/reconciliation.test.js +15 -15
- package/dist/tests/core/registry-manifest.test.js +1 -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/semver-fails-closed.test.js +30 -0
- package/dist/tests/core/skill-integrity.test.js +10 -10
- package/dist/tests/core/skill-repair-safety.test.js +92 -0
- package/dist/tests/core/sync-profile-atomicity.test.js +122 -0
- package/dist/tests/integration/copilot-init-isolated.test.js +2 -2
- package/dist/tests/providers/index.test.js +1 -1
- package/dist/tests/structural/renderer-table-is-single-source.test.js +95 -0
- package/dist/tests/structural/symlink-type-is-explicit.test.js +73 -0
- package/dist/tests/ui/text.test.js +9 -0
- package/dist/tests/utils/registry-view-overrides.test.js +2 -2
- package/package.json +6 -1
|
@@ -17,19 +17,12 @@ exports.gatherProviderChecks = gatherProviderChecks;
|
|
|
17
17
|
const fs_1 = __importDefault(require("fs"));
|
|
18
18
|
const path_1 = __importDefault(require("path"));
|
|
19
19
|
const providers_1 = require("../../providers");
|
|
20
|
+
const skill_integrity_1 = require("../skill-integrity");
|
|
20
21
|
const provider_version_1 = require("../provider-version");
|
|
21
22
|
const status_1 = require("../../commands/hooks/status");
|
|
22
23
|
const orchestrator_1 = require("../context/orchestrator");
|
|
23
24
|
const registries_1 = require("../registries");
|
|
24
|
-
|
|
25
|
-
* used by `skillsGlobalCheck` to require AWM-shaped evidence, not just an arbitrary
|
|
26
|
-
* non-empty directory (a user's own unrelated file in `~/.cursor/rules` would otherwise
|
|
27
|
-
* read as `'supported'`). Renderers absent from this map (i.e. `'link'`) never reach the
|
|
28
|
-
* branch that reads it. */
|
|
29
|
-
const RENDERED_SKILL_EXTENSIONS = {
|
|
30
|
-
'cursor-mdc': '.mdc',
|
|
31
|
-
'copilot-instructions': '.instructions.md',
|
|
32
|
-
};
|
|
25
|
+
const registry_1 = require("../renderers/registry");
|
|
33
26
|
/** Structural classification, computed purely from `provider`'s config shape — see
|
|
34
27
|
* `ProviderTier`'s doc comment in `types.ts` for what each tier means. */
|
|
35
28
|
function providerTier(provider) {
|
|
@@ -66,7 +59,7 @@ function binaryVersionCheck(agent) {
|
|
|
66
59
|
* `dir` is null — i.e. the provider has no global skill discovery mechanism at all
|
|
67
60
|
* (today: Copilot, see `globalUnsupportedReason` in providers/index.ts).
|
|
68
61
|
*
|
|
69
|
-
* `renderer` gates which verification is possible: `
|
|
62
|
+
* `renderer` gates which verification is possible: `classifySkillLinks` (via `integrity`)
|
|
70
63
|
* only ever sees symlinks — `if (!lst.isSymbolicLink()) continue;` — so for a rendered
|
|
71
64
|
* format (`cursor-mdc`, `copilot-instructions`) it scans a directory of real files and
|
|
72
65
|
* finds nothing, which would make `broken` silently read 0 regardless of whether the
|
|
@@ -87,7 +80,7 @@ function skillsGlobalCheck(dir, owners, integrity, renderer) {
|
|
|
87
80
|
// `remediationCode: 'awm-init'` is a worse remedy than none, but this
|
|
88
81
|
// check has no channel to report "can't tell" separately from "absent"
|
|
89
82
|
// (same tradeoff already made by this file's agentsNativeCheck and by
|
|
90
|
-
// skill-integrity.ts's
|
|
83
|
+
// skill-integrity.ts's classifySkillLinks — a systemic, pre-existing
|
|
91
84
|
// pattern in this codebase, not introduced here).
|
|
92
85
|
entries = [];
|
|
93
86
|
}
|
|
@@ -97,7 +90,12 @@ function skillsGlobalCheck(dir, owners, integrity, renderer) {
|
|
|
97
90
|
// Still not full integrity verification (a stray file with the right extension but
|
|
98
91
|
// wrong content still passes) — that residual gap is the same honest tradeoff this
|
|
99
92
|
// function's doc comment already accepts for the non-`'link'` branch generally.
|
|
100
|
-
|
|
93
|
+
// `rendererExtension` (core/renderers/registry.ts) is the ONE table mapping a
|
|
94
|
+
// renderer to the file it produces. This site used to keep its own partial copy —
|
|
95
|
+
// the fourth such copy in the codebase, and the exact drift that made a rendered
|
|
96
|
+
// artifact invisible to the "is it installed" check for a whole release. A new
|
|
97
|
+
// renderer must not be able to be added without this reading it.
|
|
98
|
+
const ext = (0, registry_1.rendererExtension)(renderer);
|
|
101
99
|
const present = ext ? entries.some((e) => e.endsWith(ext)) : entries.length > 0;
|
|
102
100
|
return {
|
|
103
101
|
id: 'skills.global',
|
|
@@ -165,20 +163,69 @@ function tomlAgentsHealthy(dir, entries) {
|
|
|
165
163
|
* run (e.g. claude-code-only, opencode-only, antigravity-only) would always render
|
|
166
164
|
* inapplicable rows as red ✖ and `overall` could never be 'healthy'.
|
|
167
165
|
*/
|
|
166
|
+
/**
|
|
167
|
+
* Workflows installed at machine scope. Today only Antigravity declares a `workflow`
|
|
168
|
+
* config at all (`~/.gemini/antigravity/global_workflows`), and nothing verified it:
|
|
169
|
+
* `awm init` installed the baseline's workflows there and every diagnostic looked only
|
|
170
|
+
* at skills and native agents, so a broken or emptied workflow directory reported
|
|
171
|
+
* `healthy` forever on the one provider that uses it.
|
|
172
|
+
*
|
|
173
|
+
* Same N/A discipline as `agentsNativeCheck`: a provider with no workflow config, or a
|
|
174
|
+
* registry that ships no `workflows/`, emits NO row rather than a red one nobody can
|
|
175
|
+
* act on — an absent row means "nothing to verify", not "verified fine".
|
|
176
|
+
*/
|
|
177
|
+
function workflowsGlobalCheck(agent) {
|
|
178
|
+
const provider = (0, providers_1.providerFor)(agent);
|
|
179
|
+
if (!provider.workflow || provider.workflow.global === null)
|
|
180
|
+
return null;
|
|
181
|
+
const dir = provider.workflow.global;
|
|
182
|
+
let entries;
|
|
183
|
+
try {
|
|
184
|
+
entries = fs_1.default.readdirSync(dir);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
if (entries.length === 0)
|
|
190
|
+
return null;
|
|
191
|
+
// Los workflows se instalan con el renderer `link`, asi que un symlink colgante es
|
|
192
|
+
// exactamente la misma clase de rotura que en skills — y se clasifica con la misma
|
|
193
|
+
// funcion, no con una copia local que pueda divergir.
|
|
194
|
+
const integrity = (0, skill_integrity_1.classifySkillLinks)(dir, (0, registries_1.contentRoots)());
|
|
195
|
+
const broken = integrity.repairable.length + integrity.dead.length;
|
|
196
|
+
return {
|
|
197
|
+
id: 'workflows.global',
|
|
198
|
+
state: broken > 0 ? 'broken' : 'healthy',
|
|
199
|
+
target: dir,
|
|
200
|
+
detail: broken > 0 ? `${broken} broken link(s)` : undefined,
|
|
201
|
+
remediationCode: broken > 0 ? 'awm-init' : undefined,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
168
204
|
function agentsNativeCheck(agent) {
|
|
169
205
|
const provider = (0, providers_1.providerFor)(agent);
|
|
170
206
|
if (!provider.agent || provider.agent.global === null)
|
|
171
207
|
return null;
|
|
172
208
|
const dir = provider.agent.global;
|
|
173
209
|
let entries;
|
|
210
|
+
// `absent` degrada el estado global, asi que sin `remediationCode` doctor
|
|
211
|
+
// salia 1 sin decir que hacer — y para un registry que simplemente no trae
|
|
212
|
+
// `agents/` (lo normal) ese rojo no tiene accion posible. Se reporta como
|
|
213
|
+
// `unsupported`, que describe la realidad: no hay artefactos nativos que
|
|
214
|
+
// verificar, y no es culpa de la instalacion.
|
|
215
|
+
// Un registry que simplemente no trae `agents/` es lo normal, no un defecto
|
|
216
|
+
// de la instalacion — y no hay accion que el usuario pueda tomar. Antes esto
|
|
217
|
+
// devolvia `absent`, un estado que DEGRADA, y sin `remediationCode`: doctor
|
|
218
|
+
// salia 1 mostrando `✖ native agents` sin decir que hacer, justo despues de
|
|
219
|
+
// un `awm init` exitoso. Cuando no hay nada nativo que verificar, no se
|
|
220
|
+
// emite fila — el mismo criterio que ya usan los demas casos N/A de aca.
|
|
174
221
|
try {
|
|
175
222
|
entries = fs_1.default.readdirSync(dir);
|
|
176
223
|
}
|
|
177
224
|
catch {
|
|
178
|
-
return
|
|
225
|
+
return null;
|
|
179
226
|
}
|
|
180
227
|
if (entries.length === 0)
|
|
181
|
-
return
|
|
228
|
+
return null;
|
|
182
229
|
if (provider.agent.renderer === 'codex-agent-toml') {
|
|
183
230
|
const { broken } = tomlAgentsHealthy(dir, entries);
|
|
184
231
|
return {
|
|
@@ -292,6 +339,7 @@ function gatherProviderChecks(agents, scanSkills, projectRoot) {
|
|
|
292
339
|
binaryVersionCheck(agent),
|
|
293
340
|
skillsGlobalCheck(dir, owners, integrity, provider.skill.renderer),
|
|
294
341
|
agentsNativeCheck(agent),
|
|
342
|
+
workflowsGlobalCheck(agent),
|
|
295
343
|
hookTrustCheck(agent),
|
|
296
344
|
contextGlobalCheck(agent, projectRoot),
|
|
297
345
|
].filter((check) => check !== null);
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.matchFrontmatterBlock =
|
|
6
|
+
exports.findFrontmatterDescription = exports.isBlockScalarHeader = exports.readFrontmatterDescription = exports.matchFrontmatterBlock = void 0;
|
|
7
7
|
exports.readArtifactDescription = readArtifactDescription;
|
|
8
8
|
exports.discoverSkills = discoverSkills;
|
|
9
9
|
exports.discoverWorkflows = discoverWorkflows;
|
|
@@ -12,31 +12,23 @@ exports.discoverAgents = discoverAgents;
|
|
|
12
12
|
const fs_1 = __importDefault(require("fs"));
|
|
13
13
|
const path_1 = __importDefault(require("path"));
|
|
14
14
|
const registries_1 = require("./registries");
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
// El parseo de frontmatter vive en el modulo HOJA `core/frontmatter.ts`
|
|
16
|
+
// (sin imports, para que consumidores puros como export/transform.ts no
|
|
17
|
+
// arrastren fs/git por transitividad). Se re-exporta aca porque este modulo
|
|
18
|
+
// ya era el punto de entrada historico para esos helpers.
|
|
19
|
+
var frontmatter_1 = require("./frontmatter");
|
|
20
|
+
Object.defineProperty(exports, "matchFrontmatterBlock", { enumerable: true, get: function () { return frontmatter_1.matchFrontmatterBlock; } });
|
|
21
|
+
Object.defineProperty(exports, "readFrontmatterDescription", { enumerable: true, get: function () { return frontmatter_1.readFrontmatterDescription; } });
|
|
22
|
+
Object.defineProperty(exports, "isBlockScalarHeader", { enumerable: true, get: function () { return frontmatter_1.isBlockScalarHeader; } });
|
|
23
|
+
Object.defineProperty(exports, "findFrontmatterDescription", { enumerable: true, get: function () { return frontmatter_1.findFrontmatterDescription; } });
|
|
24
|
+
const frontmatter_2 = require("./frontmatter");
|
|
20
25
|
function readArtifactDescription(filePath) {
|
|
21
26
|
try {
|
|
22
27
|
const raw = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
23
|
-
const frontmatter = matchFrontmatterBlock(raw);
|
|
28
|
+
const frontmatter = (0, frontmatter_2.matchFrontmatterBlock)(raw);
|
|
24
29
|
if (frontmatter === null)
|
|
25
30
|
return '';
|
|
26
|
-
|
|
27
|
-
.split(/\r?\n/)
|
|
28
|
-
.find((l) => /^description\s*:/.test(l));
|
|
29
|
-
if (!line)
|
|
30
|
-
return '';
|
|
31
|
-
let val = line.replace(/^description\s*:/, '').trim();
|
|
32
|
-
if ((val.startsWith('"') && val.endsWith('"')) ||
|
|
33
|
-
(val.startsWith("'") && val.endsWith("'"))) {
|
|
34
|
-
val = val.slice(1, -1);
|
|
35
|
-
}
|
|
36
|
-
const BLOCK_INDICATORS = new Set(['>-', '>', '|-', '|', '>+', '|+']);
|
|
37
|
-
if (BLOCK_INDICATORS.has(val.trim()))
|
|
38
|
-
return '';
|
|
39
|
-
return val.trim();
|
|
31
|
+
return (0, frontmatter_2.readFrontmatterDescription)(frontmatter);
|
|
40
32
|
}
|
|
41
33
|
catch {
|
|
42
34
|
return '';
|
|
@@ -102,7 +94,7 @@ function discoverWorkflows(roots = (0, registries_1.contentRoots)()) {
|
|
|
102
94
|
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
103
95
|
if (entry.isDirectory() || !entry.name.endsWith('.md'))
|
|
104
96
|
continue;
|
|
105
|
-
const name = entry.name.replace(
|
|
97
|
+
const name = entry.name.replace(/\.md$/, '');
|
|
106
98
|
const filePath = path_1.default.join(dir, entry.name);
|
|
107
99
|
mergeEntry('workflow', byName, {
|
|
108
100
|
name,
|
|
@@ -128,7 +120,7 @@ function discoverAgents(roots = (0, registries_1.contentRoots)()) {
|
|
|
128
120
|
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
129
121
|
if (entry.isDirectory() || !entry.name.endsWith('.md'))
|
|
130
122
|
continue;
|
|
131
|
-
const name = entry.name.replace(
|
|
123
|
+
const name = entry.name.replace(/\.md$/, '');
|
|
132
124
|
const filePath = path_1.default.join(dir, entry.name);
|
|
133
125
|
mergeEntry('agent', byName, {
|
|
134
126
|
name,
|
|
@@ -7,7 +7,10 @@ exports.claudeAiTransform = claudeAiTransform;
|
|
|
7
7
|
//
|
|
8
8
|
// Transform mecánico claude.ai (R3.1): función pura string → string.
|
|
9
9
|
// Frontmatter line-based plano (los SKILL.md del baseline usan claves de una
|
|
10
|
-
// línea) — sin parser YAML a propósito (YAGNI, cero deps).
|
|
10
|
+
// línea) — sin parser YAML a propósito (YAGNI, cero deps). La única forma
|
|
11
|
+
// multilínea soportada es el block scalar de `description:`, resuelto vía la
|
|
12
|
+
// función compartida de discovery.ts (ver claudeAiTransform).
|
|
13
|
+
const frontmatter_1 = require("../frontmatter");
|
|
11
14
|
const DEFERENCE_LINE = (skillName) => `In environments with AWM installed (Claude Code), defer to the registry's ${skillName} skill — this port is for environments without filesystem access.`;
|
|
12
15
|
exports.DEFERENCE_LINE = DEFERENCE_LINE;
|
|
13
16
|
// Paths intra-registry: resuelven en Claude Code (donde el registry está en
|
|
@@ -73,34 +76,36 @@ function claudeAiTransform(skillMd, skillName) {
|
|
|
73
76
|
const body = skillMd.slice(end + endMatch[0].length);
|
|
74
77
|
const fmLines = skillMd.slice(startLen, end).split(/\r?\n/)
|
|
75
78
|
.filter((l) => !/^(version|portable):/.test(l));
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
// UN SOLO camino para todas las formas de `description`. Antes habia uno por
|
|
80
|
+
// forma (plano / entrecomillado / bloque) y cada vez que el LECTOR aprendia
|
|
81
|
+
// una forma nueva, el ESCRITOR de aca quedaba atras — la divergencia exacta
|
|
82
|
+
// que este modulo dice cerrar. Casos reales que produjo esa asimetria:
|
|
83
|
+
// - escalar plano multilinea: la deference line se insertaba en la primera
|
|
84
|
+
// linea y la continuacion quedaba huerfana debajo, o sea la frase de
|
|
85
|
+
// deference terminaba enterrada en el medio de la descripcion;
|
|
86
|
+
// - escalar plano con ` # comentario` final: la deference line se anexaba
|
|
87
|
+
// DESPUES del `#`, o sea YAML se la comia entera como comentario y el
|
|
88
|
+
// artefacto exportado perdia en silencio la unica frase que este
|
|
89
|
+
// transform existe para agregar.
|
|
90
|
+
// Ahora se resuelve el campo con la funcion compartida, se reemplaza su
|
|
91
|
+
// extension COMPLETA (startLine..endLine) y se emite siempre un escalar
|
|
92
|
+
// double-quoted via JSON.stringify — superset valido de YAML que cubre
|
|
93
|
+
// comillas, `:`, `#` y los `\n` de un literal `|` sin decidir estilo.
|
|
94
|
+
const field = (0, frontmatter_1.findFrontmatterDescription)(fmLines.join('\n'));
|
|
95
|
+
if (field.startLine === -1) {
|
|
78
96
|
throw new Error('frontmatter has no description field');
|
|
79
97
|
}
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
98
|
+
const rawValue = fmLines[field.startLine].replace(/^description\s*:/, '').trim();
|
|
99
|
+
// Empieza con `>`/`|` pero no es un indicador bien formado (ej. `>-basura`):
|
|
100
|
+
// YAML mismo lo rechaza. Fallar explicito en vez de publicar basura.
|
|
101
|
+
if (/^[>|]/.test(rawValue) && !(0, frontmatter_1.isBlockScalarHeader)(rawValue)) {
|
|
102
|
+
throw new Error(`malformed block scalar indicator in description: ${rawValue}`);
|
|
84
103
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
// single- and double-quoted scalars are first-class, and we work off the
|
|
88
|
-
// trimmed value so trailing whitespace after a closing quote doesn't fool us.
|
|
89
|
-
const isDoubleQuoted = value.length >= 2 && value.startsWith('"') && value.endsWith('"');
|
|
90
|
-
const isSingleQuoted = value.length >= 2 && value.startsWith("'") && value.endsWith("'");
|
|
91
|
-
if ((value.startsWith('"') || value.startsWith("'")) && !isDoubleQuoted && !isSingleQuoted) {
|
|
92
|
-
throw new Error('description has trailing content after its closing quote (e.g. an inline comment) — not supported by the export transform; remove the comment or use a port.claude-ai.md override');
|
|
104
|
+
if (!field.value) {
|
|
105
|
+
throw new Error((0, frontmatter_1.isBlockScalarHeader)(rawValue) ? 'description block scalar has no content' : 'description is empty');
|
|
93
106
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// apostrophe, so it must be escaped before splicing into a single-quoted
|
|
97
|
-
// description or it would prematurely close the YAML string.
|
|
98
|
-
const newValue = isDoubleQuoted
|
|
99
|
-
? `${value.slice(0, -1)} ${deference}"`
|
|
100
|
-
: isSingleQuoted
|
|
101
|
-
? `${value.slice(0, -1)} ${deference.replace(/'/g, "''")}'`
|
|
102
|
-
: `${value} ${deference}`;
|
|
103
|
-
fmLines[descIdx] = `description: ${newValue}`;
|
|
107
|
+
const merged = JSON.stringify(`${field.value} ${(0, exports.DEFERENCE_LINE)(skillName)}`);
|
|
108
|
+
fmLines.splice(field.startLine, field.endLine - field.startLine + 1, `description: ${merged}`);
|
|
104
109
|
// Solo el body: el frontmatter ya se editó arriba y sus campos no son prosa
|
|
105
110
|
// navegable (R2.4).
|
|
106
111
|
return `---\n${fmLines.join('\n')}\n---\n${stripIntraRegistryPaths(body)}`;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/core/frontmatter.ts
|
|
3
|
+
//
|
|
4
|
+
// Modulo HOJA: sin imports. Es la unica fuente de verdad para leer el
|
|
5
|
+
// frontmatter YAML de un artefacto (SKILL.md, agents/*.md, workflows/*.md).
|
|
6
|
+
//
|
|
7
|
+
// Por que hoja: sus consumidores incluyen `core/export/transform.ts`, que se
|
|
8
|
+
// documenta como funcion pura string -> string sin dependencias. Si esto
|
|
9
|
+
// viviera en `discovery.ts` (que importa `registries.ts`, y este a su vez
|
|
10
|
+
// resuelve `awmHome()` y carga `simple-git` en tiempo de import), un modulo
|
|
11
|
+
// puro terminaria arrastrando la capa de fs/git por transitividad.
|
|
12
|
+
//
|
|
13
|
+
// Por que existe: el frontmatter se parseaba a mano en CUATRO lugares, cada
|
|
14
|
+
// uno con su propia copia incompleta. Un block scalar YAML valido
|
|
15
|
+
// (`description: >-`, con el texto en las lineas indentadas siguientes)
|
|
16
|
+
// degradaba en silencio en discovery, crasheaba `awm add -a cursor|copilot`,
|
|
17
|
+
// abortaba `awm export <bundle>` entero y rompia `awm add -a codex`. La
|
|
18
|
+
// semantica de aca se verifica contra js-yaml (parser real) en
|
|
19
|
+
// tests/core/frontmatter-description-vs-yaml.test.ts — no contra valores
|
|
20
|
+
// esperados escritos a mano, que pueden envejecer hacia la expectativa
|
|
21
|
+
// equivocada.
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.matchFrontmatterBlock = matchFrontmatterBlock;
|
|
24
|
+
exports.isBlockScalarHeader = isBlockScalarHeader;
|
|
25
|
+
exports.findFrontmatterDescription = findFrontmatterDescription;
|
|
26
|
+
exports.readFrontmatterDescription = readFrontmatterDescription;
|
|
27
|
+
/** Texto crudo del frontmatter (entre los delimitadores `---`), o null si el
|
|
28
|
+
* bloque falta o esta mal formado. Tolera CRLF. */
|
|
29
|
+
function matchFrontmatterBlock(raw) {
|
|
30
|
+
const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
31
|
+
return fmMatch ? fmMatch[1] : null;
|
|
32
|
+
}
|
|
33
|
+
/** Indicador de block scalar en la linea de la clave: estilo (`>` folded /
|
|
34
|
+
* `|` literal) + chomping opcional (`-`/`+`) + indentacion explicita opcional
|
|
35
|
+
* + comentario final opcional. Detalles verificados contra js-yaml:
|
|
36
|
+
* - la indentacion explicita es UN digito 1-9 (ni `0` ni `10`);
|
|
37
|
+
* - `>- # nota` es valido y su bloque se lee normal;
|
|
38
|
+
* - `>-basura` NO es valido y aca tampoco matchea. */
|
|
39
|
+
const BLOCK_SCALAR_HEADER = /^([>|])(?:([+-])?([1-9])?|([1-9])?([+-])?)(?:\s+#.*)?$/;
|
|
40
|
+
/** ¿El valor de la linea de la clave es un indicador de block scalar?
|
|
41
|
+
* Cualquier caller que RAMIFIQUE sobre "esto es un bloque" debe usar esta
|
|
42
|
+
* funcion, nunca una aproximacion propia: una guarda por prefijo (`/^[>|]/`)
|
|
43
|
+
* y un resolver por match completo se desincronizan, y en ese hueco el
|
|
44
|
+
* escritor borro contenido real creyendo que resolvia un bloque. */
|
|
45
|
+
function isBlockScalarHeader(value) {
|
|
46
|
+
return BLOCK_SCALAR_HEADER.test(value.trim());
|
|
47
|
+
}
|
|
48
|
+
/** Indice de la comilla que CIERRA el escalar que empieza en la posicion 0, o
|
|
49
|
+
* -1 si no cierra. Cada estilo escapa distinto: en single-quoted un `''` es
|
|
50
|
+
* un apostrofe literal (no un cierre); en double-quoted un `\"` es una
|
|
51
|
+
* comilla literal. */
|
|
52
|
+
function findClosingQuote(value, quote) {
|
|
53
|
+
for (let i = 1; i < value.length; i++) {
|
|
54
|
+
if (quote === '"' && value[i] === '\\') {
|
|
55
|
+
i++;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (value[i] !== quote)
|
|
59
|
+
continue;
|
|
60
|
+
if (quote === "'" && value[i + 1] === "'") {
|
|
61
|
+
i++;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
return i;
|
|
65
|
+
}
|
|
66
|
+
return -1;
|
|
67
|
+
}
|
|
68
|
+
function foldBlock(stripped, isLiteral) {
|
|
69
|
+
if (isLiteral)
|
|
70
|
+
return stripped.join('\n').trim();
|
|
71
|
+
// Folded (`>`): las lineas al nivel base del bloque se pliegan entre si con
|
|
72
|
+
// UN espacio; una linea MAS indentada no se pliega (conserva su salto y su
|
|
73
|
+
// sangria — la regla que permite incrustar una lista dentro de un folded);
|
|
74
|
+
// y k lineas en blanco consecutivas producen k saltos de linea.
|
|
75
|
+
// Ojo: NO se hace trim por linea — YAML conserva los espacios finales de
|
|
76
|
+
// cada linea y el plegado agrega el suyo encima.
|
|
77
|
+
let out = '';
|
|
78
|
+
let prevLiteral = false;
|
|
79
|
+
let started = false;
|
|
80
|
+
let blanks = 0;
|
|
81
|
+
for (const line of stripped) {
|
|
82
|
+
if (line === '') {
|
|
83
|
+
blanks++;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const literal = /^[ \t]/.test(line);
|
|
87
|
+
if (!started) {
|
|
88
|
+
out = line;
|
|
89
|
+
started = true;
|
|
90
|
+
prevLiteral = literal;
|
|
91
|
+
blanks = 0;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (blanks > 0) {
|
|
95
|
+
out += '\n'.repeat(blanks);
|
|
96
|
+
blanks = 0;
|
|
97
|
+
}
|
|
98
|
+
else if (literal || prevLiteral)
|
|
99
|
+
out += '\n';
|
|
100
|
+
else
|
|
101
|
+
out += ' ';
|
|
102
|
+
out += line;
|
|
103
|
+
prevLiteral = literal;
|
|
104
|
+
}
|
|
105
|
+
return out.trim();
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Localiza y RESUELVE el campo `description` de un bloque de frontmatter,
|
|
109
|
+
* devolviendo tambien la extension exacta que ocupa (para que un escritor
|
|
110
|
+
* pueda reemplazarlo entero).
|
|
111
|
+
*
|
|
112
|
+
* Formas soportadas, todas contrastadas contra js-yaml: escalar plano (de una
|
|
113
|
+
* o varias lineas), escalar entrecomillado (simple y doble, con su escape
|
|
114
|
+
* propio deshecho), y block scalar folded/literal con chomping e indentacion
|
|
115
|
+
* explicita.
|
|
116
|
+
*/
|
|
117
|
+
function findFrontmatterDescription(frontmatter) {
|
|
118
|
+
const lines = frontmatter.split(/\r?\n/);
|
|
119
|
+
// Ancla en COLUMNA 0 a proposito: `description` en un frontmatter es
|
|
120
|
+
// siempre clave de nivel superior. Un `^\s*` haria ganar a un
|
|
121
|
+
// `description:` INDENTADO — anidado en otro mapa, o una linea de
|
|
122
|
+
// CONTENIDO de otro block scalar que casualmente empiece asi.
|
|
123
|
+
const startLine = lines.findIndex((l) => /^description\s*:/.test(l));
|
|
124
|
+
if (startLine === -1)
|
|
125
|
+
return { value: '', startLine: -1, endLine: -1 };
|
|
126
|
+
const raw = lines[startLine].replace(/^description\s*:/, '').trim();
|
|
127
|
+
if (isBlockScalarHeader(raw)) {
|
|
128
|
+
const isLiteral = raw.trim().startsWith('|');
|
|
129
|
+
// El bloque son las lineas ESTRICTAMENTE indentadas respecto de la
|
|
130
|
+
// clave (columna 0). Las lineas en blanco no lo cortan: pertenecen a el.
|
|
131
|
+
const block = [];
|
|
132
|
+
let last = startLine;
|
|
133
|
+
for (let i = startLine + 1; i < lines.length; i++) {
|
|
134
|
+
if (lines[i].trim() === '') {
|
|
135
|
+
block.push(lines[i]);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (!/^[ \t]/.test(lines[i]))
|
|
139
|
+
break;
|
|
140
|
+
block.push(lines[i]);
|
|
141
|
+
last = i;
|
|
142
|
+
}
|
|
143
|
+
while (block.length > 0 && block[block.length - 1].trim() === '')
|
|
144
|
+
block.pop();
|
|
145
|
+
const first = block.find((l) => l.trim() !== '');
|
|
146
|
+
if (first === undefined)
|
|
147
|
+
return { value: '', startLine, endLine: startLine };
|
|
148
|
+
const declared = raw.match(/[1-9]/);
|
|
149
|
+
const blockIndent = declared !== null ? Number(declared[0]) : first.match(/^[ \t]*/)[0].length;
|
|
150
|
+
// Una linea con MENOS indentacion que el bloque es YAML invalido
|
|
151
|
+
// (js-yaml lanza "bad indentation"). Cortar a ciegas con slice()
|
|
152
|
+
// comeria caracteres reales: preferimos no inventar un valor.
|
|
153
|
+
if (block.some((l) => l.trim() !== '' && l.match(/^[ \t]*/)[0].length < blockIndent)) {
|
|
154
|
+
return { value: '', startLine, endLine: last };
|
|
155
|
+
}
|
|
156
|
+
const stripped = block.map((l) => l.slice(blockIndent));
|
|
157
|
+
return { value: foldBlock(stripped, isLiteral), startLine, endLine: last };
|
|
158
|
+
}
|
|
159
|
+
// Empieza con `>`/`|` pero NO es un indicador bien formado (`>0`, `>12`,
|
|
160
|
+
// `>-basura`): YAML lo rechaza. Devolver '' hace que los consumidores
|
|
161
|
+
// fallen fuerte (throw en parseSkillSource / claudeAiTransform) en vez de
|
|
162
|
+
// caer al camino de escalar plano de abajo, que publicaria el indicador
|
|
163
|
+
// mismo como si fuera la descripcion.
|
|
164
|
+
if (/^[>|]/.test(raw))
|
|
165
|
+
return { value: '', startLine, endLine: startLine };
|
|
166
|
+
// Continuacion multilinea. Aplica a DOS formas, y ambas la necesitan:
|
|
167
|
+
// - escalar plano (`description: primera` + lineas indentadas), que YAML
|
|
168
|
+
// pliega con espacios igual que un `>`;
|
|
169
|
+
// - escalar ENTRECOMILLADO cuya comilla de cierre esta en una linea
|
|
170
|
+
// posterior (`description: "hola` / ` mundo"`), que sin esto devolvia
|
|
171
|
+
// `"hola` — con la comilla de apertura pegada — como descripcion.
|
|
172
|
+
// Sin juntar las lineas primero, del lado escritor ademas quedaban
|
|
173
|
+
// huerfanas en la salida exportada.
|
|
174
|
+
let endLine = startLine;
|
|
175
|
+
let value = raw;
|
|
176
|
+
const opensUnclosedQuote = /^['"]/.test(raw) && findClosingQuote(raw, raw[0]) === -1;
|
|
177
|
+
if (raw !== '' && (!/^['"]/.test(raw) || opensUnclosedQuote)) {
|
|
178
|
+
const cont = [];
|
|
179
|
+
for (let i = startLine + 1; i < lines.length; i++) {
|
|
180
|
+
if (lines[i].trim() === '')
|
|
181
|
+
break;
|
|
182
|
+
if (!/^[ \t]/.test(lines[i]))
|
|
183
|
+
break;
|
|
184
|
+
cont.push(lines[i].trim());
|
|
185
|
+
endLine = i;
|
|
186
|
+
}
|
|
187
|
+
if (cont.length > 0)
|
|
188
|
+
value = [raw, ...cont].join(' ');
|
|
189
|
+
}
|
|
190
|
+
// Escalares entrecomillados. Dos cosas que no alcanzan por separado:
|
|
191
|
+
// 1. Hay que ENCONTRAR la comilla de cierre real antes de mirar el resto:
|
|
192
|
+
// lo que sigue a esa comilla es un comentario, no parte del valor.
|
|
193
|
+
// Comparar con `endsWith` fallaba justo cuando habia comentario
|
|
194
|
+
// (`description: "x" # nota` devolvia `"x"` CON las comillas literales,
|
|
195
|
+
// mientras js-yaml devuelve `x`).
|
|
196
|
+
// 2. Hay que deshacer el escape propio de cada estilo: single-quoted
|
|
197
|
+
// duplica el apostrofe (`''`); double-quoted es superset de la string
|
|
198
|
+
// JSON, asi que JSON.parse resuelve \n, \t, \", \\ y \uXXXX.
|
|
199
|
+
const quote = value[0];
|
|
200
|
+
if (quote === "'" || quote === '"') {
|
|
201
|
+
const close = findClosingQuote(value, quote);
|
|
202
|
+
if (close !== -1) {
|
|
203
|
+
const scalar = value.slice(0, close + 1);
|
|
204
|
+
if (quote === "'") {
|
|
205
|
+
return { value: scalar.slice(1, -1).replace(/''/g, "'").trim(), startLine, endLine };
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
return { value: String(JSON.parse(scalar)).trim(), startLine, endLine };
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return { value: scalar.slice(1, -1).trim(), startLine, endLine };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Escalar plano: un `#` PRECEDIDO DE ESPACIO abre un comentario y no forma
|
|
216
|
+
// parte del valor (un `#` pegado a texto, como en `C#`, si). Dentro de
|
|
217
|
+
// comillas el `#` es literal, y esas ramas ya retornaron.
|
|
218
|
+
return { value: value.replace(/\s+#.*$/, '').trim(), startLine, endLine };
|
|
219
|
+
}
|
|
220
|
+
/** Azucar para el caso comun: solo el valor resuelto de `description`. */
|
|
221
|
+
function readFrontmatterDescription(frontmatter) {
|
|
222
|
+
return findFrontmatterDescription(frontmatter).value;
|
|
223
|
+
}
|
|
@@ -110,6 +110,17 @@ function planInitMutationTargets(params) {
|
|
|
110
110
|
for (const b of machineBundles) {
|
|
111
111
|
addBundleTargets(targets, b.name, bundles, agent, 'global', cwd, contentDir);
|
|
112
112
|
}
|
|
113
|
+
// Providers whose context injection is itself project-scope (managed-agents-md
|
|
114
|
+
// with a null globalPath — Cursor, Copilot) write AGENTS.md and the materialized
|
|
115
|
+
// .awm/context/ file. `stepContextInjection` now refuses to write those without a
|
|
116
|
+
// discovered project root, but enumerating them against `cwd` unconditionally
|
|
117
|
+
// costs nothing (a target that goes unwritten is a no-op backup entry, per this
|
|
118
|
+
// module's opening note) and keeps the guarantee from resting on a single site:
|
|
119
|
+
// under-enumeration here is the one failure mode that silently defeats rollback.
|
|
120
|
+
if (injection?.type === 'managed-agents-md' && injection.globalPath === null) {
|
|
121
|
+
targets.add(path_1.default.join(cwd, path_1.default.basename(injection.localFile)));
|
|
122
|
+
targets.add((0, materializer_1.projectContextPath)(cwd));
|
|
123
|
+
}
|
|
113
124
|
// project-level: profile, sensors manifest, project injection, and every
|
|
114
125
|
// extension currently recorded in .awm/profile.json
|
|
115
126
|
const projectRoot = (0, profile_1.findProjectRoot)(cwd);
|