@hasna/terminal 2.0.2 → 2.0.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.
- package/dist/ai.js +49 -6
- package/dist/output-processor.js +11 -2
- package/package.json +1 -1
- package/src/ai.ts +39 -6
- package/src/output-processor.ts +12 -2
package/dist/ai.js
CHANGED
|
@@ -107,23 +107,66 @@ function detectProjectContext() {
|
|
|
107
107
|
catch { }
|
|
108
108
|
}
|
|
109
109
|
// Python
|
|
110
|
-
if (existsSync(join(cwd, "
|
|
111
|
-
|
|
110
|
+
if (existsSync(join(cwd, "pyproject.toml"))) {
|
|
111
|
+
try {
|
|
112
|
+
const pyproject = readFileSync(join(cwd, "pyproject.toml"), "utf8");
|
|
113
|
+
const nameMatch = pyproject.match(/name\s*=\s*"([^"]+)"/);
|
|
114
|
+
const versionMatch = pyproject.match(/version\s*=\s*"([^"]+)"/);
|
|
115
|
+
parts.push(`Project: ${nameMatch?.[1] ?? "Python"}${versionMatch ? `@${versionMatch[1]}` : ""} (Python)`);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
parts.push("Project: Python (pyproject.toml found)");
|
|
119
|
+
}
|
|
120
|
+
parts.push("Use pip/python/pytest commands. Test: pytest. Build: python -m build.");
|
|
121
|
+
}
|
|
122
|
+
else if (existsSync(join(cwd, "requirements.txt"))) {
|
|
123
|
+
parts.push("Project: Python (requirements.txt). Use pip/python/pytest commands.");
|
|
112
124
|
}
|
|
113
125
|
// Go
|
|
114
126
|
if (existsSync(join(cwd, "go.mod"))) {
|
|
115
|
-
|
|
127
|
+
try {
|
|
128
|
+
const gomod = readFileSync(join(cwd, "go.mod"), "utf8");
|
|
129
|
+
const moduleMatch = gomod.match(/module\s+(\S+)/);
|
|
130
|
+
parts.push(`Project: ${moduleMatch?.[1] ?? "Go"} (Go module)`);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
parts.push("Project: Go (go.mod found)");
|
|
134
|
+
}
|
|
135
|
+
parts.push("Use go build/test/run. Test: go test ./... Build: go build.");
|
|
116
136
|
}
|
|
117
137
|
// Rust
|
|
118
138
|
if (existsSync(join(cwd, "Cargo.toml"))) {
|
|
119
|
-
|
|
139
|
+
try {
|
|
140
|
+
const cargo = readFileSync(join(cwd, "Cargo.toml"), "utf8");
|
|
141
|
+
const nameMatch = cargo.match(/name\s*=\s*"([^"]+)"/);
|
|
142
|
+
const versionMatch = cargo.match(/version\s*=\s*"([^"]+)"/);
|
|
143
|
+
parts.push(`Project: ${nameMatch?.[1] ?? "Rust"}${versionMatch ? `@${versionMatch[1]}` : ""} (Rust/Cargo)`);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
parts.push("Project: Rust (Cargo.toml found)");
|
|
147
|
+
}
|
|
148
|
+
parts.push("Use cargo build/test/run. Test: cargo test. Build: cargo build --release.");
|
|
120
149
|
}
|
|
121
150
|
// Java
|
|
122
151
|
if (existsSync(join(cwd, "pom.xml"))) {
|
|
123
|
-
parts.push("Project: Java/Maven. Use mvn commands.");
|
|
152
|
+
parts.push("Project: Java/Maven. Use mvn commands. Test: mvn test. Build: mvn package.");
|
|
124
153
|
}
|
|
125
154
|
if (existsSync(join(cwd, "build.gradle")) || existsSync(join(cwd, "build.gradle.kts"))) {
|
|
126
|
-
parts.push("Project: Java/Gradle. Use gradle commands.");
|
|
155
|
+
parts.push("Project: Java/Gradle. Use gradle commands. Test: gradle test. Build: gradle build.");
|
|
156
|
+
}
|
|
157
|
+
// Docker
|
|
158
|
+
if (existsSync(join(cwd, "Dockerfile")) || existsSync(join(cwd, "docker-compose.yml")) || existsSync(join(cwd, "docker-compose.yaml"))) {
|
|
159
|
+
parts.push("Docker: Dockerfile/docker-compose present. Container commands available.");
|
|
160
|
+
}
|
|
161
|
+
// Makefile
|
|
162
|
+
if (existsSync(join(cwd, "Makefile"))) {
|
|
163
|
+
try {
|
|
164
|
+
const { execSync: execS } = require("child_process");
|
|
165
|
+
const targets = execS("grep -E '^[a-zA-Z_-]+:' Makefile | head -10 | cut -d: -f1", { cwd, encoding: "utf8", timeout: 1000 }).trim();
|
|
166
|
+
if (targets)
|
|
167
|
+
parts.push(`Makefile targets: ${targets.split("\n").join(", ")}`);
|
|
168
|
+
}
|
|
169
|
+
catch { }
|
|
127
170
|
}
|
|
128
171
|
// Directory structure — so AI knows actual paths (not guessed ones)
|
|
129
172
|
try {
|
package/dist/output-processor.js
CHANGED
|
@@ -15,7 +15,9 @@ RULES:
|
|
|
15
15
|
- Use symbols: ✓ for success/yes, ✗ for failure/no, ⚠ for warnings
|
|
16
16
|
- Maximum 8 lines
|
|
17
17
|
- Keep errors/failures verbatim
|
|
18
|
-
- Be direct and concise — the user wants an ANSWER, not a data dump
|
|
18
|
+
- Be direct and concise — the user wants an ANSWER, not a data dump
|
|
19
|
+
- For TEST OUTPUT: look for "X pass" and "X fail" lines. These are DEFINITIVE. If you see "42 pass, 0 fail" in the output, the answer is "42 tests pass, 0 fail." NEVER say "no tests found" or "incomplete" when pass/fail counts are visible.
|
|
20
|
+
- For BUILD OUTPUT: if tsc/build exits 0 with no output, it SUCCEEDED. Empty output = success.`;
|
|
19
21
|
/**
|
|
20
22
|
* Process command output through AI summarization.
|
|
21
23
|
* Cheap AI call (~100 tokens) saves 1000+ tokens downstream.
|
|
@@ -45,8 +47,15 @@ export async function processOutput(command, output, originalPrompt) {
|
|
|
45
47
|
output.slice(-tailChars);
|
|
46
48
|
}
|
|
47
49
|
try {
|
|
50
|
+
// Pre-parse: extract test counts so AI can't misread them
|
|
51
|
+
let preParseHint = "";
|
|
52
|
+
const passMatch = output.match(/(\d+)\s+pass/i);
|
|
53
|
+
const failMatch = output.match(/(\d+)\s+fail/i);
|
|
54
|
+
if (passMatch || failMatch) {
|
|
55
|
+
preParseHint = `\nPRE-PARSED TEST RESULTS: ${passMatch?.[1] ?? 0} passed, ${failMatch?.[1] ?? 0} failed. USE THESE NUMBERS.`;
|
|
56
|
+
}
|
|
48
57
|
const provider = getProvider();
|
|
49
|
-
const summary = await provider.complete(`${originalPrompt ? `User asked: ${originalPrompt}\n` : ""}Command: ${command}\nOutput (${lines.length} lines):\n${toSummarize}`, {
|
|
58
|
+
const summary = await provider.complete(`${originalPrompt ? `User asked: ${originalPrompt}\n` : ""}Command: ${command}${preParseHint}\nOutput (${lines.length} lines):\n${toSummarize}`, {
|
|
50
59
|
system: SUMMARIZE_PROMPT,
|
|
51
60
|
maxTokens: 300,
|
|
52
61
|
});
|
package/package.json
CHANGED
package/src/ai.ts
CHANGED
|
@@ -130,26 +130,59 @@ function detectProjectContext(): string {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
// Python
|
|
133
|
-
if (existsSync(join(cwd, "
|
|
134
|
-
|
|
133
|
+
if (existsSync(join(cwd, "pyproject.toml"))) {
|
|
134
|
+
try {
|
|
135
|
+
const pyproject = readFileSync(join(cwd, "pyproject.toml"), "utf8");
|
|
136
|
+
const nameMatch = pyproject.match(/name\s*=\s*"([^"]+)"/);
|
|
137
|
+
const versionMatch = pyproject.match(/version\s*=\s*"([^"]+)"/);
|
|
138
|
+
parts.push(`Project: ${nameMatch?.[1] ?? "Python"}${versionMatch ? `@${versionMatch[1]}` : ""} (Python)`);
|
|
139
|
+
} catch { parts.push("Project: Python (pyproject.toml found)"); }
|
|
140
|
+
parts.push("Use pip/python/pytest commands. Test: pytest. Build: python -m build.");
|
|
141
|
+
} else if (existsSync(join(cwd, "requirements.txt"))) {
|
|
142
|
+
parts.push("Project: Python (requirements.txt). Use pip/python/pytest commands.");
|
|
135
143
|
}
|
|
136
144
|
|
|
137
145
|
// Go
|
|
138
146
|
if (existsSync(join(cwd, "go.mod"))) {
|
|
139
|
-
|
|
147
|
+
try {
|
|
148
|
+
const gomod = readFileSync(join(cwd, "go.mod"), "utf8");
|
|
149
|
+
const moduleMatch = gomod.match(/module\s+(\S+)/);
|
|
150
|
+
parts.push(`Project: ${moduleMatch?.[1] ?? "Go"} (Go module)`);
|
|
151
|
+
} catch { parts.push("Project: Go (go.mod found)"); }
|
|
152
|
+
parts.push("Use go build/test/run. Test: go test ./... Build: go build.");
|
|
140
153
|
}
|
|
141
154
|
|
|
142
155
|
// Rust
|
|
143
156
|
if (existsSync(join(cwd, "Cargo.toml"))) {
|
|
144
|
-
|
|
157
|
+
try {
|
|
158
|
+
const cargo = readFileSync(join(cwd, "Cargo.toml"), "utf8");
|
|
159
|
+
const nameMatch = cargo.match(/name\s*=\s*"([^"]+)"/);
|
|
160
|
+
const versionMatch = cargo.match(/version\s*=\s*"([^"]+)"/);
|
|
161
|
+
parts.push(`Project: ${nameMatch?.[1] ?? "Rust"}${versionMatch ? `@${versionMatch[1]}` : ""} (Rust/Cargo)`);
|
|
162
|
+
} catch { parts.push("Project: Rust (Cargo.toml found)"); }
|
|
163
|
+
parts.push("Use cargo build/test/run. Test: cargo test. Build: cargo build --release.");
|
|
145
164
|
}
|
|
146
165
|
|
|
147
166
|
// Java
|
|
148
167
|
if (existsSync(join(cwd, "pom.xml"))) {
|
|
149
|
-
parts.push("Project: Java/Maven. Use mvn commands.");
|
|
168
|
+
parts.push("Project: Java/Maven. Use mvn commands. Test: mvn test. Build: mvn package.");
|
|
150
169
|
}
|
|
151
170
|
if (existsSync(join(cwd, "build.gradle")) || existsSync(join(cwd, "build.gradle.kts"))) {
|
|
152
|
-
parts.push("Project: Java/Gradle. Use gradle commands.");
|
|
171
|
+
parts.push("Project: Java/Gradle. Use gradle commands. Test: gradle test. Build: gradle build.");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Docker
|
|
175
|
+
if (existsSync(join(cwd, "Dockerfile")) || existsSync(join(cwd, "docker-compose.yml")) || existsSync(join(cwd, "docker-compose.yaml"))) {
|
|
176
|
+
parts.push("Docker: Dockerfile/docker-compose present. Container commands available.");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Makefile
|
|
180
|
+
if (existsSync(join(cwd, "Makefile"))) {
|
|
181
|
+
try {
|
|
182
|
+
const { execSync: execS } = require("child_process");
|
|
183
|
+
const targets = execS("grep -E '^[a-zA-Z_-]+:' Makefile | head -10 | cut -d: -f1", { cwd, encoding: "utf8", timeout: 1000 }).trim();
|
|
184
|
+
if (targets) parts.push(`Makefile targets: ${targets.split("\n").join(", ")}`);
|
|
185
|
+
} catch {}
|
|
153
186
|
}
|
|
154
187
|
|
|
155
188
|
// Directory structure — so AI knows actual paths (not guessed ones)
|
package/src/output-processor.ts
CHANGED
|
@@ -39,7 +39,9 @@ RULES:
|
|
|
39
39
|
- Use symbols: ✓ for success/yes, ✗ for failure/no, ⚠ for warnings
|
|
40
40
|
- Maximum 8 lines
|
|
41
41
|
- Keep errors/failures verbatim
|
|
42
|
-
- Be direct and concise — the user wants an ANSWER, not a data dump
|
|
42
|
+
- Be direct and concise — the user wants an ANSWER, not a data dump
|
|
43
|
+
- For TEST OUTPUT: look for "X pass" and "X fail" lines. These are DEFINITIVE. If you see "42 pass, 0 fail" in the output, the answer is "42 tests pass, 0 fail." NEVER say "no tests found" or "incomplete" when pass/fail counts are visible.
|
|
44
|
+
- For BUILD OUTPUT: if tsc/build exits 0 with no output, it SUCCEEDED. Empty output = success.`;
|
|
43
45
|
|
|
44
46
|
/**
|
|
45
47
|
* Process command output through AI summarization.
|
|
@@ -77,9 +79,17 @@ export async function processOutput(
|
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
try {
|
|
82
|
+
// Pre-parse: extract test counts so AI can't misread them
|
|
83
|
+
let preParseHint = "";
|
|
84
|
+
const passMatch = output.match(/(\d+)\s+pass/i);
|
|
85
|
+
const failMatch = output.match(/(\d+)\s+fail/i);
|
|
86
|
+
if (passMatch || failMatch) {
|
|
87
|
+
preParseHint = `\nPRE-PARSED TEST RESULTS: ${passMatch?.[1] ?? 0} passed, ${failMatch?.[1] ?? 0} failed. USE THESE NUMBERS.`;
|
|
88
|
+
}
|
|
89
|
+
|
|
80
90
|
const provider = getProvider();
|
|
81
91
|
const summary = await provider.complete(
|
|
82
|
-
`${originalPrompt ? `User asked: ${originalPrompt}\n` : ""}Command: ${command}\nOutput (${lines.length} lines):\n${toSummarize}`,
|
|
92
|
+
`${originalPrompt ? `User asked: ${originalPrompt}\n` : ""}Command: ${command}${preParseHint}\nOutput (${lines.length} lines):\n${toSummarize}`,
|
|
83
93
|
{
|
|
84
94
|
system: SUMMARIZE_PROMPT,
|
|
85
95
|
maxTokens: 300,
|