juno-code 1.0.46 → 1.0.49

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.
Files changed (54) hide show
  1. package/README.md +44 -8
  2. package/dist/bin/cli.d.mts +17 -0
  3. package/dist/bin/cli.d.ts +17 -0
  4. package/dist/bin/cli.js +5601 -17505
  5. package/dist/bin/cli.js.map +1 -1
  6. package/dist/bin/cli.mjs +5640 -17542
  7. package/dist/bin/cli.mjs.map +1 -1
  8. package/dist/bin/feedback-collector.d.mts +2 -0
  9. package/dist/bin/feedback-collector.d.ts +2 -0
  10. package/dist/bin/feedback-collector.js.map +1 -1
  11. package/dist/bin/feedback-collector.mjs.map +1 -1
  12. package/dist/index.d.mts +2107 -0
  13. package/dist/index.d.ts +2107 -0
  14. package/dist/index.js +3760 -14728
  15. package/dist/index.js.map +1 -1
  16. package/dist/index.mjs +3761 -14536
  17. package/dist/index.mjs.map +1 -1
  18. package/dist/templates/extensions/pi/juno-skill-preprocessor.ts +239 -0
  19. package/dist/templates/scripts/__pycache__/github.cpython-313.pyc +0 -0
  20. package/dist/templates/scripts/__pycache__/parallel_runner.cpython-313.pyc +0 -0
  21. package/dist/templates/scripts/__pycache__/slack_respond.cpython-313.pyc +0 -0
  22. package/dist/templates/scripts/kanban.sh +18 -4
  23. package/dist/templates/scripts/parallel_runner.sh +2242 -0
  24. package/dist/templates/services/README.md +61 -1
  25. package/dist/templates/services/__pycache__/claude.cpython-313.pyc +0 -0
  26. package/dist/templates/services/__pycache__/codex.cpython-313.pyc +0 -0
  27. package/dist/templates/services/__pycache__/pi.cpython-313.pyc +0 -0
  28. package/dist/templates/services/claude.py +132 -33
  29. package/dist/templates/services/codex.py +179 -66
  30. package/dist/templates/services/gemini.py +117 -27
  31. package/dist/templates/services/pi.py +1753 -0
  32. package/dist/templates/skills/claude/plan-kanban-tasks/SKILL.md +14 -7
  33. package/dist/templates/skills/claude/ralph-loop/SKILL.md +18 -22
  34. package/dist/templates/skills/claude/ralph-loop/references/first_check.md +15 -14
  35. package/dist/templates/skills/claude/ralph-loop/references/implement.md +17 -17
  36. package/dist/templates/skills/claude/ralph-loop/scripts/kanban.sh +18 -4
  37. package/dist/templates/skills/claude/understand-project/SKILL.md +14 -7
  38. package/dist/templates/skills/codex/ralph-loop/SKILL.md +18 -22
  39. package/dist/templates/skills/codex/ralph-loop/references/first_check.md +15 -14
  40. package/dist/templates/skills/codex/ralph-loop/references/implement.md +17 -17
  41. package/dist/templates/skills/codex/ralph-loop/scripts/kanban.sh +18 -4
  42. package/dist/templates/skills/pi/.gitkeep +0 -0
  43. package/dist/templates/skills/pi/plan-kanban-tasks/SKILL.md +32 -0
  44. package/dist/templates/skills/pi/ralph-loop/SKILL.md +39 -0
  45. package/dist/templates/skills/pi/ralph-loop/references/first_check.md +21 -0
  46. package/dist/templates/skills/pi/ralph-loop/references/implement.md +99 -0
  47. package/dist/templates/skills/pi/understand-project/SKILL.md +46 -0
  48. package/package.json +20 -42
  49. package/dist/templates/scripts/__pycache__/attachment_downloader.cpython-38.pyc +0 -0
  50. package/dist/templates/scripts/__pycache__/github.cpython-38.pyc +0 -0
  51. package/dist/templates/scripts/__pycache__/slack_fetch.cpython-38.pyc +0 -0
  52. package/dist/templates/scripts/__pycache__/slack_state.cpython-38.pyc +0 -0
  53. package/dist/templates/services/__pycache__/claude.cpython-38.pyc +0 -0
  54. package/dist/templates/services/__pycache__/codex.cpython-38.pyc +0 -0
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Juno Skill Preprocessor — Pi Extension
3
+ *
4
+ * Adds variable substitution ($1, $2, $ARGUMENTS, $@, ${@:N}, ${@:N:L})
5
+ * and shell directive execution (!`command`) to Pi skill invocations.
6
+ *
7
+ * This extension intercepts /skill: commands via the "input" event,
8
+ * BEFORE Pi's internal _expandSkillCommand() runs. It reads the skill
9
+ * file, processes variables and shell directives, wraps the result in
10
+ * <skill> tags, and returns the fully expanded text.
11
+ *
12
+ * Shell directives only execute when the skill's frontmatter contains:
13
+ * enable-shell-directives: true
14
+ *
15
+ * Variable substitution always runs when arguments are provided.
16
+ *
17
+ * Shipped via juno-code's SkillInstaller to .pi/extensions/.
18
+ */
19
+ import type { ExtensionAPI, InputEvent } from "@mariozechner/pi-coding-agent";
20
+ import { execSync } from "child_process";
21
+ import { existsSync, readFileSync } from "fs";
22
+ import { dirname, join } from "path";
23
+
24
+ const SHELL_DIRECTIVE_REGEX = /!`([^`]+)`/g;
25
+ const DEFAULT_SHELL_TIMEOUT = 5000;
26
+
27
+ /** Directories to search for skill files, relative to project root. */
28
+ const SKILL_DIRS = [".pi/skills", ".claude/skills"];
29
+
30
+ /**
31
+ * Find a skill's SKILL.md file by name, searching known skill directories.
32
+ * Checks both {dir}/{name}/SKILL.md and {dir}/{name}.md patterns.
33
+ */
34
+ function findSkillFile(skillName: string, cwd: string): string | null {
35
+ for (const dir of SKILL_DIRS) {
36
+ const candidates = [join(cwd, dir, skillName, "SKILL.md"), join(cwd, dir, `${skillName}.md`)];
37
+ for (const candidate of candidates) {
38
+ if (existsSync(candidate)) return candidate;
39
+ }
40
+ }
41
+ return null;
42
+ }
43
+
44
+ /**
45
+ * Parse YAML-like frontmatter from a skill file.
46
+ * Returns frontmatter key-value pairs and the body text after the frontmatter block.
47
+ */
48
+ function parseFrontmatter(content: string): { frontmatter: Record<string, string | boolean>; body: string } {
49
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/);
50
+ if (!match) return { frontmatter: {}, body: content };
51
+
52
+ const yaml = match[1];
53
+ const body = match[2] ?? '';
54
+ const frontmatter: Record<string, string | boolean> = {};
55
+
56
+ for (const rawLine of yaml!.split("\n")) {
57
+ const colonIndex = rawLine.indexOf(":");
58
+ if (colonIndex === -1) continue;
59
+ const key = rawLine.slice(0, colonIndex).trim();
60
+ const value = rawLine.slice(colonIndex + 1).trim();
61
+ if (value === "true") {
62
+ frontmatter[key] = true;
63
+ } else if (value === "false") {
64
+ frontmatter[key] = false;
65
+ } else {
66
+ frontmatter[key] = value;
67
+ }
68
+ }
69
+
70
+ return { frontmatter, body };
71
+ }
72
+
73
+ /**
74
+ * Parse command arguments respecting single and double quotes.
75
+ * Handles escape characters with backslash.
76
+ *
77
+ * Examples:
78
+ * 'hello world' → ["hello", "world"]
79
+ * '"hello world"' → ["hello world"]
80
+ * "'hello' \"world\"" → ["hello", "world"]
81
+ */
82
+ function parseCommandArgs(input: string): string[] {
83
+ if (!input.trim()) return [];
84
+
85
+ const args: string[] = [];
86
+ let current = "";
87
+ let inSingle = false;
88
+ let inDouble = false;
89
+ let escape = false;
90
+
91
+ for (const char of input) {
92
+ if (escape) {
93
+ current += char;
94
+ escape = false;
95
+ continue;
96
+ }
97
+ if (char === "\\") {
98
+ escape = true;
99
+ continue;
100
+ }
101
+ if (char === '"' && !inSingle) {
102
+ inDouble = !inDouble;
103
+ continue;
104
+ }
105
+ if (char === "'" && !inDouble) {
106
+ inSingle = !inSingle;
107
+ continue;
108
+ }
109
+ if (char === " " && !inSingle && !inDouble) {
110
+ if (current) {
111
+ args.push(current);
112
+ current = "";
113
+ }
114
+ continue;
115
+ }
116
+ current += char;
117
+ }
118
+ if (current) args.push(current);
119
+ return args;
120
+ }
121
+
122
+ /**
123
+ * Substitute argument placeholders in content.
124
+ *
125
+ * Supports (1-indexed, aligned with bash and Pi's prompt-templates):
126
+ * $1, $2, ... — positional arguments
127
+ * $@ — all arguments joined by space
128
+ * $ARGUMENTS — all arguments joined by space (alias)
129
+ * ${@:N} — arguments from Nth position onwards
130
+ * ${@:N:L} — L arguments starting from position N
131
+ */
132
+ function substituteArgs(content: string, args: string[]): string {
133
+ let result = content;
134
+
135
+ // Replace $1, $2, etc. FIRST (before wildcards to prevent re-substitution)
136
+ result = result.replace(/\$(\d+)/g, (_, num) => {
137
+ const index = parseInt(num, 10) - 1;
138
+ return args[index] ?? "";
139
+ });
140
+
141
+ // Replace ${@:start} or ${@:start:length} (bash-style, 1-indexed)
142
+ result = result.replace(/\$\{@:(\d+)(?::(\d+))?\}/g, (_, startStr, lengthStr) => {
143
+ let start = parseInt(startStr, 10) - 1;
144
+ if (start < 0) start = 0;
145
+ if (lengthStr) {
146
+ const length = parseInt(lengthStr, 10);
147
+ return args.slice(start, start + length).join(" ");
148
+ }
149
+ return args.slice(start).join(" ");
150
+ });
151
+
152
+ const allArgs = args.join(" ");
153
+ result = result.replace(/\$ARGUMENTS/g, allArgs);
154
+ result = result.replace(/\$@/g, allArgs);
155
+
156
+ return result;
157
+ }
158
+
159
+ /**
160
+ * Process shell directives (!`command`) by executing them and inlining stdout.
161
+ * On error: replaces with [Error executing: command].
162
+ * Timeout: DEFAULT_SHELL_TIMEOUT ms (configurable).
163
+ */
164
+ function processShellDirectives(content: string, cwd: string, timeout: number = DEFAULT_SHELL_TIMEOUT): string {
165
+ return content.replace(SHELL_DIRECTIVE_REGEX, (_, command: string) => {
166
+ try {
167
+ return execSync(command, {
168
+ cwd,
169
+ timeout,
170
+ encoding: "utf-8",
171
+ stdio: ["pipe", "pipe", "pipe"],
172
+ }).trim();
173
+ } catch {
174
+ return `[Error executing: ${command}]`;
175
+ }
176
+ });
177
+ }
178
+
179
+ /**
180
+ * Juno Skill Preprocessor Extension
181
+ *
182
+ * Intercepts /skill: commands via the "input" event (before Pi's internal
183
+ * _expandSkillCommand runs), applies variable substitution and shell
184
+ * directive processing, then returns the fully expanded skill block.
185
+ */
186
+ export default function junoSkillPreprocessor(pi: ExtensionAPI) {
187
+ pi.on("input", (event: InputEvent) => {
188
+ const text = typeof event.text === "string" ? event.text : "";
189
+ if (!text.startsWith("/skill:")) return { action: "continue" };
190
+
191
+ const cwd = process.cwd();
192
+
193
+ // Parse skill name and arguments from the command
194
+ const spaceIndex = text.indexOf(" ");
195
+ const skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);
196
+ const argsString = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
197
+ const args = parseCommandArgs(argsString);
198
+
199
+ // Find the skill file on disk
200
+ const skillPath = findSkillFile(skillName, cwd);
201
+ if (!skillPath) return { action: "continue" }; // Unknown skill — let Pi handle it
202
+
203
+ try {
204
+ const content = readFileSync(skillPath, "utf-8");
205
+ const { frontmatter, body } = parseFrontmatter(content);
206
+ let processedBody = body.trim();
207
+
208
+ // Substitute variable placeholders with provided arguments
209
+ if (args.length > 0) {
210
+ processedBody = substituteArgs(processedBody, args);
211
+ }
212
+
213
+ // Execute shell directives (only when explicitly opted in via frontmatter)
214
+ if (frontmatter["enable-shell-directives"] === true) {
215
+ processedBody = processShellDirectives(processedBody, cwd);
216
+ }
217
+
218
+ // Build the <skill> block (matches Pi's _expandSkillCommand format)
219
+ const baseDir = dirname(skillPath);
220
+ const skillBlock = [
221
+ `<skill name="${skillName}" location="${skillPath}">`,
222
+ `References are relative to ${baseDir}.`,
223
+ "",
224
+ processedBody,
225
+ "</skill>",
226
+ ].join("\n");
227
+
228
+ // Return transformed text — Pi's _expandSkillCommand will see this
229
+ // doesn't start with /skill: and pass it through unchanged.
230
+ return { action: "transform", text: skillBlock };
231
+ } catch {
232
+ // On error, let Pi's native expansion handle it
233
+ return { action: "continue" };
234
+ }
235
+ });
236
+ }
237
+
238
+ // Export internals for testing
239
+ export { findSkillFile, parseCommandArgs, parseFrontmatter, processShellDirectives, substituteArgs };
@@ -170,8 +170,22 @@ ensure_python_environment() {
170
170
  # Get the directory where this script is located
171
171
  SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
172
172
 
173
- # Navigate to project root (parent of scripts directory)
174
- PROJECT_ROOT="$( cd "$SCRIPT_DIR/../.." && pwd )"
173
+ # Auto-discover project root by walking up the directory tree looking for .juno_task/
174
+ # This makes kanban.sh work from any installation depth (e.g. .juno_task/scripts/,
175
+ # .claude/skills/ralph-loop/scripts/, etc.) without a hardcoded relative path.
176
+ PROJECT_ROOT=""
177
+ _dir="$SCRIPT_DIR"
178
+ while [[ "$_dir" != "/" ]]; do
179
+ if [[ -d "$_dir/.juno_task" ]]; then
180
+ PROJECT_ROOT="$_dir"
181
+ break
182
+ fi
183
+ _dir="$( cd "$_dir/.." && pwd )"
184
+ done
185
+ if [[ -z "$PROJECT_ROOT" ]]; then
186
+ echo "ERROR: Could not find project root (no .juno_task/ directory found above $SCRIPT_DIR)" >&2
187
+ exit 1
188
+ fi
175
189
 
176
190
  # Change to project root
177
191
  cd "$PROJECT_ROOT"
@@ -192,7 +206,7 @@ normalize_arguments() {
192
206
  local found_command=false
193
207
 
194
208
  # Known subcommands
195
- local commands="create search get show update archive mark list merge"
209
+ local commands="create search get show update archive mark list merge ready deps order"
196
210
 
197
211
  while [[ $# -gt 0 ]]; do
198
212
  case $1 in
@@ -207,7 +221,7 @@ normalize_arguments() {
207
221
  fi
208
222
  ;;
209
223
  # Global flags that don't take a value
210
- -p|--pretty|--raw|-v|--verbose|-h|--help|--version)
224
+ -p|--pretty|--raw|-v|--verbose|--version)
211
225
  NORMALIZED_GLOBAL_FLAGS+=("$1")
212
226
  shift
213
227
  ;;