tau-coding-agent 0.1.0
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/LICENSE +21 -0
- package/README.md +42 -0
- package/extensions/answer.ts +601 -0
- package/extensions/branch-term.ts +405 -0
- package/extensions/btw.ts +444 -0
- package/extensions/ghostty.ts +301 -0
- package/extensions/git-diff-stats.ts +277 -0
- package/extensions/git-pr-status.ts +286 -0
- package/extensions/insights.ts +2367 -0
- package/extensions/interlude.ts +144 -0
- package/extensions/loop.ts +529 -0
- package/extensions/memory.ts +1889 -0
- package/extensions/notify.ts +161 -0
- package/extensions/openai-fast.ts +227 -0
- package/extensions/openai-verbosity.ts +223 -0
- package/extensions/review.ts +4347 -0
- package/extensions/sandbox/index.ts +2578 -0
- package/extensions/usage/anthropic.ts +198 -0
- package/extensions/usage/github-copilot.ts +204 -0
- package/extensions/usage/google-gemini-cli.ts +232 -0
- package/extensions/usage/index.ts +2310 -0
- package/extensions/usage/minimax.ts +208 -0
- package/extensions/usage/openai-codex.ts +180 -0
- package/extensions/usage/openrouter.ts +168 -0
- package/extensions/usage/providers.ts +26 -0
- package/extensions/usage/shared.ts +156 -0
- package/extensions/usage/types.ts +53 -0
- package/extensions/usage/zai.ts +186 -0
- package/extensions/websearch/README.md +68 -0
- package/extensions/websearch/browser/chromium.ts +330 -0
- package/extensions/websearch/browser/discovery.ts +75 -0
- package/extensions/websearch/browser/firefox.ts +150 -0
- package/extensions/websearch/browser/sqlite.ts +38 -0
- package/extensions/websearch/config.ts +76 -0
- package/extensions/websearch/index.ts +312 -0
- package/extensions/websearch/normalize.ts +155 -0
- package/extensions/websearch/providers/anthropic.pi.ts +139 -0
- package/extensions/websearch/providers/gemini.browser.ts +185 -0
- package/extensions/websearch/providers/gemini.pi.ts +108 -0
- package/extensions/websearch/providers/openai-codex.browser.ts +77 -0
- package/extensions/websearch/providers/openai-codex.pi.ts +49 -0
- package/extensions/websearch/providers/openai-codex.shared.ts +123 -0
- package/extensions/websearch/providers/pi-model.shared.ts +86 -0
- package/extensions/websearch/providers/search-prompt.shared.ts +17 -0
- package/extensions/websearch/providers/shared.ts +76 -0
- package/extensions/websearch/types.ts +52 -0
- package/extensions/worktree.ts +2223 -0
- package/package.json +72 -0
- package/skills/browser-tools/SKILL.md +253 -0
- package/skills/browser-tools/scripts/browser-content.js +100 -0
- package/skills/browser-tools/scripts/browser-cookies.js +33 -0
- package/skills/browser-tools/scripts/browser-dismiss-cookies.js +455 -0
- package/skills/browser-tools/scripts/browser-eval.js +75 -0
- package/skills/browser-tools/scripts/browser-logs-tail.js +89 -0
- package/skills/browser-tools/scripts/browser-nav.js +28 -0
- package/skills/browser-tools/scripts/browser-net-summary.js +115 -0
- package/skills/browser-tools/scripts/browser-pick.js +133 -0
- package/skills/browser-tools/scripts/browser-screenshot.js +18 -0
- package/skills/browser-tools/scripts/browser-start.js +281 -0
- package/skills/browser-tools/scripts/browser-watch.js +321 -0
- package/skills/browser-tools/scripts/utils.js +60 -0
- package/skills/git-clean-history/SKILL.md +45 -0
- package/skills/git-commit/SKILL.md +55 -0
- package/skills/oracle/SKILL.md +64 -0
- package/skills/oracle/scripts/oracle +249 -0
- package/skills/oracle/scripts/oracle-bundle +227 -0
- package/skills/sentry/SKILL.md +198 -0
- package/skills/sentry/lib/auth.js +99 -0
- package/skills/sentry/scripts/fetch-event.js +325 -0
- package/skills/sentry/scripts/fetch-issue.js +368 -0
- package/skills/sentry/scripts/list-issues.js +245 -0
- package/skills/sentry/scripts/search-events.js +291 -0
- package/skills/sentry/scripts/search-logs.js +234 -0
- package/skills/update-changelog/SKILL.md +135 -0
- package/skills/web-design/SKILL.md +117 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Background logging for browser-tools.
|
|
5
|
+
*
|
|
6
|
+
* Logs console output, page errors, and network activity to JSONL.
|
|
7
|
+
*
|
|
8
|
+
* Default log root:
|
|
9
|
+
* $TMPDIR/agent-browser-tools/logs
|
|
10
|
+
*
|
|
11
|
+
* Override with:
|
|
12
|
+
* BROWSER_TOOLS_LOG_ROOT=/some/dir
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
createWriteStream,
|
|
17
|
+
existsSync,
|
|
18
|
+
mkdirSync,
|
|
19
|
+
readFileSync,
|
|
20
|
+
writeFileSync,
|
|
21
|
+
unlinkSync,
|
|
22
|
+
} from "node:fs";
|
|
23
|
+
import { tmpdir } from "node:os";
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
import puppeteer from "puppeteer-core";
|
|
26
|
+
|
|
27
|
+
const DEFAULT_TMP = process.platform === "win32" ? tmpdir() : "/tmp";
|
|
28
|
+
|
|
29
|
+
const LOG_ROOT =
|
|
30
|
+
process.env.BROWSER_TOOLS_LOG_ROOT || join(DEFAULT_TMP, "agent-browser-tools", "logs");
|
|
31
|
+
|
|
32
|
+
const PID_FILE = join(LOG_ROOT, ".watch.pid");
|
|
33
|
+
|
|
34
|
+
function ensureDir(dir) {
|
|
35
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isProcessAlive(pid) {
|
|
39
|
+
try {
|
|
40
|
+
process.kill(pid, 0);
|
|
41
|
+
return true;
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getDateDir() {
|
|
48
|
+
const now = new Date();
|
|
49
|
+
const yyyy = String(now.getFullYear());
|
|
50
|
+
const mm = String(now.getMonth() + 1).padStart(2, "0");
|
|
51
|
+
const dd = String(now.getDate()).padStart(2, "0");
|
|
52
|
+
return join(LOG_ROOT, `${yyyy}-${mm}-${dd}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function safeFileName(value) {
|
|
56
|
+
return String(value).replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function nowIso() {
|
|
60
|
+
return new Date().toISOString();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function connectBrowser(timeout = 5000) {
|
|
64
|
+
return Promise.race([
|
|
65
|
+
puppeteer.connect({ browserURL: "http://127.0.0.1:9222", defaultViewport: null }),
|
|
66
|
+
new Promise((_, reject) => {
|
|
67
|
+
setTimeout(() => reject(new Error("timeout")), timeout).unref();
|
|
68
|
+
}),
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
ensureDir(LOG_ROOT);
|
|
73
|
+
|
|
74
|
+
// Prevent duplicate watchers.
|
|
75
|
+
if (existsSync(PID_FILE)) {
|
|
76
|
+
try {
|
|
77
|
+
const existing = Number(readFileSync(PID_FILE, "utf8").trim());
|
|
78
|
+
if (existing && isProcessAlive(existing)) {
|
|
79
|
+
console.log("✓ watch already running");
|
|
80
|
+
process.exit(0);
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
// ignore stale pid
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
writeFileSync(PID_FILE, String(process.pid));
|
|
88
|
+
|
|
89
|
+
const dateDir = getDateDir();
|
|
90
|
+
ensureDir(dateDir);
|
|
91
|
+
|
|
92
|
+
// targetId -> { stream, filepath }
|
|
93
|
+
const targetState = new Map();
|
|
94
|
+
// request object -> requestId
|
|
95
|
+
const requestIds = new WeakMap();
|
|
96
|
+
// page -> targetId
|
|
97
|
+
const pageToTarget = new WeakMap();
|
|
98
|
+
const instrumentedPages = new WeakSet();
|
|
99
|
+
let nextSyntheticPageId = 1;
|
|
100
|
+
|
|
101
|
+
function getOrCreateTargetState(targetId) {
|
|
102
|
+
let state = targetState.get(targetId);
|
|
103
|
+
if (state) return state;
|
|
104
|
+
|
|
105
|
+
const filename = `${safeFileName(targetId)}.jsonl`;
|
|
106
|
+
const filepath = join(dateDir, filename);
|
|
107
|
+
const stream = createWriteStream(filepath, { flags: "a" });
|
|
108
|
+
|
|
109
|
+
state = { stream, filepath };
|
|
110
|
+
targetState.set(targetId, state);
|
|
111
|
+
return state;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function writeLog(targetId, payload) {
|
|
115
|
+
const state = getOrCreateTargetState(targetId);
|
|
116
|
+
const record = {
|
|
117
|
+
ts: nowIso(),
|
|
118
|
+
targetId,
|
|
119
|
+
...payload,
|
|
120
|
+
};
|
|
121
|
+
state.stream.write(`${JSON.stringify(record)}\n`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function internalTargetId(page) {
|
|
125
|
+
try {
|
|
126
|
+
const t = page.target?.();
|
|
127
|
+
return t?._targetId || t?._targetInfo?.targetId || t?._id || null;
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function ensureTargetId(page) {
|
|
134
|
+
const existing = pageToTarget.get(page);
|
|
135
|
+
if (existing) return existing;
|
|
136
|
+
|
|
137
|
+
const internal = internalTargetId(page);
|
|
138
|
+
const chosen = internal ? String(internal) : `page-${nextSyntheticPageId++}`;
|
|
139
|
+
pageToTarget.set(page, chosen);
|
|
140
|
+
return chosen;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function ensureRequestId(targetId, request) {
|
|
144
|
+
if (requestIds.has(request)) return requestIds.get(request);
|
|
145
|
+
const id = `${targetId}-${Math.random().toString(16).slice(2)}-${Date.now()}`;
|
|
146
|
+
requestIds.set(request, id);
|
|
147
|
+
return id;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function closeTarget(targetId) {
|
|
151
|
+
const state = targetState.get(targetId);
|
|
152
|
+
if (!state) return;
|
|
153
|
+
try {
|
|
154
|
+
state.stream.end();
|
|
155
|
+
} catch {
|
|
156
|
+
// ignore
|
|
157
|
+
}
|
|
158
|
+
targetState.delete(targetId);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function instrumentPage(page) {
|
|
162
|
+
if (instrumentedPages.has(page)) return;
|
|
163
|
+
instrumentedPages.add(page);
|
|
164
|
+
|
|
165
|
+
const targetId = ensureTargetId(page);
|
|
166
|
+
|
|
167
|
+
// Do not double-instrument.
|
|
168
|
+
if (targetState.has(targetId)) {
|
|
169
|
+
// Note: this assumes a 1:1 mapping between targetId and page.
|
|
170
|
+
// That’s fine for our usage.
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
writeLog(targetId, {
|
|
174
|
+
type: "target.attached",
|
|
175
|
+
url: page.url?.() || null,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
page.on("console", (msg) => {
|
|
179
|
+
try {
|
|
180
|
+
writeLog(targetId, {
|
|
181
|
+
type: "console",
|
|
182
|
+
level: msg.type?.() || null,
|
|
183
|
+
text: msg.text?.() || null,
|
|
184
|
+
args: (msg.args?.() || []).map((a) => a.toString()),
|
|
185
|
+
});
|
|
186
|
+
} catch {
|
|
187
|
+
// ignore
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
page.on("pageerror", (err) => {
|
|
192
|
+
writeLog(targetId, {
|
|
193
|
+
type: "exception",
|
|
194
|
+
text: err?.message || String(err),
|
|
195
|
+
stack: err?.stack || null,
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
page.on("error", (err) => {
|
|
200
|
+
writeLog(targetId, {
|
|
201
|
+
type: "page.crash",
|
|
202
|
+
text: err?.message || String(err),
|
|
203
|
+
stack: err?.stack || null,
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
page.on("request", (req) => {
|
|
208
|
+
const requestId = ensureRequestId(targetId, req);
|
|
209
|
+
writeLog(targetId, {
|
|
210
|
+
type: "network.request",
|
|
211
|
+
requestId,
|
|
212
|
+
method: req.method?.() || null,
|
|
213
|
+
url: req.url?.() || null,
|
|
214
|
+
resourceType: req.resourceType?.() || null,
|
|
215
|
+
isNavigationRequest: !!req.isNavigationRequest?.(),
|
|
216
|
+
hasPostData: !!req.postData?.(),
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
page.on("response", (res) => {
|
|
221
|
+
try {
|
|
222
|
+
const req = res.request();
|
|
223
|
+
const requestId = ensureRequestId(targetId, req);
|
|
224
|
+
const headers = res.headers?.() || {};
|
|
225
|
+
const contentType = headers["content-type"] || headers["Content-Type"] || null;
|
|
226
|
+
|
|
227
|
+
writeLog(targetId, {
|
|
228
|
+
type: "network.response",
|
|
229
|
+
requestId,
|
|
230
|
+
url: res.url?.() || null,
|
|
231
|
+
status: res.status?.(),
|
|
232
|
+
statusText: res.statusText?.() || null,
|
|
233
|
+
mimeType: contentType,
|
|
234
|
+
fromDiskCache: !!res.fromCache?.(),
|
|
235
|
+
fromServiceWorker: !!res.fromServiceWorker?.(),
|
|
236
|
+
});
|
|
237
|
+
} catch (e) {
|
|
238
|
+
writeLog(targetId, {
|
|
239
|
+
type: "network.response",
|
|
240
|
+
requestId: null,
|
|
241
|
+
url: null,
|
|
242
|
+
status: null,
|
|
243
|
+
statusText: null,
|
|
244
|
+
mimeType: null,
|
|
245
|
+
error: e?.message || String(e),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
page.on("requestfailed", (req) => {
|
|
251
|
+
const requestId = ensureRequestId(targetId, req);
|
|
252
|
+
writeLog(targetId, {
|
|
253
|
+
type: "network.failure",
|
|
254
|
+
requestId,
|
|
255
|
+
url: req.url?.() || null,
|
|
256
|
+
errorText: req.failure?.()?.errorText || null,
|
|
257
|
+
canceled: req.failure?.()?.errorText === "net::ERR_ABORTED",
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
page.on("framenavigated", (frame) => {
|
|
262
|
+
if (frame === page.mainFrame()) {
|
|
263
|
+
writeLog(targetId, {
|
|
264
|
+
type: "page.navigated",
|
|
265
|
+
url: frame.url?.() || null,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
page.on("close", () => {
|
|
271
|
+
writeLog(targetId, { type: "target.closed" });
|
|
272
|
+
closeTarget(targetId);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function cleanupAndExit(code = 0) {
|
|
277
|
+
try {
|
|
278
|
+
for (const targetId of targetState.keys()) {
|
|
279
|
+
closeTarget(targetId);
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
unlinkSync(PID_FILE);
|
|
283
|
+
} catch {
|
|
284
|
+
// ignore
|
|
285
|
+
}
|
|
286
|
+
} finally {
|
|
287
|
+
process.exit(code);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
process.on("SIGINT", () => cleanupAndExit(0));
|
|
292
|
+
process.on("SIGTERM", () => cleanupAndExit(0));
|
|
293
|
+
|
|
294
|
+
async function main() {
|
|
295
|
+
const browser = await connectBrowser(5000).catch((e) => {
|
|
296
|
+
console.error("✗ Could not connect to browser:", e.message);
|
|
297
|
+
console.error(" Run: browser-start.js");
|
|
298
|
+
process.exit(1);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Existing pages.
|
|
302
|
+
const pages = await browser.pages();
|
|
303
|
+
for (const page of pages) {
|
|
304
|
+
await instrumentPage(page);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// New pages.
|
|
308
|
+
browser.on("targetcreated", async (target) => {
|
|
309
|
+
try {
|
|
310
|
+
if (target.type?.() !== "page") return;
|
|
311
|
+
const page = await target.page();
|
|
312
|
+
if (page) await instrumentPage(page);
|
|
313
|
+
} catch {
|
|
314
|
+
// ignore
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
console.log(`✓ watch started (logs: ${dateDir})`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
await main();
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import puppeteer from "puppeteer-core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Connect to browser with timeout, exit on failure.
|
|
5
|
+
* @param {number} timeout - Connection timeout in ms (default: 5000)
|
|
6
|
+
* @returns {Promise<Browser>}
|
|
7
|
+
*/
|
|
8
|
+
export async function connectBrowser(timeout = 5000) {
|
|
9
|
+
const browser = await Promise.race([
|
|
10
|
+
puppeteer.connect({
|
|
11
|
+
browserURL: "http://127.0.0.1:9222",
|
|
12
|
+
defaultViewport: null,
|
|
13
|
+
}),
|
|
14
|
+
new Promise((_, reject) => {
|
|
15
|
+
setTimeout(() => reject(new Error("timeout")), timeout).unref();
|
|
16
|
+
}),
|
|
17
|
+
]).catch((e) => {
|
|
18
|
+
console.error("✗ Could not connect to browser:", e.message);
|
|
19
|
+
console.error(" Run: browser-start.js");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
});
|
|
22
|
+
return browser;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get the active/focused page. Fall back to the last http/https page, then the last page.
|
|
27
|
+
* Exit if none found.
|
|
28
|
+
* @param {Browser} browser
|
|
29
|
+
* @returns {Promise<Page>}
|
|
30
|
+
*/
|
|
31
|
+
export async function getActivePage(browser) {
|
|
32
|
+
const pages = await browser.pages();
|
|
33
|
+
if (pages.length === 0) {
|
|
34
|
+
console.error("✗ No active tab found");
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const page of [...pages].reverse()) {
|
|
39
|
+
try {
|
|
40
|
+
const isActive = await page.evaluate(
|
|
41
|
+
() => document.visibilityState === "visible" && document.hasFocus(),
|
|
42
|
+
);
|
|
43
|
+
if (isActive) return page;
|
|
44
|
+
} catch {}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return pages.filter((page) => page.url().startsWith("http")).at(-1) || pages.at(-1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Print result, using JSON for objects/arrays.
|
|
52
|
+
* @param {any} result
|
|
53
|
+
*/
|
|
54
|
+
export function printResult(result) {
|
|
55
|
+
if (typeof result === "object" && result !== null) {
|
|
56
|
+
console.log(JSON.stringify(result, null, 2));
|
|
57
|
+
} else {
|
|
58
|
+
console.log(result);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: git-clean-history
|
|
3
|
+
description: "Reimplement the current Git branch on a fresh branch off `main` with a clean, narrative-quality commit history."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Git Rebase
|
|
7
|
+
|
|
8
|
+
Use this skill to reimplement the current branch on a new branch with a clean, narrative-quality git commit history suitable for reviewer comprehension.
|
|
9
|
+
|
|
10
|
+
## Steps
|
|
11
|
+
|
|
12
|
+
1. **Validate the source branch**
|
|
13
|
+
- Ensure no uncommitted changes or merge conflicts
|
|
14
|
+
- Confirm it is up to date with `main`
|
|
15
|
+
|
|
16
|
+
2. **Analyze the diff**
|
|
17
|
+
- Study all changes between source branch and `main`
|
|
18
|
+
- Form a clear understanding of the final intended state
|
|
19
|
+
|
|
20
|
+
3. **Create the clean branch**
|
|
21
|
+
- Create a new branch off of `main` using the new branch name
|
|
22
|
+
- Use the `{source_branch}-clean` name unless another name is provided by the user
|
|
23
|
+
|
|
24
|
+
4. **Plan the commit storyline**
|
|
25
|
+
- Break the implementation into self-contained logical steps
|
|
26
|
+
- Each step should reflect a stage of development, as if writing a tutorial
|
|
27
|
+
|
|
28
|
+
5. **Reimplement the work**
|
|
29
|
+
- Recreate changes in the clean branch, committing step by step
|
|
30
|
+
- Each commit must:
|
|
31
|
+
- Introduce a single coherent idea
|
|
32
|
+
- Include a clear commit message and description
|
|
33
|
+
- Follow best practices for the message as outlined by the `git-commit` skill
|
|
34
|
+
- **Use `git commit --no-verify` for all intermediate commits**
|
|
35
|
+
- Pre-commit hooks check tests, types, and imports that may not pass until the full implementation is complete; do not waste time fixing issues in intermediate commits that will be resolved by later commits
|
|
36
|
+
|
|
37
|
+
6. **Verify correctness**
|
|
38
|
+
- Confirm the final state exactly matches the source branch
|
|
39
|
+
- Run the final commit **without** `--no-verify` to ensure all checks pass
|
|
40
|
+
|
|
41
|
+
### Rules
|
|
42
|
+
|
|
43
|
+
- Do not add yourself as an author or contributor
|
|
44
|
+
- Do not include "Generated with ...", "Co-Authored-By: ...", or any AI attribution
|
|
45
|
+
- The end state of the clean branch must be identical to the source branch
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: git-commit
|
|
3
|
+
description: "Commit changes with an emphasis on tidiness and reviewability: small, focused commits, with clear rationale in messages."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Git Commit
|
|
7
|
+
|
|
8
|
+
Use this skill as the playbook for producing reviewable commits and a clean, consistent commit history.
|
|
9
|
+
|
|
10
|
+
## Phase 1: Review changes
|
|
11
|
+
|
|
12
|
+
1. Check what files have changed
|
|
13
|
+
2. If there are no changes to commit, inform the user and stop
|
|
14
|
+
3. If there are unstaged changes, stage them with `git add`
|
|
15
|
+
|
|
16
|
+
Rules:
|
|
17
|
+
|
|
18
|
+
- Keep commits small, focused, and atomic
|
|
19
|
+
- Avoid commits that mix unrelated changes (e.g., "Address feedback")
|
|
20
|
+
- Never stage or commit files that look like secrets (e.g. `.env`, credentials, API keys)
|
|
21
|
+
|
|
22
|
+
## Phase 2: Generate commit message
|
|
23
|
+
|
|
24
|
+
1. Remember commit messages best practices
|
|
25
|
+
2. Write a short subject line:
|
|
26
|
+
- Up to 50 chars
|
|
27
|
+
- Start subject with a capital letter, don’t end with a period
|
|
28
|
+
- Use imperative mood (e.g. "Fix memory leak while scrolling widget")
|
|
29
|
+
3. Leave a blank line between subject and body
|
|
30
|
+
4. Only when needed (don’t force it for trivial changes), write a body:
|
|
31
|
+
- Wrap lines at 72 chars
|
|
32
|
+
- Focus on "what" and "why", not the "how"
|
|
33
|
+
- Explain the motivation
|
|
34
|
+
- Mention side effects, trade-offs, or alternatives considered
|
|
35
|
+
- Reference relevant issue IDs / tickets at the bottom if known
|
|
36
|
+
5. Check style of recent commits in the repo (tone, structure, capitalization, length, etc): `git log -10 --oneline`
|
|
37
|
+
6. Generate a message that adheres to the style in the repo while following best practices
|
|
38
|
+
|
|
39
|
+
Rules:
|
|
40
|
+
|
|
41
|
+
- Never stage files that look like secrets (e.g. `.env`, credentials, API keys)
|
|
42
|
+
- Do not add yourself as an author or contributor
|
|
43
|
+
- Do not include "Generated with ...", "Co-Authored-By: ...", or any AI attribution
|
|
44
|
+
|
|
45
|
+
## Phase 3: Commit
|
|
46
|
+
|
|
47
|
+
1. Commit staged changes with `git commit`
|
|
48
|
+
|
|
49
|
+
Rules:
|
|
50
|
+
|
|
51
|
+
- Avoid `git commit --amend` unless explicitly requested
|
|
52
|
+
- Do not bypass hooks with `git commit --no-verify`
|
|
53
|
+
- If `git commit` fails due to hooks, fix issues and retry
|
|
54
|
+
- Use `git commit -F` (or heredoc) when there is a body, never newline escapes in `-m`.
|
|
55
|
+
- Never push to remotes unless explicitly requested
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: oracle
|
|
3
|
+
description: Get a second opinion by bundling a prompt + a curated file set, then asking a strong model from a different family through a separate Pi invocation.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Oracle
|
|
7
|
+
|
|
8
|
+
Use this skill when you want a second opinion from a capable model in a different family than the current session.
|
|
9
|
+
|
|
10
|
+
The oracle workflow bundles selected files into a standalone prompt, selects an alternate model with `pi --list-models`, and sends the bundle to `pi -p --no-tools`. It is read-only by default because the oracle receives the selected context directly and does not get tools.
|
|
11
|
+
|
|
12
|
+
## Model selection
|
|
13
|
+
|
|
14
|
+
The `./scripts/oracle` wrapper chooses the strongest known model it can find in the first usable family from this order:
|
|
15
|
+
|
|
16
|
+
| Current family | Oracle preference |
|
|
17
|
+
| ------------------ | ----------------------------------------------------- |
|
|
18
|
+
| OpenAI / Codex | Claude / Anthropic → Gemini / Google → OpenAI / Codex |
|
|
19
|
+
| Claude / Anthropic | OpenAI / Codex → Gemini / Google → Claude / Anthropic |
|
|
20
|
+
| Gemini / Google | OpenAI / Codex → Claude / Anthropic → Gemini / Google |
|
|
21
|
+
| Unknown | Claude / Anthropic → OpenAI / Codex → Gemini / Google |
|
|
22
|
+
|
|
23
|
+
It prefers models listed in Pi's `enabledModels`, then falls back to the best matching model shown by `pi --list-models`. Override only when needed with `--model provider/model`.
|
|
24
|
+
|
|
25
|
+
If the current model is not obvious to the script, pass it explicitly with `--current provider/model`. Otherwise it falls back to Pi's default model from `settings.json`.
|
|
26
|
+
|
|
27
|
+
## Workflow
|
|
28
|
+
|
|
29
|
+
1. Pick the smallest file set that contains the truth. Avoid secrets by default.
|
|
30
|
+
2. Preview selected files before sending.
|
|
31
|
+
3. Ask the oracle with a standalone prompt that states the task, constraints, and desired output format.
|
|
32
|
+
4. Treat the result as advice, not authority. Reason from first principles before applying recommendations.
|
|
33
|
+
|
|
34
|
+
## Commands
|
|
35
|
+
|
|
36
|
+
Run commands from this skill directory:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Preview selected files
|
|
40
|
+
./scripts/oracle-bundle --dry-run -p "<task>" --file "src/**" --file "!**/*.test.*"
|
|
41
|
+
|
|
42
|
+
# Preview the full bundle
|
|
43
|
+
./scripts/oracle-bundle -p "<task>" --file "src/**" --file "!**/*.test.*"
|
|
44
|
+
|
|
45
|
+
# Show which oracle model would be selected
|
|
46
|
+
./scripts/oracle --list-models --current openai-codex/gpt-5.5
|
|
47
|
+
|
|
48
|
+
# Ask the automatically selected oracle model
|
|
49
|
+
./scripts/oracle --current openai-codex/gpt-5.5 \
|
|
50
|
+
-p "<task>" --file "src/**" --file "!**/*.test.*"
|
|
51
|
+
|
|
52
|
+
# Override the oracle model when the automatic choice is wrong
|
|
53
|
+
./scripts/oracle --model github-copilot/claude-opus-4.7 \
|
|
54
|
+
-p "<task>" --file "src/**" --file "!**/*.test.*"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Tips
|
|
58
|
+
|
|
59
|
+
- Prefer a minimal file set over the whole repo.
|
|
60
|
+
- If you need diffs reviewed, paste the diff into the prompt or attach the diff file via `--file`.
|
|
61
|
+
- Make the prompt completely standalone: include intent, goals, constraints, error text, and whether you want a plan, review, pros/cons, or patch guidance.
|
|
62
|
+
- Never include secrets (`.env`, tokens, key files).
|
|
63
|
+
- For general code review, ask for feedback on correctness, consistency with existing code, simplicity, maintainability, performance, security, and edge cases.
|
|
64
|
+
- Oracle can be slow while it reasons. Allow it several minutes to process.
|