gyoshu 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/AGENTS.md +1039 -0
- package/README.ja.md +390 -0
- package/README.ko.md +385 -0
- package/README.md +459 -0
- package/README.zh.md +383 -0
- package/bin/gyoshu.js +295 -0
- package/install.sh +241 -0
- package/package.json +65 -0
- package/src/agent/baksa.md +494 -0
- package/src/agent/executor.md +1851 -0
- package/src/agent/gyoshu.md +2351 -0
- package/src/agent/jogyo-feedback.md +137 -0
- package/src/agent/jogyo-insight.md +359 -0
- package/src/agent/jogyo-paper-writer.md +370 -0
- package/src/agent/jogyo.md +1445 -0
- package/src/agent/plan-reviewer.md +1862 -0
- package/src/agent/plan.md +97 -0
- package/src/agent/task-orchestrator.md +1121 -0
- package/src/bridge/gyoshu_bridge.py +782 -0
- package/src/command/analyze-knowledge.md +840 -0
- package/src/command/analyze-plans.md +513 -0
- package/src/command/execute.md +893 -0
- package/src/command/generate-policy.md +924 -0
- package/src/command/generate-suggestions.md +1111 -0
- package/src/command/gyoshu-auto.md +258 -0
- package/src/command/gyoshu.md +1352 -0
- package/src/command/learn.md +1181 -0
- package/src/command/planner.md +630 -0
- package/src/lib/artifact-security.ts +159 -0
- package/src/lib/atomic-write.ts +107 -0
- package/src/lib/cell-identity.ts +176 -0
- package/src/lib/checkpoint-schema.ts +455 -0
- package/src/lib/environment-capture.ts +181 -0
- package/src/lib/filesystem-check.ts +84 -0
- package/src/lib/literature-client.ts +1048 -0
- package/src/lib/marker-parser.ts +474 -0
- package/src/lib/notebook-frontmatter.ts +835 -0
- package/src/lib/paths.ts +799 -0
- package/src/lib/pdf-export.ts +340 -0
- package/src/lib/quality-gates.ts +369 -0
- package/src/lib/readme-index.ts +462 -0
- package/src/lib/report-markdown.ts +870 -0
- package/src/lib/session-lock.ts +411 -0
- package/src/plugin/gyoshu-hooks.ts +140 -0
- package/src/skill/data-analysis/SKILL.md +369 -0
- package/src/skill/experiment-design/SKILL.md +374 -0
- package/src/skill/ml-rigor/SKILL.md +672 -0
- package/src/skill/scientific-method/SKILL.md +331 -0
- package/src/tool/checkpoint-manager.ts +1387 -0
- package/src/tool/gyoshu-completion.ts +493 -0
- package/src/tool/gyoshu-snapshot.ts +745 -0
- package/src/tool/literature-search.ts +389 -0
- package/src/tool/migration-tool.ts +1404 -0
- package/src/tool/notebook-search.ts +794 -0
- package/src/tool/notebook-writer.ts +391 -0
- package/src/tool/python-repl.ts +1038 -0
- package/src/tool/research-manager.ts +1494 -0
- package/src/tool/retrospective-store.ts +347 -0
- package/src/tool/session-manager.ts +565 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrospective Store Tool - Manages feedback storage and retrieval for cross-session learning.
|
|
3
|
+
*
|
|
4
|
+
* Storage: {project}/.gyoshu/retrospectives/feedback.jsonl
|
|
5
|
+
* Actions: append, list, query, top, stats
|
|
6
|
+
*
|
|
7
|
+
* @module retrospective-store
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { tool } from "@opencode-ai/plugin";
|
|
11
|
+
import * as fs from "fs";
|
|
12
|
+
import * as path from "path";
|
|
13
|
+
import * as crypto from "crypto";
|
|
14
|
+
import {
|
|
15
|
+
getRetrospectivesDir,
|
|
16
|
+
getRetrospectivesFeedbackPath,
|
|
17
|
+
ensureDirSync,
|
|
18
|
+
} from "../lib/paths";
|
|
19
|
+
|
|
20
|
+
interface RetrospectiveFeedback {
|
|
21
|
+
id: string;
|
|
22
|
+
timestamp: string;
|
|
23
|
+
task_context: string;
|
|
24
|
+
observation: string;
|
|
25
|
+
learning: string;
|
|
26
|
+
recommendation: string;
|
|
27
|
+
impact_score: number;
|
|
28
|
+
tags: string[];
|
|
29
|
+
source_session_id?: string;
|
|
30
|
+
run_id?: string;
|
|
31
|
+
dedupe_key: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface StoreIndex {
|
|
35
|
+
lastUpdated: string;
|
|
36
|
+
count: number;
|
|
37
|
+
tagHistogram: Record<string, number>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getRetroDir(): string {
|
|
41
|
+
return getRetrospectivesDir();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getFeedbackFile(): string {
|
|
45
|
+
return getRetrospectivesFeedbackPath();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getIndexPath(): string {
|
|
49
|
+
return path.join(getRetroDir(), "index.json");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function ensureRetroDir(): void {
|
|
53
|
+
ensureDirSync(getRetroDir());
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function generateDedupeKey(taskContext: string, learning: string): string {
|
|
57
|
+
const content = `${taskContext}:${learning}`;
|
|
58
|
+
return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function generateId(): string {
|
|
62
|
+
return `fb_${crypto.randomUUID().slice(0, 8)}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function loadAllFeedback(): RetrospectiveFeedback[] {
|
|
66
|
+
const filePath = getFeedbackFile();
|
|
67
|
+
if (!fs.existsSync(filePath)) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const lines = fs.readFileSync(filePath, "utf-8").split("\n").filter(l => l.trim());
|
|
72
|
+
const feedback: RetrospectiveFeedback[] = [];
|
|
73
|
+
|
|
74
|
+
for (const line of lines) {
|
|
75
|
+
try {
|
|
76
|
+
feedback.push(JSON.parse(line));
|
|
77
|
+
} catch {
|
|
78
|
+
// Skip malformed lines
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return feedback;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function appendFeedback(feedback: RetrospectiveFeedback): void {
|
|
86
|
+
ensureRetroDir();
|
|
87
|
+
const filePath = getFeedbackFile();
|
|
88
|
+
fs.appendFileSync(filePath, JSON.stringify(feedback) + "\n");
|
|
89
|
+
updateIndex(feedback);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function loadIndex(): StoreIndex {
|
|
93
|
+
const indexPath = getIndexPath();
|
|
94
|
+
if (!fs.existsSync(indexPath)) {
|
|
95
|
+
return {
|
|
96
|
+
lastUpdated: new Date().toISOString(),
|
|
97
|
+
count: 0,
|
|
98
|
+
tagHistogram: {},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
return JSON.parse(fs.readFileSync(indexPath, "utf-8"));
|
|
104
|
+
} catch {
|
|
105
|
+
return {
|
|
106
|
+
lastUpdated: new Date().toISOString(),
|
|
107
|
+
count: 0,
|
|
108
|
+
tagHistogram: {},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function saveIndex(index: StoreIndex): void {
|
|
114
|
+
ensureRetroDir();
|
|
115
|
+
fs.writeFileSync(getIndexPath(), JSON.stringify(index, null, 2));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function updateIndex(feedback: RetrospectiveFeedback): void {
|
|
119
|
+
const index = loadIndex();
|
|
120
|
+
index.lastUpdated = new Date().toISOString();
|
|
121
|
+
index.count += 1;
|
|
122
|
+
|
|
123
|
+
for (const tag of feedback.tags) {
|
|
124
|
+
index.tagHistogram[tag] = (index.tagHistogram[tag] || 0) + 1;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
saveIndex(index);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function calculateRecencyWeight(timestamp: string): number {
|
|
131
|
+
const now = Date.now();
|
|
132
|
+
const feedbackTime = new Date(timestamp).getTime();
|
|
133
|
+
const daysSince = (now - feedbackTime) / (1000 * 60 * 60 * 24);
|
|
134
|
+
return Math.max(0, 1 - daysSince / 30);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function calculateScore(feedback: RetrospectiveFeedback): number {
|
|
138
|
+
const recency = calculateRecencyWeight(feedback.timestamp);
|
|
139
|
+
return feedback.impact_score * 0.7 + recency * 0.3;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function matchesQuery(feedback: RetrospectiveFeedback, query: string): boolean {
|
|
143
|
+
const lowerQuery = query.toLowerCase();
|
|
144
|
+
return (
|
|
145
|
+
feedback.task_context.toLowerCase().includes(lowerQuery) ||
|
|
146
|
+
feedback.observation.toLowerCase().includes(lowerQuery) ||
|
|
147
|
+
feedback.learning.toLowerCase().includes(lowerQuery) ||
|
|
148
|
+
feedback.recommendation.toLowerCase().includes(lowerQuery) ||
|
|
149
|
+
feedback.tags.some(t => t.toLowerCase().includes(lowerQuery))
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export default tool({
|
|
154
|
+
description:
|
|
155
|
+
"Manage retrospective feedback for cross-session learning. " +
|
|
156
|
+
"Actions: append (add feedback), list (get recent), query (search), top (ranked), stats (counts).",
|
|
157
|
+
|
|
158
|
+
args: {
|
|
159
|
+
action: tool.schema
|
|
160
|
+
.enum(["append", "list", "query", "top", "stats"])
|
|
161
|
+
.describe(
|
|
162
|
+
"append: Add new feedback, " +
|
|
163
|
+
"list: Get recent feedback, " +
|
|
164
|
+
"query: Search feedback by text, " +
|
|
165
|
+
"top: Get top-ranked feedback, " +
|
|
166
|
+
"stats: Get storage statistics"
|
|
167
|
+
),
|
|
168
|
+
feedback: tool.schema
|
|
169
|
+
.any()
|
|
170
|
+
.optional()
|
|
171
|
+
.describe(
|
|
172
|
+
"Feedback record for append action. Object with: " +
|
|
173
|
+
"task_context (required), observation (required), learning (required), " +
|
|
174
|
+
"recommendation (required), impact_score (0-1), tags (array), " +
|
|
175
|
+
"source_session_id, run_id"
|
|
176
|
+
),
|
|
177
|
+
query: tool.schema
|
|
178
|
+
.string()
|
|
179
|
+
.optional()
|
|
180
|
+
.describe("Search text for query action"),
|
|
181
|
+
limit: tool.schema
|
|
182
|
+
.number()
|
|
183
|
+
.optional()
|
|
184
|
+
.describe("Maximum results to return (default: 10)"),
|
|
185
|
+
tags: tool.schema
|
|
186
|
+
.array(tool.schema.string())
|
|
187
|
+
.optional()
|
|
188
|
+
.describe("Filter by tags"),
|
|
189
|
+
since: tool.schema
|
|
190
|
+
.string()
|
|
191
|
+
.optional()
|
|
192
|
+
.describe("ISO timestamp to filter from"),
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
async execute(args) {
|
|
196
|
+
const { action, limit = 10 } = args;
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
switch (action) {
|
|
200
|
+
case "append": {
|
|
201
|
+
if (!args.feedback) {
|
|
202
|
+
return JSON.stringify({
|
|
203
|
+
success: false,
|
|
204
|
+
error: "feedback object required for append action",
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const feedback: RetrospectiveFeedback = {
|
|
209
|
+
id: generateId(),
|
|
210
|
+
timestamp: new Date().toISOString(),
|
|
211
|
+
task_context: args.feedback.task_context,
|
|
212
|
+
observation: args.feedback.observation,
|
|
213
|
+
learning: args.feedback.learning,
|
|
214
|
+
recommendation: args.feedback.recommendation,
|
|
215
|
+
impact_score: Math.max(0, Math.min(1, args.feedback.impact_score ?? 0.5)),
|
|
216
|
+
tags: args.feedback.tags ?? [],
|
|
217
|
+
source_session_id: args.feedback.source_session_id,
|
|
218
|
+
run_id: args.feedback.run_id,
|
|
219
|
+
dedupe_key: generateDedupeKey(
|
|
220
|
+
args.feedback.task_context,
|
|
221
|
+
args.feedback.learning
|
|
222
|
+
),
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
appendFeedback(feedback);
|
|
226
|
+
|
|
227
|
+
return JSON.stringify({
|
|
228
|
+
success: true,
|
|
229
|
+
feedback_id: feedback.id,
|
|
230
|
+
dedupe_key: feedback.dedupe_key,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
case "list": {
|
|
235
|
+
let allFeedback = loadAllFeedback();
|
|
236
|
+
|
|
237
|
+
if (args.since) {
|
|
238
|
+
const sinceTime = new Date(args.since).getTime();
|
|
239
|
+
allFeedback = allFeedback.filter(
|
|
240
|
+
f => new Date(f.timestamp).getTime() >= sinceTime
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (args.tags?.length) {
|
|
245
|
+
allFeedback = allFeedback.filter(f =>
|
|
246
|
+
args.tags!.some(t => f.tags.includes(t))
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
allFeedback.sort(
|
|
251
|
+
(a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
return JSON.stringify({
|
|
255
|
+
success: true,
|
|
256
|
+
count: Math.min(limit, allFeedback.length),
|
|
257
|
+
total: allFeedback.length,
|
|
258
|
+
feedback: allFeedback.slice(0, limit),
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
case "query": {
|
|
263
|
+
if (!args.query) {
|
|
264
|
+
return JSON.stringify({
|
|
265
|
+
success: false,
|
|
266
|
+
error: "query string required for query action",
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let allFeedback = loadAllFeedback();
|
|
271
|
+
const matches = allFeedback.filter(f => matchesQuery(f, args.query!));
|
|
272
|
+
matches.sort((a, b) => calculateScore(b) - calculateScore(a));
|
|
273
|
+
|
|
274
|
+
return JSON.stringify({
|
|
275
|
+
success: true,
|
|
276
|
+
query: args.query,
|
|
277
|
+
count: Math.min(limit, matches.length),
|
|
278
|
+
total_matches: matches.length,
|
|
279
|
+
feedback: matches.slice(0, limit),
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
case "top": {
|
|
284
|
+
let allFeedback = loadAllFeedback();
|
|
285
|
+
|
|
286
|
+
if (args.tags?.length) {
|
|
287
|
+
allFeedback = allFeedback.filter(f =>
|
|
288
|
+
args.tags!.some(t => f.tags.includes(t))
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const scored = allFeedback.map(f => ({
|
|
293
|
+
feedback: f,
|
|
294
|
+
score: calculateScore(f),
|
|
295
|
+
}));
|
|
296
|
+
|
|
297
|
+
scored.sort((a, b) => b.score - a.score);
|
|
298
|
+
|
|
299
|
+
const seenKeys = new Set<string>();
|
|
300
|
+
const dedupedTop: Array<{ feedback: RetrospectiveFeedback; score: number }> = [];
|
|
301
|
+
|
|
302
|
+
for (const item of scored) {
|
|
303
|
+
if (!seenKeys.has(item.feedback.dedupe_key)) {
|
|
304
|
+
seenKeys.add(item.feedback.dedupe_key);
|
|
305
|
+
dedupedTop.push(item);
|
|
306
|
+
if (dedupedTop.length >= limit) break;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return JSON.stringify({
|
|
311
|
+
success: true,
|
|
312
|
+
count: dedupedTop.length,
|
|
313
|
+
feedback: dedupedTop.map(item => ({
|
|
314
|
+
...item.feedback,
|
|
315
|
+
_score: Math.round(item.score * 100) / 100,
|
|
316
|
+
})),
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
case "stats": {
|
|
321
|
+
const index = loadIndex();
|
|
322
|
+
const topTags = Object.entries(index.tagHistogram)
|
|
323
|
+
.sort((a, b) => b[1] - a[1])
|
|
324
|
+
.slice(0, 10);
|
|
325
|
+
|
|
326
|
+
return JSON.stringify({
|
|
327
|
+
success: true,
|
|
328
|
+
last_updated: index.lastUpdated,
|
|
329
|
+
total_feedback: index.count,
|
|
330
|
+
top_tags: Object.fromEntries(topTags),
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
default:
|
|
335
|
+
return JSON.stringify({
|
|
336
|
+
success: false,
|
|
337
|
+
error: `Unknown action: ${action}`,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
} catch (e) {
|
|
341
|
+
return JSON.stringify({
|
|
342
|
+
success: false,
|
|
343
|
+
error: (e as Error).message,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
});
|