incitaciones 0.10.1 → 0.10.3

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.
@@ -49,17 +49,37 @@ Uses `$JOURNAL_PATH` (defaults to `~/dev/status`) for the daily log journal, and
49
49
  grep -i "<slug>" "$JOURNAL"/inbox.md
50
50
  ```
51
51
 
52
- 7. **Check `~/.whisper/` for accumulated knowledge.**
53
- If `~/.whisper/` exists, load context:
52
+ 7. **Check for accumulated whisper knowledge.**
53
+
54
+ **If `turu` is available** (`turu key --json` succeeds), delegate every
55
+ mechanical step to it — never hand-roll repo-key or path derivation:
56
+ ```bash
57
+ turu key --json # canonical repo key, branch slug, worktree slot
58
+ turu recall repo # repo-scope knowledge (env facts)
59
+ turu recall branch # branch-scope knowledge (notes)
60
+ turu recall worktree # worktree-slot knowledge (only when step 8 uses one)
61
+ ```
62
+ - `turu recall` output already routes to the right store (global, repo,
63
+ or repo-local `.whisper/`), so never `ls`/`find` `~/.whisper/` to guess
64
+ paths — canonical keys don't match raw remote URLs.
65
+ - `turu recall` exits non-zero with `nothing to recall in this scope`
66
+ when a scope is empty — treat that as "no knowledge here", not a
67
+ failure; move on without improvising alternate lookup paths.
68
+
69
+ **Fallback (turu absent):** if `~/.whisper/` exists, load context with:
54
70
  ```bash
55
71
  repo_url=$(git remote get-url origin 2>/dev/null | sed 's|https://||;s|git@||;s|\.git$||')
56
72
  branch_slug=$(git rev-parse --abbrev-ref HEAD | sed 's|/|--|g')
57
73
  whisper_dir=~/.whisper/repos/"${repo_url}"/branches/"${branch_slug}"
58
74
  ```
59
- - Read `context.md` if it exists (extract beads-epic, status)
60
- - Read `plan.md` if it has real content
61
75
  - Read `notes.md` if it has real content
62
76
  - Read `env.md` at repo level for infra facts
77
+ - If the repo has a committed repo-local `.whisper/` directory, prefer
78
+ that over the global path. Its layout mirrors the global one relative
79
+ to the repo root:
80
+ `.whisper/repos/<org-path>/<repo>/branches/<branch-slug>/notes.md`
81
+ (e.g. `.whisper/repos/ak/PhormaSci/talleres/branches/main/notes.md`).
82
+
63
83
  - If beads is available, fetch open issues:
64
84
  ```bash
65
85
  bd list --label branch:<branch> 2>/dev/null
@@ -93,9 +113,9 @@ Uses `$JOURNAL_PATH` (defaults to `~/dev/status`) for the daily log journal, and
93
113
  **Related inbox items** *(if any)*
94
114
  - items that mention this project
95
115
 
96
- **Whisper context** *(if ~/.whisper/ exists)*
97
- - Plan: summary of current plan
98
- - Notes: key findings
116
+ **Whisper context** *(if turu recall or the fallback found content)*
117
+ - Env: repo-scope infra facts (`turu recall repo` / `env.md`)
118
+ - Notes: branch-scope findings (`turu recall branch` / `notes.md`)
99
119
  - Beads issues: <n> open, <n> in_progress
100
120
 
101
121
  **⚠ Active in flight** *(omit if none)*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incitaciones",
3
- "version": "0.10.1",
3
+ "version": "0.10.3",
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"
@@ -63,17 +63,37 @@ Uses `$JOURNAL_PATH` (defaults to `~/dev/status`) for the daily log journal, and
63
63
  grep -i "<slug>" "$JOURNAL"/inbox.md
64
64
  ```
65
65
 
66
- 7. **Check `~/.whisper/` for accumulated knowledge.**
67
- If `~/.whisper/` exists, load context:
66
+ 7. **Check for accumulated whisper knowledge.**
67
+
68
+ **If `turu` is available** (`turu key --json` succeeds), delegate every
69
+ mechanical step to it — never hand-roll repo-key or path derivation:
70
+ ```bash
71
+ turu key --json # canonical repo key, branch slug, worktree slot
72
+ turu recall repo # repo-scope knowledge (env facts)
73
+ turu recall branch # branch-scope knowledge (notes)
74
+ turu recall worktree # worktree-slot knowledge (only when step 8 uses one)
75
+ ```
76
+ - `turu recall` output already routes to the right store (global, repo,
77
+ or repo-local `.whisper/`), so never `ls`/`find` `~/.whisper/` to guess
78
+ paths — canonical keys don't match raw remote URLs.
79
+ - `turu recall` exits non-zero with `nothing to recall in this scope`
80
+ when a scope is empty — treat that as "no knowledge here", not a
81
+ failure; move on without improvising alternate lookup paths.
82
+
83
+ **Fallback (turu absent):** if `~/.whisper/` exists, load context with:
68
84
  ```bash
69
85
  repo_url=$(git remote get-url origin 2>/dev/null | sed 's|https://||;s|git@||;s|\.git$||')
70
86
  branch_slug=$(git rev-parse --abbrev-ref HEAD | sed 's|/|--|g')
71
87
  whisper_dir=~/.whisper/repos/"${repo_url}"/branches/"${branch_slug}"
72
88
  ```
73
- - Read `context.md` if it exists (extract beads-epic, status)
74
- - Read `plan.md` if it has real content
75
89
  - Read `notes.md` if it has real content
76
90
  - Read `env.md` at repo level for infra facts
91
+ - If the repo has a committed repo-local `.whisper/` directory, prefer
92
+ that over the global path. Its layout mirrors the global one relative
93
+ to the repo root:
94
+ `.whisper/repos/<org-path>/<repo>/branches/<branch-slug>/notes.md`
95
+ (e.g. `.whisper/repos/ak/PhormaSci/talleres/branches/main/notes.md`).
96
+
77
97
  - If beads is available, fetch open issues:
78
98
  ```bash
79
99
  bd list --label branch:<branch> 2>/dev/null
@@ -107,9 +127,9 @@ Uses `$JOURNAL_PATH` (defaults to `~/dev/status`) for the daily log journal, and
107
127
  **Related inbox items** *(if any)*
108
128
  - items that mention this project
109
129
 
110
- **Whisper context** *(if ~/.whisper/ exists)*
111
- - Plan: summary of current plan
112
- - Notes: key findings
130
+ **Whisper context** *(if turu recall or the fallback found content)*
131
+ - Env: repo-scope infra facts (`turu recall repo` / `env.md`)
132
+ - Notes: branch-scope findings (`turu recall branch` / `notes.md`)
113
133
  - Beads issues: <n> open, <n> in_progress
114
134
 
115
135
  **⚠ Active in flight** *(omit if none)*
@@ -49,17 +49,37 @@ Uses `$JOURNAL_PATH` (defaults to `~/dev/status`) for the daily log journal, and
49
49
  grep -i "<slug>" "$JOURNAL"/inbox.md
50
50
  ```
51
51
 
52
- 7. **Check `~/.whisper/` for accumulated knowledge.**
53
- If `~/.whisper/` exists, load context:
52
+ 7. **Check for accumulated whisper knowledge.**
53
+
54
+ **If `turu` is available** (`turu key --json` succeeds), delegate every
55
+ mechanical step to it — never hand-roll repo-key or path derivation:
56
+ ```bash
57
+ turu key --json # canonical repo key, branch slug, worktree slot
58
+ turu recall repo # repo-scope knowledge (env facts)
59
+ turu recall branch # branch-scope knowledge (notes)
60
+ turu recall worktree # worktree-slot knowledge (only when step 8 uses one)
61
+ ```
62
+ - `turu recall` output already routes to the right store (global, repo,
63
+ or repo-local `.whisper/`), so never `ls`/`find` `~/.whisper/` to guess
64
+ paths — canonical keys don't match raw remote URLs.
65
+ - `turu recall` exits non-zero with `nothing to recall in this scope`
66
+ when a scope is empty — treat that as "no knowledge here", not a
67
+ failure; move on without improvising alternate lookup paths.
68
+
69
+ **Fallback (turu absent):** if `~/.whisper/` exists, load context with:
54
70
  ```bash
55
71
  repo_url=$(git remote get-url origin 2>/dev/null | sed 's|https://||;s|git@||;s|\.git$||')
56
72
  branch_slug=$(git rev-parse --abbrev-ref HEAD | sed 's|/|--|g')
57
73
  whisper_dir=~/.whisper/repos/"${repo_url}"/branches/"${branch_slug}"
58
74
  ```
59
- - Read `context.md` if it exists (extract beads-epic, status)
60
- - Read `plan.md` if it has real content
61
75
  - Read `notes.md` if it has real content
62
76
  - Read `env.md` at repo level for infra facts
77
+ - If the repo has a committed repo-local `.whisper/` directory, prefer
78
+ that over the global path. Its layout mirrors the global one relative
79
+ to the repo root:
80
+ `.whisper/repos/<org-path>/<repo>/branches/<branch-slug>/notes.md`
81
+ (e.g. `.whisper/repos/ak/PhormaSci/talleres/branches/main/notes.md`).
82
+
63
83
  - If beads is available, fetch open issues:
64
84
  ```bash
65
85
  bd list --label branch:<branch> 2>/dev/null
@@ -93,9 +113,9 @@ Uses `$JOURNAL_PATH` (defaults to `~/dev/status`) for the daily log journal, and
93
113
  **Related inbox items** *(if any)*
94
114
  - items that mention this project
95
115
 
96
- **Whisper context** *(if ~/.whisper/ exists)*
97
- - Plan: summary of current plan
98
- - Notes: key findings
116
+ **Whisper context** *(if turu recall or the fallback found content)*
117
+ - Env: repo-scope infra facts (`turu recall repo` / `env.md`)
118
+ - Notes: branch-scope findings (`turu recall branch` / `notes.md`)
99
119
  - Beads issues: <n> open, <n> in_progress
100
120
 
101
121
  **⚠ Active in flight** *(omit if none)*
@@ -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
+ }