genesis-compiler 1.2.11 → 1.2.12
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
CHANGED
|
@@ -122,9 +122,12 @@ Genesis does not install substitute generic `nodejs`, `vue`, `php`, or similar
|
|
|
122
122
|
skills. Official, user, or host skills retain their normal names. A Stack piece
|
|
123
123
|
may instead name one authoritative technology skill; Genesis copies that
|
|
124
124
|
complete directory into `.agents/skills/`, including its `references/`,
|
|
125
|
-
`scripts/`, `assets/`, and agent metadata.
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
`scripts/`, `assets/`, and agent metadata. Every relative Markdown link and image
|
|
126
|
+
must resolve within that same Skill directory after relocation; a missing
|
|
127
|
+
target or path that escapes the Skill root makes the Skill invalid. The agent
|
|
128
|
+
loads valid resources only when the Skill requires them. For example, the
|
|
129
|
+
`genesis-stack` catalog's JSKIT piece installs the JSKIT package's own `jskit`
|
|
130
|
+
skill. A piece's optional
|
|
128
131
|
`## Guidance` is concise
|
|
129
132
|
supplemental project-work guidance; it does not create or replace a generic
|
|
130
133
|
technology skill.
|
package/docs/stack-components.md
CHANGED
|
@@ -126,7 +126,10 @@ selects one complete [Agent Skills](https://agentskills.io) directory, either
|
|
|
126
126
|
from Genesis or from the declared npm package. Its `SKILL.md`, `references/`,
|
|
127
127
|
`scripts/`, `assets/`, and `agents/` metadata are copied together to
|
|
128
128
|
`.agents/skills/<skill-name>/`; the agent loads them progressively instead of
|
|
129
|
-
Genesis expanding every manual into every prompt.
|
|
129
|
+
Genesis expanding every manual into every prompt. Relative Markdown links and
|
|
130
|
+
images must resolve within that copied Skill root. Missing targets and paths
|
|
131
|
+
that escape the root are invalid, even if an escaped path happens to exist in
|
|
132
|
+
the source package or project. `Resources` declares generic
|
|
130
133
|
alternative environment-name sets reported by prompt generation and checked
|
|
131
134
|
before verification. `allowEmpty` may name a required variable whose empty
|
|
132
135
|
string is valid. `Environment defaults` declares public non-secret constants
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genesis-compiler",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "An agent-independent prompt, multi-language code-index, cleanup, and verification companion with optional Codex hooks.",
|
|
6
6
|
"repository": {
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"@ast-grep/lang-ruby": "^0.0.7",
|
|
58
58
|
"@ast-grep/lang-rust": "^0.0.7",
|
|
59
59
|
"@ast-grep/napi": "^0.45.1",
|
|
60
|
+
"mdast-util-from-markdown": "^2.0.3",
|
|
60
61
|
"yaml": "^2.9.0"
|
|
61
62
|
}
|
|
62
63
|
}
|
|
@@ -12,9 +12,10 @@ import {
|
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import { fileURLToPath } from 'node:url';
|
|
14
14
|
|
|
15
|
+
import { fromMarkdown } from 'mdast-util-from-markdown';
|
|
15
16
|
import { parse as parseYaml } from 'yaml';
|
|
16
17
|
|
|
17
|
-
import { GenesisError } from './errors.js';
|
|
18
|
+
import { asDiagnostic, GenesisError } from './errors.js';
|
|
18
19
|
import { sha256, stableJson, writeFileAtomic } from './utils.js';
|
|
19
20
|
|
|
20
21
|
const packageRoot = fileURLToPath(new URL('../../', import.meta.url));
|
|
@@ -76,6 +77,90 @@ function frontmatter(source, location) {
|
|
|
76
77
|
return metadata;
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
function markdownDestinations(tree) {
|
|
81
|
+
const destinations = [];
|
|
82
|
+
const nodes = [tree];
|
|
83
|
+
while (nodes.length > 0) {
|
|
84
|
+
const node = nodes.pop();
|
|
85
|
+
if (['definition', 'image', 'link'].includes(node?.type) && typeof node.url === 'string') {
|
|
86
|
+
destinations.push({ line: node.position?.start?.line, url: node.url });
|
|
87
|
+
}
|
|
88
|
+
if (Array.isArray(node?.children)) nodes.push(...node.children);
|
|
89
|
+
}
|
|
90
|
+
return destinations.sort((left, right) => (left.line || 0) - (right.line || 0));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function localReferencePath(url) {
|
|
94
|
+
const value = String(url || '').trim();
|
|
95
|
+
if (
|
|
96
|
+
!value
|
|
97
|
+
|| value.startsWith('#')
|
|
98
|
+
|| value.startsWith('?')
|
|
99
|
+
|| value.startsWith('/')
|
|
100
|
+
|| /^[a-z][a-z\d+.-]*:/iu.test(value)
|
|
101
|
+
) return null;
|
|
102
|
+
const separator = value.search(/[?#]/u);
|
|
103
|
+
const encodedPath = separator === -1 ? value : value.slice(0, separator);
|
|
104
|
+
if (!encodedPath) return null;
|
|
105
|
+
try {
|
|
106
|
+
return { path: decodeURIComponent(encodedPath) };
|
|
107
|
+
} catch {
|
|
108
|
+
return { error: 'malformed', path: encodedPath };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function outsideDirectory(directory, target) {
|
|
113
|
+
const relative = path.relative(directory, target);
|
|
114
|
+
return relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function validateMarkdownReferences(directory, files) {
|
|
118
|
+
const root = path.resolve(directory);
|
|
119
|
+
const problems = [];
|
|
120
|
+
for (const file of files.filter(({ path: filePath }) => filePath.endsWith('.md'))) {
|
|
121
|
+
const location = path.join(root, file.path);
|
|
122
|
+
let tree;
|
|
123
|
+
try {
|
|
124
|
+
tree = fromMarkdown(await readFile(location, 'utf8'));
|
|
125
|
+
} catch (error) {
|
|
126
|
+
invalidSkill(`Agent Skill Markdown is invalid: ${location}.`, {
|
|
127
|
+
cause: error.message,
|
|
128
|
+
path: file.path,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
for (const destination of markdownDestinations(tree)) {
|
|
132
|
+
const local = localReferencePath(destination.url);
|
|
133
|
+
if (!local) continue;
|
|
134
|
+
const problem = {
|
|
135
|
+
path: file.path,
|
|
136
|
+
target: destination.url,
|
|
137
|
+
...(destination.line ? { line: destination.line } : {}),
|
|
138
|
+
};
|
|
139
|
+
if (local.error) {
|
|
140
|
+
problems.push({ ...problem, reason: local.error });
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const target = path.resolve(path.dirname(location), local.path);
|
|
144
|
+
if (outsideDirectory(root, target)) {
|
|
145
|
+
problems.push({ ...problem, reason: 'outside-skill' });
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
await lstat(target);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (!['ENOENT', 'ENOTDIR'].includes(error?.code)) throw error;
|
|
152
|
+
problems.push({ ...problem, reason: 'missing' });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (problems.length > 0) {
|
|
157
|
+
invalidSkill(
|
|
158
|
+
`Agent Skill contains ${problems.length} unresolved or non-portable local Markdown reference${problems.length === 1 ? '' : 's'}: ${directory}.`,
|
|
159
|
+
{ references: problems },
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
79
164
|
async function skillTree(directory, relative = '') {
|
|
80
165
|
const entries = await readdir(path.join(directory, relative), { withFileTypes: true });
|
|
81
166
|
const files = [];
|
|
@@ -146,6 +231,7 @@ async function describeSkill(directory, source) {
|
|
|
146
231
|
)
|
|
147
232
|
) invalidSkill(`Agent Skill metadata must map strings to strings: ${skillFile}.`);
|
|
148
233
|
const files = await skillTree(directory);
|
|
234
|
+
await validateMarkdownReferences(directory, files);
|
|
149
235
|
return {
|
|
150
236
|
name,
|
|
151
237
|
description,
|
|
@@ -395,7 +481,7 @@ export async function inspectProjectSkills({ projectRoot, stack } = {}) {
|
|
|
395
481
|
let installed;
|
|
396
482
|
try { installed = await describeSkill(target, `project:${expected.name}`); } catch (error) {
|
|
397
483
|
invalid = true;
|
|
398
|
-
diagnostics.push(
|
|
484
|
+
diagnostics.push(asDiagnostic(error));
|
|
399
485
|
continue;
|
|
400
486
|
}
|
|
401
487
|
if (!installed) {
|