opencode-autognosis 1.0.1 → 2.0.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/README.md +346 -76
- package/dist/activeset.d.ts +3 -0
- package/dist/activeset.js +498 -0
- package/dist/chunk-cards.d.ts +3 -0
- package/dist/chunk-cards.js +586 -0
- package/dist/git-worktree.d.ts +3 -0
- package/dist/git-worktree.js +384 -0
- package/dist/index.js +12 -0
- package/dist/module-summaries.d.ts +3 -0
- package/dist/module-summaries.js +510 -0
- package/dist/performance-optimization.d.ts +3 -0
- package/dist/performance-optimization.js +653 -0
- package/dist/testing-infrastructure.d.ts +3 -0
- package/dist/testing-infrastructure.js +603 -0
- package/package.json +27 -3
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
|
+
import { exec } from "node:child_process";
|
|
3
|
+
import * as fs from "node:fs/promises";
|
|
4
|
+
import * as fsSync from "node:fs";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
import * as crypto from "node:crypto";
|
|
8
|
+
const execAsync = promisify(exec);
|
|
9
|
+
const PROJECT_ROOT = process.cwd();
|
|
10
|
+
const OPENCODE_DIR = path.join(PROJECT_ROOT, ".opencode");
|
|
11
|
+
const WORKTREE_DIR = path.join(OPENCODE_DIR, "worktrees");
|
|
12
|
+
// Internal logging
|
|
13
|
+
function log(message, data) {
|
|
14
|
+
console.error(`[GitWorktree] ${message}`, data || '');
|
|
15
|
+
}
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// HELPERS
|
|
18
|
+
// =============================================================================
|
|
19
|
+
async function runCmd(cmd, cwd = PROJECT_ROOT, timeoutMs = 30000) {
|
|
20
|
+
try {
|
|
21
|
+
const { stdout, stderr } = await execAsync(cmd, {
|
|
22
|
+
cwd,
|
|
23
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
24
|
+
timeout: timeoutMs
|
|
25
|
+
});
|
|
26
|
+
return { stdout: stdout.trim(), stderr: stderr.trim() };
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error.signal === 'SIGTERM' && error.code === undefined) {
|
|
30
|
+
return { stdout: "", stderr: `Command timed out after ${timeoutMs}ms`, error, timedOut: true };
|
|
31
|
+
}
|
|
32
|
+
return { stdout: "", stderr: error instanceof Error ? error.message : `${error}`, error };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function ensureWorktreeDir() {
|
|
36
|
+
await fs.mkdir(WORKTREE_DIR, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
async function getGitState() {
|
|
39
|
+
const { stdout: head } = await runCmd("git rev-parse HEAD");
|
|
40
|
+
const { stdout: branch } = await runCmd("git rev-parse --abbrev-ref HEAD");
|
|
41
|
+
const { stdout: status } = await runCmd("git status --porcelain");
|
|
42
|
+
const { stdout: remotes } = await runCmd("git remote");
|
|
43
|
+
return {
|
|
44
|
+
head,
|
|
45
|
+
branch,
|
|
46
|
+
isDirty: status.length > 0,
|
|
47
|
+
statusLines: status.split('\n').filter(Boolean),
|
|
48
|
+
remotes: remotes.split('\n').filter(Boolean),
|
|
49
|
+
isMainBranch: branch === "main" || branch === "master"
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async function discoverWorktrees() {
|
|
53
|
+
await ensureWorktreeDir();
|
|
54
|
+
try {
|
|
55
|
+
const worktrees = await fs.readdir(WORKTREE_DIR);
|
|
56
|
+
const results = [];
|
|
57
|
+
for (const worktree of worktrees) {
|
|
58
|
+
const worktreePath = path.join(WORKTREE_DIR, worktree);
|
|
59
|
+
const stat = await fs.stat(worktreePath);
|
|
60
|
+
if (stat.isDirectory()) {
|
|
61
|
+
const gitDir = path.join(worktreePath, ".git");
|
|
62
|
+
if (fsSync.existsSync(gitDir)) {
|
|
63
|
+
const { stdout: branch } = await runCmd("git rev-parse --abbrev-ref HEAD", worktreePath);
|
|
64
|
+
const { stdout: head } = await runCmd("git rev-parse HEAD", worktreePath);
|
|
65
|
+
const { stdout: status } = await runCmd("git status --porcelain", worktreePath);
|
|
66
|
+
results.push({
|
|
67
|
+
name: worktree,
|
|
68
|
+
path: worktreePath,
|
|
69
|
+
branch,
|
|
70
|
+
head,
|
|
71
|
+
isDirty: status.length > 0,
|
|
72
|
+
statusLines: status.split('\n').filter(Boolean)
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return results;
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// =============================================================================
|
|
84
|
+
// AGENT CONTRACT INTEGRATION
|
|
85
|
+
// =============================================================================
|
|
86
|
+
const AGENT_USAGE_POLICY = `
|
|
87
|
+
AGENT USAGE POLICY:
|
|
88
|
+
- Use git.preflight before any repository operations
|
|
89
|
+
- Create checkpoints with git.checkpoint_create for clean session starts
|
|
90
|
+
- Work in integration branches, never directly on main/master
|
|
91
|
+
- Validate all patches before application
|
|
92
|
+
- Clean up worktrees after completion to avoid resource leaks
|
|
93
|
+
`.trim();
|
|
94
|
+
const WORKFLOW_PATTERNS = `
|
|
95
|
+
STANDARD WORKFLOW:
|
|
96
|
+
1. git.preflight -> Validate repository state
|
|
97
|
+
2. git.checkpoint_create -> Create clean starting point
|
|
98
|
+
3. Work in feature/integration branch
|
|
99
|
+
4. git.validate_patch -> Verify changes before commit
|
|
100
|
+
5. Clean up worktrees and finalize
|
|
101
|
+
`.trim();
|
|
102
|
+
// =============================================================================
|
|
103
|
+
// GIT WORKTREE MANAGEMENT TOOLS
|
|
104
|
+
// =============================================================================
|
|
105
|
+
export function gitWorktreeTools() {
|
|
106
|
+
return {
|
|
107
|
+
git_preflight: tool({
|
|
108
|
+
description: `Validate repository state and check prerequisites for Git operations. ${AGENT_USAGE_POLICY} Returns comprehensive repository status including branch state, cleanliness, and available worktrees.`,
|
|
109
|
+
args: {
|
|
110
|
+
strict: tool.schema.boolean().optional().default(false).describe("Enable strict validation mode")
|
|
111
|
+
},
|
|
112
|
+
async execute({ strict }) {
|
|
113
|
+
log("Tool call: git_preflight", { strict });
|
|
114
|
+
try {
|
|
115
|
+
const state = await getGitState();
|
|
116
|
+
const worktrees = await discoverWorktrees();
|
|
117
|
+
const issues = [];
|
|
118
|
+
const warnings = [];
|
|
119
|
+
// Check for dirty working directory
|
|
120
|
+
if (state.isDirty) {
|
|
121
|
+
if (strict) {
|
|
122
|
+
issues.push("Working directory is dirty - commit or stash changes");
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
warnings.push("Working directory is dirty");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Check for main branch
|
|
129
|
+
if (state.isMainBranch && state.isDirty) {
|
|
130
|
+
issues.push("Never work directly on main/master branch with uncommitted changes");
|
|
131
|
+
}
|
|
132
|
+
// Check for worktree conflicts
|
|
133
|
+
const conflictingWorktrees = worktrees.filter(w => w.branch === state.branch);
|
|
134
|
+
if (conflictingWorktrees.length > 0) {
|
|
135
|
+
warnings.push(`Branch '${state.branch}' is checked out in ${conflictingWorktrees.length} worktree(s)`);
|
|
136
|
+
}
|
|
137
|
+
// Check for git availability
|
|
138
|
+
const { error: gitError } = await runCmd("git --version");
|
|
139
|
+
if (gitError) {
|
|
140
|
+
issues.push("Git is not available or not in PATH");
|
|
141
|
+
}
|
|
142
|
+
const result = {
|
|
143
|
+
status: issues.length > 0 ? "FAILED" : warnings.length > 0 ? "WARNING" : "OK",
|
|
144
|
+
repository: {
|
|
145
|
+
head: state.head,
|
|
146
|
+
branch: state.branch,
|
|
147
|
+
isDirty: state.isDirty,
|
|
148
|
+
isMainBranch: state.isMainBranch,
|
|
149
|
+
statusLines: state.statusLines,
|
|
150
|
+
remotes: state.remotes
|
|
151
|
+
},
|
|
152
|
+
worktrees: {
|
|
153
|
+
discovered: worktrees,
|
|
154
|
+
count: worktrees.length
|
|
155
|
+
},
|
|
156
|
+
validation: {
|
|
157
|
+
issues,
|
|
158
|
+
warnings,
|
|
159
|
+
strict
|
|
160
|
+
},
|
|
161
|
+
workflow: WORKFLOW_PATTERNS
|
|
162
|
+
};
|
|
163
|
+
return JSON.stringify(result, null, 2);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
return JSON.stringify({
|
|
167
|
+
status: "ERROR",
|
|
168
|
+
message: error instanceof Error ? error.message : `${error}`,
|
|
169
|
+
workflow: WORKFLOW_PATTERNS
|
|
170
|
+
}, null, 2);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}),
|
|
174
|
+
git_checkpoint_create: tool({
|
|
175
|
+
description: `Create a clean checkpoint for starting a new work session. ${AGENT_USAGE_POLICY} Creates a deterministic integration branch and optional worktree for isolated work.`,
|
|
176
|
+
args: {
|
|
177
|
+
branch_name: tool.schema.string().optional().describe("Custom branch name (auto-generated if not provided)"),
|
|
178
|
+
create_worktree: tool.schema.boolean().optional().default(true).describe("Create isolated worktree for the branch"),
|
|
179
|
+
message: tool.schema.string().optional().default("Agent checkpoint").describe("Checkpoint commit message")
|
|
180
|
+
},
|
|
181
|
+
async execute({ branch_name, create_worktree, message }) {
|
|
182
|
+
log("Tool call: git_checkpoint_create", { branch_name, create_worktree, message });
|
|
183
|
+
try {
|
|
184
|
+
const state = await getGitState();
|
|
185
|
+
// Generate branch name if not provided
|
|
186
|
+
const targetBranch = branch_name || `agent-integration-${Date.now()}`;
|
|
187
|
+
// Ensure we're on a clean state
|
|
188
|
+
if (state.isDirty) {
|
|
189
|
+
return JSON.stringify({
|
|
190
|
+
status: "ERROR",
|
|
191
|
+
message: "Cannot create checkpoint from dirty working directory. Commit or stash changes first.",
|
|
192
|
+
current_state: state
|
|
193
|
+
}, null, 2);
|
|
194
|
+
}
|
|
195
|
+
// Create and checkout new branch
|
|
196
|
+
const { error: branchError } = await runCmd(`git checkout -b ${targetBranch}`);
|
|
197
|
+
if (branchError) {
|
|
198
|
+
return JSON.stringify({
|
|
199
|
+
status: "ERROR",
|
|
200
|
+
message: `Failed to create branch: ${branchError instanceof Error ? branchError.message : `${branchError}`}`,
|
|
201
|
+
current_state: state
|
|
202
|
+
}, null, 2);
|
|
203
|
+
}
|
|
204
|
+
// Create initial commit if needed
|
|
205
|
+
const { stdout: log } = await runCmd("git log --oneline -1");
|
|
206
|
+
if (!log || log.includes("Initial commit")) {
|
|
207
|
+
await runCmd(`git commit --allow-empty -m "${message}"`);
|
|
208
|
+
}
|
|
209
|
+
let worktreePath = null;
|
|
210
|
+
if (create_worktree) {
|
|
211
|
+
await ensureWorktreeDir();
|
|
212
|
+
worktreePath = path.join(WORKTREE_DIR, targetBranch.replace(/[^a-zA-Z0-9-_]/g, '-'));
|
|
213
|
+
// Remove existing worktree if it exists
|
|
214
|
+
if (fsSync.existsSync(worktreePath)) {
|
|
215
|
+
const { error: removeError } = await runCmd(`git worktree remove ${worktreePath}`);
|
|
216
|
+
if (removeError) {
|
|
217
|
+
console.error("[GitWorktree] Warning: Failed to remove existing worktree", removeError);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Create new worktree
|
|
221
|
+
const { error: worktreeError } = await runCmd(`git worktree add ${worktreePath} ${targetBranch}`);
|
|
222
|
+
if (worktreeError) {
|
|
223
|
+
return JSON.stringify({
|
|
224
|
+
status: "WARNING",
|
|
225
|
+
message: `Branch created but worktree failed: ${worktreeError instanceof Error ? worktreeError.message : `${worktreeError}`}`,
|
|
226
|
+
checkpoint: {
|
|
227
|
+
branch: targetBranch,
|
|
228
|
+
head: state.head,
|
|
229
|
+
created_at: new Date().toISOString()
|
|
230
|
+
}
|
|
231
|
+
}, null, 2);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const result = {
|
|
235
|
+
status: "SUCCESS",
|
|
236
|
+
checkpoint: {
|
|
237
|
+
branch: targetBranch,
|
|
238
|
+
head: state.head,
|
|
239
|
+
created_at: new Date().toISOString(),
|
|
240
|
+
message
|
|
241
|
+
},
|
|
242
|
+
worktree: worktreePath ? {
|
|
243
|
+
path: worktreePath,
|
|
244
|
+
branch: targetBranch
|
|
245
|
+
} : null,
|
|
246
|
+
next_steps: [
|
|
247
|
+
`Work in branch: ${targetBranch}`,
|
|
248
|
+
worktreePath ? `Use worktree: ${worktreePath}` : "Work in main repository",
|
|
249
|
+
"Validate patches before applying",
|
|
250
|
+
"Clean up worktree when done"
|
|
251
|
+
]
|
|
252
|
+
};
|
|
253
|
+
return JSON.stringify(result, null, 2);
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
return JSON.stringify({
|
|
257
|
+
status: "ERROR",
|
|
258
|
+
message: error instanceof Error ? error.message : `${error}`
|
|
259
|
+
}, null, 2);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}),
|
|
263
|
+
git_worktree_status: tool({
|
|
264
|
+
description: "Discover and report status of all worktrees. Returns detailed information about active worktrees, their branches, and cleanliness.",
|
|
265
|
+
args: {
|
|
266
|
+
include_clean: tool.schema.boolean().optional().default(false).describe("Include clean worktrees in results")
|
|
267
|
+
},
|
|
268
|
+
async execute({ include_clean }) {
|
|
269
|
+
log("Tool call: git_worktree_status", { include_clean });
|
|
270
|
+
try {
|
|
271
|
+
const worktrees = await discoverWorktrees();
|
|
272
|
+
const state = await getGitState();
|
|
273
|
+
let filteredWorktrees = worktrees;
|
|
274
|
+
if (!include_clean) {
|
|
275
|
+
filteredWorktrees = worktrees.filter(w => w.isDirty || w.branch === state.branch);
|
|
276
|
+
}
|
|
277
|
+
const result = {
|
|
278
|
+
status: "OK",
|
|
279
|
+
current_repository: {
|
|
280
|
+
branch: state.branch,
|
|
281
|
+
head: state.head,
|
|
282
|
+
isDirty: state.isDirty
|
|
283
|
+
},
|
|
284
|
+
worktrees: {
|
|
285
|
+
total: worktrees.length,
|
|
286
|
+
filtered: filteredWorktrees.length,
|
|
287
|
+
active: filteredWorktrees.map(w => ({
|
|
288
|
+
name: w.name,
|
|
289
|
+
path: w.path,
|
|
290
|
+
branch: w.branch,
|
|
291
|
+
head: w.head,
|
|
292
|
+
isDirty: w.isDirty,
|
|
293
|
+
status_summary: `${w.isDirty ? 'DIRTY' : 'CLEAN'} - ${w.branch}`
|
|
294
|
+
}))
|
|
295
|
+
},
|
|
296
|
+
recommendations: [
|
|
297
|
+
"Clean up unused worktrees to free resources",
|
|
298
|
+
"Avoid multiple worktrees on the same branch",
|
|
299
|
+
"Use git.checkpoint_create for new clean workspaces"
|
|
300
|
+
]
|
|
301
|
+
};
|
|
302
|
+
return JSON.stringify(result, null, 2);
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
return JSON.stringify({
|
|
306
|
+
status: "ERROR",
|
|
307
|
+
message: error instanceof Error ? error.message : `${error}`
|
|
308
|
+
}, null, 2);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}),
|
|
312
|
+
git_worktree_cleanup: tool({
|
|
313
|
+
description: `Clean up worktrees to free resources. ${AGENT_USAGE_POLICY} Removes specified worktrees after ensuring no uncommitted changes will be lost.`,
|
|
314
|
+
args: {
|
|
315
|
+
worktree_names: tool.schema.array(tool.schema.string()).optional().describe("Specific worktree names to clean up (cleans all if not specified)"),
|
|
316
|
+
force: tool.schema.boolean().optional().default(false).describe("Force cleanup even with uncommitted changes (not recommended)")
|
|
317
|
+
},
|
|
318
|
+
async execute({ worktree_names, force }) {
|
|
319
|
+
log("Tool call: git_worktree_cleanup", { worktree_names, force });
|
|
320
|
+
try {
|
|
321
|
+
const worktrees = await discoverWorktrees();
|
|
322
|
+
let targetWorktrees = worktrees;
|
|
323
|
+
if (worktree_names && worktree_names.length > 0) {
|
|
324
|
+
targetWorktrees = worktrees.filter(w => worktree_names.includes(w.name));
|
|
325
|
+
if (targetWorktrees.length === 0) {
|
|
326
|
+
return JSON.stringify({
|
|
327
|
+
status: "WARNING",
|
|
328
|
+
message: "No matching worktrees found",
|
|
329
|
+
requested: worktree_names,
|
|
330
|
+
available: worktrees.map(w => w.name)
|
|
331
|
+
}, null, 2);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const cleanupResults = [];
|
|
335
|
+
const warnings = [];
|
|
336
|
+
for (const worktree of targetWorktrees) {
|
|
337
|
+
if (worktree.isDirty && !force) {
|
|
338
|
+
warnings.push(`Skipping dirty worktree: ${worktree.name} (${worktree.branch})`);
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
try {
|
|
342
|
+
// Remove worktree
|
|
343
|
+
const { error: removeError } = await runCmd(`git worktree remove ${worktree.path}`);
|
|
344
|
+
if (removeError) {
|
|
345
|
+
warnings.push(`Failed to remove ${worktree.name}: ${removeError instanceof Error ? removeError.message : `${removeError}`}`);
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
// Remove directory if it still exists
|
|
349
|
+
if (fsSync.existsSync(worktree.path)) {
|
|
350
|
+
await fs.rm(worktree.path, { recursive: true, force: true });
|
|
351
|
+
}
|
|
352
|
+
cleanupResults.push({
|
|
353
|
+
name: worktree.name,
|
|
354
|
+
path: worktree.path,
|
|
355
|
+
branch: worktree.branch,
|
|
356
|
+
status: "REMOVED"
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
warnings.push(`Error cleaning up ${worktree.name}: ${error instanceof Error ? error.message : `${error}`}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const result = {
|
|
364
|
+
status: warnings.length > 0 ? "PARTIAL" : "SUCCESS",
|
|
365
|
+
cleaned_up: cleanupResults,
|
|
366
|
+
warnings,
|
|
367
|
+
summary: {
|
|
368
|
+
processed: targetWorktrees.length,
|
|
369
|
+
removed: cleanupResults.length,
|
|
370
|
+
warnings: warnings.length
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
return JSON.stringify(result, null, 2);
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
return JSON.stringify({
|
|
377
|
+
status: "ERROR",
|
|
378
|
+
message: error instanceof Error ? error.message : `${error}`
|
|
379
|
+
}, null, 2);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
})
|
|
383
|
+
};
|
|
384
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
import { systemTools } from "./system-tools.js";
|
|
2
|
+
import { gitWorktreeTools } from "./git-worktree.js";
|
|
3
|
+
import { testingTools } from "./testing-infrastructure.js";
|
|
4
|
+
import { chunkCardsTools } from "./chunk-cards.js";
|
|
5
|
+
import { activeSetTools } from "./activeset.js";
|
|
6
|
+
import { moduleSummariesTools } from "./module-summaries.js";
|
|
7
|
+
import { performanceTools } from "./performance-optimization.js";
|
|
2
8
|
export default function plugin() {
|
|
3
9
|
return {
|
|
4
10
|
tools: {
|
|
5
11
|
...systemTools(),
|
|
12
|
+
...gitWorktreeTools(),
|
|
13
|
+
...testingTools(),
|
|
14
|
+
...chunkCardsTools(),
|
|
15
|
+
...activeSetTools(),
|
|
16
|
+
...moduleSummariesTools(),
|
|
17
|
+
...performanceTools(),
|
|
6
18
|
},
|
|
7
19
|
};
|
|
8
20
|
}
|