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.
Files changed (59) hide show
  1. package/AGENTS.md +1039 -0
  2. package/README.ja.md +390 -0
  3. package/README.ko.md +385 -0
  4. package/README.md +459 -0
  5. package/README.zh.md +383 -0
  6. package/bin/gyoshu.js +295 -0
  7. package/install.sh +241 -0
  8. package/package.json +65 -0
  9. package/src/agent/baksa.md +494 -0
  10. package/src/agent/executor.md +1851 -0
  11. package/src/agent/gyoshu.md +2351 -0
  12. package/src/agent/jogyo-feedback.md +137 -0
  13. package/src/agent/jogyo-insight.md +359 -0
  14. package/src/agent/jogyo-paper-writer.md +370 -0
  15. package/src/agent/jogyo.md +1445 -0
  16. package/src/agent/plan-reviewer.md +1862 -0
  17. package/src/agent/plan.md +97 -0
  18. package/src/agent/task-orchestrator.md +1121 -0
  19. package/src/bridge/gyoshu_bridge.py +782 -0
  20. package/src/command/analyze-knowledge.md +840 -0
  21. package/src/command/analyze-plans.md +513 -0
  22. package/src/command/execute.md +893 -0
  23. package/src/command/generate-policy.md +924 -0
  24. package/src/command/generate-suggestions.md +1111 -0
  25. package/src/command/gyoshu-auto.md +258 -0
  26. package/src/command/gyoshu.md +1352 -0
  27. package/src/command/learn.md +1181 -0
  28. package/src/command/planner.md +630 -0
  29. package/src/lib/artifact-security.ts +159 -0
  30. package/src/lib/atomic-write.ts +107 -0
  31. package/src/lib/cell-identity.ts +176 -0
  32. package/src/lib/checkpoint-schema.ts +455 -0
  33. package/src/lib/environment-capture.ts +181 -0
  34. package/src/lib/filesystem-check.ts +84 -0
  35. package/src/lib/literature-client.ts +1048 -0
  36. package/src/lib/marker-parser.ts +474 -0
  37. package/src/lib/notebook-frontmatter.ts +835 -0
  38. package/src/lib/paths.ts +799 -0
  39. package/src/lib/pdf-export.ts +340 -0
  40. package/src/lib/quality-gates.ts +369 -0
  41. package/src/lib/readme-index.ts +462 -0
  42. package/src/lib/report-markdown.ts +870 -0
  43. package/src/lib/session-lock.ts +411 -0
  44. package/src/plugin/gyoshu-hooks.ts +140 -0
  45. package/src/skill/data-analysis/SKILL.md +369 -0
  46. package/src/skill/experiment-design/SKILL.md +374 -0
  47. package/src/skill/ml-rigor/SKILL.md +672 -0
  48. package/src/skill/scientific-method/SKILL.md +331 -0
  49. package/src/tool/checkpoint-manager.ts +1387 -0
  50. package/src/tool/gyoshu-completion.ts +493 -0
  51. package/src/tool/gyoshu-snapshot.ts +745 -0
  52. package/src/tool/literature-search.ts +389 -0
  53. package/src/tool/migration-tool.ts +1404 -0
  54. package/src/tool/notebook-search.ts +794 -0
  55. package/src/tool/notebook-writer.ts +391 -0
  56. package/src/tool/python-repl.ts +1038 -0
  57. package/src/tool/research-manager.ts +1494 -0
  58. package/src/tool/retrospective-store.ts +347 -0
  59. 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
+ });