@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 +1 -1
- package/src/ai.js +50 -4
- package/src/config.js +6 -1
- package/src/index.js +63 -40
- package/src/prompt.js +22 -14
package/package.json
CHANGED
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
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
187
|
+
try {
|
|
188
|
+
if (!hasValidConfig()) {
|
|
189
|
+
await setupFlow();
|
|
190
|
+
}
|
|
172
191
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
10
|
+
1. OUTPUT ONLY THE COMMIT MESSAGE. No markdown, no filler.
|
|
7
11
|
2. Follow Conventional Commits: <type>(<scope>): <subject>
|
|
8
|
-
3. Use types:
|
|
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",
|
|
11
|
-
- Max 50 characters.
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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(
|
|
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 = `
|