@riflo/ryte 1.2.0 → 1.2.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": "@riflo/ryte",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "AI Git Workflow Assistant - Generate semantic commits and PRs",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/ai.js CHANGED
@@ -45,16 +45,62 @@ export async function generateAIResponse(messages, overrideConfig = null) {
45
45
  }
46
46
 
47
47
  if (!response.ok) {
48
- const error = await response.json();
49
- throw new Error(error.error?.message || "API request failed");
48
+ let errorData;
49
+ try {
50
+ errorData = await response.json();
51
+ } catch (e) {
52
+ throw new Error(`HTTP Error ${response.status}: ${response.statusText}`);
53
+ }
54
+ throw new Error(errorData.error?.message || `API request failed with status ${response.status}`);
50
55
  }
51
56
 
52
57
  const data = await response.json();
53
- return data.choices[0].message.content.trim();
58
+ if (!data.choices || !data.choices[0] || !data.choices[0].message) {
59
+ throw new Error("Invalid response format from AI provider.");
60
+ }
61
+
62
+ const content = data.choices[0].message.content;
63
+ if (!content) {
64
+ throw new Error("AI provider returned an empty response.");
65
+ }
66
+
67
+ const trimmed = content.trim();
68
+
69
+ // Deterministic Internal Contract Prep
70
+ // Currently returns { text: string, structured: Object }
71
+ return parseAICommitMessage(trimmed);
54
72
  } catch (e) {
73
+ // Check for network errors (Offline)
74
+ if (e.code === 'ENOTFOUND' || e.code === 'EAI_AGAIN') {
75
+ throw new Error("Network unreachable. Please check your internet connection.");
76
+ }
77
+
55
78
  if (attempt === MAX_RETRIES) {
56
- throw e; // Let the caller handle the final failure
79
+ throw e;
57
80
  }
58
81
  }
59
82
  }
60
83
  }
84
+
85
+ /**
86
+ * Parses a conventional commit message into a structured object.
87
+ * This is the 'Deterministic Contract' for the engine.
88
+ */
89
+ function parseAICommitMessage(text) {
90
+ const lines = text.split("\n");
91
+ const header = lines[0].trim();
92
+
93
+ // Simple regex for conventional commit: type(scope): subject
94
+ const match = header.match(/^(\w+)(?:\(([^)]+)\))?:\s*(.+)$/);
95
+
96
+ return {
97
+ text: text, // The full raw text for legacy compatibility
98
+ structured: {
99
+ header: header,
100
+ type: match ? match[1] : "other",
101
+ scope: match ? match[2] : null,
102
+ subject: match ? match[3] : header,
103
+ body: lines.slice(1).filter(l => l.trim().length > 0).join("\n").trim() || null
104
+ }
105
+ };
106
+ }
package/src/config.js CHANGED
@@ -18,8 +18,13 @@ export function getConfig() {
18
18
  }
19
19
  try {
20
20
  const data = fs.readFileSync(CONFIG_FILE, "utf-8");
21
- return { ...DEFAULT_CONFIG, ...JSON.parse(data) };
21
+ const parsed = JSON.parse(data);
22
+ // Ensure some basic structure integrity
23
+ if (typeof parsed !== "object" || parsed === null) throw new Error("Invalid config format");
24
+ return { ...DEFAULT_CONFIG, ...parsed };
22
25
  } catch (e) {
26
+ console.warn(`\n\x1b[33m⚠ Warning: Configuration file is corrupted. Re-initializing...\x1b[0m`);
27
+ // If corrupted, return null so setupFlow triggers or we can fallback
23
28
  return null;
24
29
  }
25
30
  }
package/src/index.js CHANGED
@@ -14,7 +14,7 @@ const rl = readline.createInterface({
14
14
  output: process.stdout
15
15
  });
16
16
 
17
- const VERSION = "1.2.0";
17
+ const VERSION = "1.2.2";
18
18
 
19
19
  async function question(query) {
20
20
  return new Promise(resolve => rl.question(query, resolve));
@@ -113,25 +113,34 @@ async function interactiveLoop(initialResult, type) {
113
113
  async function handleCommit() {
114
114
  const diff = getStagedDiff();
115
115
  if (!diff) {
116
- console.log("No staged changes found. Use `git add` to stage files.");
116
+ console.log("\n\x1b[33m⚠ No staged changes found.\x1b[0m");
117
+ console.log("Use \x1b[32m`git add` \x1b[0m to stage files before committing.");
117
118
  process.exit(0);
118
119
  }
119
120
 
120
121
  const branch = getCurrentBranch();
121
122
 
122
123
  while (true) {
123
- console.log("\nAnalyzing staged diff...");
124
- const result = await generateAIResponse([
125
- { role: "system", content: COMMIT_SYSTEM_PROMPT },
126
- { role: "user", content: `Branch: ${branch}\n\nDiff:\n${diff}` }
127
- ]);
128
-
129
- const finalAction = await interactiveLoop(result, "Commit Message");
130
-
131
- if (finalAction !== "REGENERATE") {
132
- applyCommit(finalAction);
133
- console.log("\n\x1b[32m✔ Commit applied successfully!\x1b[0m");
134
- break;
124
+ try {
125
+ console.log("\nAnalyzing staged diff...");
126
+ const result = await generateAIResponse([
127
+ { role: "system", content: COMMIT_SYSTEM_PROMPT },
128
+ { role: "user", content: `Branch: ${branch}\n\nDiff:\n${diff}` }
129
+ ]);
130
+
131
+ const finalAction = await interactiveLoop(result.text, "Commit Message");
132
+
133
+ if (finalAction !== "REGENERATE") {
134
+ applyCommit(finalAction);
135
+ console.log("\n\x1b[32m✔ Commit applied successfully!\x1b[0m");
136
+ break;
137
+ }
138
+ } catch (e) {
139
+ console.error(`\n\x1b[31m✖ AI Generation failed:\x1b[0m ${e.message}`);
140
+ const retry = await question("\nWould you like to try again? [y/N]: ");
141
+ if (retry.toLowerCase() !== "y") {
142
+ process.exit(1);
143
+ }
135
144
  }
136
145
  }
137
146
  }
@@ -139,25 +148,34 @@ async function handleCommit() {
139
148
  async function handlePR() {
140
149
  const commits = getBranchCommits();
141
150
  if (!commits) {
142
- console.log("No recent commits found distinct from main branch.");
151
+ console.log("\n\x1b[33m⚠ No recent commits found.\x1b[0m");
152
+ console.log("Ensure you have committed changes that are distinct from your main branch.");
143
153
  process.exit(0);
144
154
  }
145
155
  const branch = getCurrentBranch();
146
156
 
147
157
  while (true) {
148
- console.log("\nAnalyzing recent commits...");
149
- const result = await generateAIResponse([
150
- { role: "system", content: PR_SYSTEM_PROMPT },
151
- { role: "user", content: `Branch: ${branch}\n\nCommits:\n${commits}` }
152
- ]);
153
-
154
- const finalAction = await interactiveLoop(result, "PR Markdown Description");
155
-
156
- if (finalAction !== "REGENERATE") {
157
- console.log("\n\x1b[32mFinal PR Content:\x1b[0m\n");
158
- console.log(finalAction);
159
- console.log("\n(You can copy-paste the above into your pull request or we can pipe it to the clipboard later)");
160
- break;
158
+ try {
159
+ console.log("\nAnalyzing recent commits...");
160
+ const result = await generateAIResponse([
161
+ { role: "system", content: PR_SYSTEM_PROMPT },
162
+ { role: "user", content: `Branch: ${branch}\n\nCommits:\n${commits}` }
163
+ ]);
164
+
165
+ const finalAction = await interactiveLoop(result.text, "PR Markdown Description");
166
+
167
+ if (finalAction !== "REGENERATE") {
168
+ console.log("\n\x1b[32mFinal PR Content:\x1b[0m\n");
169
+ console.log(finalAction);
170
+ console.log("\n(You can copy-paste the above into your pull request)");
171
+ break;
172
+ }
173
+ } catch (e) {
174
+ console.error(`\n\x1b[31m✖ AI Generation failed:\x1b[0m ${e.message}`);
175
+ const retry = await question("\nWould you like to try again? [y/N]: ");
176
+ if (retry.toLowerCase() !== "y") {
177
+ process.exit(1);
178
+ }
161
179
  }
162
180
  }
163
181
  }
@@ -166,18 +184,19 @@ async function main() {
166
184
  const args = process.argv.slice(2);
167
185
  const cmd = args[0]?.toLowerCase();
168
186
 
169
- if (!hasValidConfig()) {
170
- await setupFlow();
171
- }
187
+ try {
188
+ if (!hasValidConfig()) {
189
+ await setupFlow();
190
+ }
172
191
 
173
- if (cmd === "c" || cmd === "commit") {
174
- await handleCommit();
175
- } else if (cmd === "pr") {
176
- await handlePR();
177
- } else if (cmd === "config") {
178
- await handleConfig();
179
- } else {
180
- console.log(`
192
+ if (cmd === "c" || cmd === "commit") {
193
+ await handleCommit();
194
+ } else if (cmd === "pr") {
195
+ await handlePR();
196
+ } else if (cmd === "config") {
197
+ await handleConfig();
198
+ } else {
199
+ console.log(`
181
200
  \x1b[1;38;5;39m██████╗ \x1b[1;38;5;63m██╗ ██╗\x1b[1;38;5;129m████████╗\x1b[1;38;5;161m███████╗\x1b[0m
182
201
  \x1b[1;38;5;39m██╔══██╗\x1b[1;38;5;63m╚██╗ ██╔╝\x1b[1;38;5;129m╚══██╔══╝\x1b[1;38;5;161m██╔════╝\x1b[0m
183
202
  \x1b[1;38;5;39m██████╔╝\x1b[1;38;5;63m ╚████╔╝ \x1b[1;38;5;129m ██║ \x1b[1;38;5;161m█████╗ \x1b[0m
@@ -196,7 +215,11 @@ async function main() {
196
215
  \x1b[33mONBOARDING:\x1b[0m
197
216
  No .env required. Run \x1b[32mryte config\x1b[0m or just run \x1b[32mryte c\x1b[0m to
198
217
  start the interactive setup.
199
- `);
218
+ `);
219
+ }
220
+ } catch (e) {
221
+ console.error(`\n\x1b[31m✖ Unexpected Error:\x1b[0m ${e.message}`);
222
+ process.exit(1);
200
223
  }
201
224
 
202
225
  rl.close();
package/src/prompt.js CHANGED
@@ -2,25 +2,33 @@ export const COMMIT_SYSTEM_PROMPT = `
2
2
  You are an expert developer assistant specialized in Git workflows and the Conventional Commits specification.
3
3
  Your goal is to generate a concise, meaningful, and technically accurate commit message based on provided git diffs.
4
4
 
5
+ PRIORITY:
6
+ 1. LOGIC OVER METADATA: If the diff contains both code changes (src/, lib/) and metadata changes (package.json version, README typos), the commit message MUST focus on the code logic.
7
+ 2. SUBSTANCE: Avoid subjects like "bump version" or "update files" if there is meaningful logic change. Focus on the "What" and "Why" of the code evolution.
8
+
5
9
  RULES:
6
- 1. OUTPUT ONLY THE COMMIT MESSAGE. No markdown, no "Here is your commit", no backticks.
10
+ 1. OUTPUT ONLY THE COMMIT MESSAGE. No markdown, no filler.
7
11
  2. Follow Conventional Commits: <type>(<scope>): <subject>
8
- 3. Use types: feat (new feature), fix (bug fix), docs (documentation), style (formatting), refactor (code cleanup), perf (performance), test (adding tests), chore (maintenance).
12
+ 3. Use types:
13
+ - feat: new capability or significant hardening/stabilization.
14
+ - fix: bug fixes.
15
+ - refactor: code restructuring without changing behavior.
16
+ - docs: documentation only.
17
+ - chore: maintenance, version bumps (only if NO logic changed).
9
18
  4. Subject line:
10
- - Use imperative mood ("add", not "adds" or "added").
11
- - Max 50 characters.
12
- - Do not end with a period.
13
- - Focus on the "why" or the core "what", not every trivial change.
14
- 5. Breaking Changes: If the diff shows breaking changes, use "!" after the type (e.g., "feat!: delete deprecated api").
15
- 6. Scope: If the diff is localized, infer a scope (e.g., "auth", "ui", "config").
16
- 7. Body (Optional): If the change is complex, add a brief body after 1 blank line to explain technical nuances.
17
- 8. Context: If a branch name or ticket is provided, incorporate it into the scope or footer if applicable.
18
- 9. Anti-Hallucination: Documentation files (like README.md) often contain example commit messages (e.g., "feat(auth): ..."). DO NOT assume these examples are the topic of the current change.
19
- 10. Logical Validation: Your suggested <scope> must be derived from actual modified logic in the diff, not from text inside code blocks, comments, or examples within a documentation file.
20
- 11. If only README.md or docs are changed, the type MUST be "docs" and the scope should relate to the documentation structure (e.g., "readme", "config", "intro"), NOT the example code inside it.
19
+ - Use imperative mood ("add", "implement", "harden").
20
+ - Max 50 characters. No period.
21
+ - Focus on the technical achievement.
22
+ 5. Breaking Changes: Use "!" if behavior changes significantly (e.g., "feat!: ...").
23
+ 6. Scope: Infer from affected module (e.g., "config", "ai", "core").
24
+ 7. Body: Use bullet points for complex multi-file changes to explain "Why" and nuances.
21
25
 
22
26
  Example:
23
- feat(ui): add loading state to checkout button
27
+ feat(core): harden config recovery and ai response validation
28
+
29
+ - Implement auto-healing for corrupted config.json
30
+ - Add defensive network error handling in ai.js
31
+ - Standardize internal engine response contract
24
32
  `;
25
33
 
26
34
  export const PR_SYSTEM_PROMPT = `