incitaciones 0.10.1 → 0.10.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incitaciones",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "description": "Reusable prompts and skills for CLI LLM tools, with native pi package support.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,6 +27,7 @@
27
27
  "content/manifest.json",
28
28
  "pi-package/",
29
29
  "scripts/cli.mjs",
30
+ "scripts/lib/",
30
31
  "scripts/generate-pi-resources.mjs",
31
32
  "LICENSE",
32
33
  "README.md"
@@ -0,0 +1,132 @@
1
+ // Purpose: Shared compiler for router members and compat pointers.
2
+ // Responsibilities:
3
+ // - Resolve a manifest `pointer_for` value ("<router>/<member>") to its member file
4
+ // - Compile a skill directory into one self-contained markdown document
5
+ // (SKILL.md + all nested references/, recursively, with path headers)
6
+ // - Compile a pointer body: provenance banner + full member content
7
+ // - Validate that every pointer_for target exists
8
+ // Rationale: compiled pointers are distribution artifacts (site, pi-package,
9
+ // skills/ catalog, flat installs) and must never be hand-written prose. This
10
+ // module is the single implementation shared by build.sh, generate-pi-resources.mjs
11
+ // and generate-skills-dir.mjs so the content cannot drift between surfaces.
12
+ import fs from "node:fs";
13
+ import path from "node:path";
14
+
15
+ export function readText(filePath) {
16
+ return fs.readFileSync(filePath, "utf8");
17
+ }
18
+
19
+ // Strip a leading frontmatter block from a markdown document.
20
+ export function stripFrontmatter(raw) {
21
+ if (!raw.startsWith("---\n")) return raw;
22
+ const end = raw.indexOf("\n---\n", 3);
23
+ if (end === -1) return raw;
24
+ return raw.slice(end + 5).replace(/^\n+/, "");
25
+ }
26
+
27
+ // Resolve "<router>/<member>" to the member's entry file:
28
+ // prefers references/<member>/SKILL.md, falls back to references/<member>.md.
29
+ export function resolveMemberPath(repoRoot, pointerFor) {
30
+ const [router, member] = pointerFor.split("/");
31
+ const base = path.join(repoRoot, "content", "distilled", router, "references");
32
+ const multi = path.join(base, member, "SKILL.md");
33
+ if (fs.existsSync(multi)) return { file: multi, multiFile: true, router, member };
34
+ const flat = path.join(base, `${member}.md`);
35
+ if (fs.existsSync(flat)) return { file: flat, multiFile: false, router, member };
36
+ return null;
37
+ }
38
+
39
+ // Recursively collect markdown files under references/, sorted for determinism.
40
+ function collectReferences(refDir) {
41
+ const files = [];
42
+ if (!fs.existsSync(refDir)) return files;
43
+ const entries = fs.readdirSync(refDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
44
+ for (const entry of entries) {
45
+ const full = path.join(refDir, entry.name);
46
+ if (entry.isDirectory()) {
47
+ files.push(...collectReferences(full));
48
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
49
+ files.push(full);
50
+ }
51
+ }
52
+ return files;
53
+ }
54
+
55
+ // Compile a skill directory (containing SKILL.md) into one self-contained
56
+ // document: core instructions followed by every reference file, each headed
57
+ // by its path relative to the skill directory.
58
+ export function compileSkillDoc(repoRoot, skillRelDir) {
59
+ const skillDir = path.join(repoRoot, skillRelDir);
60
+ const skillFile = path.join(skillDir, "SKILL.md");
61
+ if (!fs.existsSync(skillFile)) {
62
+ throw new Error(`No SKILL.md in ${skillRelDir}`);
63
+ }
64
+ const parts = [];
65
+ parts.push(`#### Core Instructions (${path.join(skillRelDir, "SKILL.md")})\n\n`);
66
+ parts.push(readText(skillFile).trimEnd());
67
+ for (const ref of collectReferences(path.join(skillDir, "references"))) {
68
+ const rel = path.relative(skillDir, ref);
69
+ parts.push(`\n\n---\n\n#### Reference: ${rel}\n\n`);
70
+ parts.push(readText(ref).trimEnd());
71
+ }
72
+ return parts.join("");
73
+ }
74
+
75
+ // Compile the body of a pointer: provenance banner + full member content.
76
+ // The pointer is a permanent compiled alias — never a prose stub, never deleted.
77
+ export function compilePointerBody(repoRoot, pointerFor) {
78
+ const resolved = resolveMemberPath(repoRoot, pointerFor);
79
+ if (!resolved) {
80
+ throw new Error(`pointer_for target not found: ${pointerFor}`);
81
+ }
82
+ const memberRel = path.relative(repoRoot, resolved.file);
83
+ const banner = [
84
+ `> **Moved:** this entry is now the **${resolved.member}** mode of the **${resolved.router}** skill.`,
85
+ `> The full method is compiled below from \`${memberRel}\`.`,
86
+ `> Invoke via \`/skill:${resolved.member}\`, invoke \`/skill:${resolved.router}\` for the mode table,`,
87
+ `> or use this URL directly in a chat interface.`,
88
+ "",
89
+ "",
90
+ ].join("\n");
91
+
92
+ let content;
93
+ if (resolved.multiFile) {
94
+ content = compileSkillDoc(repoRoot, path.dirname(memberRel));
95
+ } else {
96
+ content = readText(resolved.file).trimEnd();
97
+ }
98
+ return banner + content;
99
+ }
100
+
101
+ // List member names of a multi-file router skill: subdirectories of references/
102
+ // that contain a SKILL.md (multi-file members), plus flat references/*.md.
103
+ export function listMembers(repoRoot, skillRelDir) {
104
+ const refDir = path.join(repoRoot, skillRelDir, "references");
105
+ const multi = [];
106
+ const flat = [];
107
+ if (!fs.existsSync(refDir)) return { multi, flat };
108
+ for (const entry of fs.readdirSync(refDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
109
+ if (entry.isDirectory() && fs.existsSync(path.join(refDir, entry.name, "SKILL.md"))) {
110
+ multi.push(entry.name);
111
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
112
+ flat.push(entry.name.replace(/\.md$/, ""));
113
+ }
114
+ }
115
+ return { multi, flat };
116
+ }
117
+
118
+ // Validate every pointer_for in the manifest resolves to an existing member.
119
+ // Returns an array of error strings (empty = valid).
120
+ export function validatePointers(repoRoot, manifest) {
121
+ const errors = [];
122
+ for (const prompt of manifest.prompts) {
123
+ if (!prompt.pointer_for) continue;
124
+ if (!prompt.disable_model_invocation) {
125
+ errors.push(`${prompt.name}: pointer_for set but disable_model_invocation is not true`);
126
+ }
127
+ if (!resolveMemberPath(repoRoot, prompt.pointer_for)) {
128
+ errors.push(`${prompt.name}: pointer_for target not found: ${prompt.pointer_for}`);
129
+ }
130
+ }
131
+ return errors;
132
+ }