@make-u-free/migi 0.5.1 → 0.5.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/bin/migi.js +25 -2
- package/package.json +1 -1
- package/src/agent.js +57 -31
- package/src/context.js +15 -2
package/bin/migi.js
CHANGED
|
@@ -79,6 +79,29 @@ function sepWithLabel(label) {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
// ---- チャット入力(Enter送信 / Shift+Enter改行)----
|
|
82
|
+
// 全角文字(日本語・絵文字など)は端末上で2カラム幅を占める
|
|
83
|
+
// string.length はコードポイント数なので、カーソル位置計算に使うと日本語入力でズレる
|
|
84
|
+
function displayWidth(str) {
|
|
85
|
+
let w = 0
|
|
86
|
+
for (const ch of str) {
|
|
87
|
+
const cp = ch.codePointAt(0)
|
|
88
|
+
const wide =
|
|
89
|
+
(cp >= 0x1100 && cp <= 0x115F) || // Hangul Jamo
|
|
90
|
+
(cp >= 0x2E80 && cp <= 0x303F) || // CJK Radicals
|
|
91
|
+
(cp >= 0x3040 && cp <= 0x33FF) || // Hiragana〜CJK Compat
|
|
92
|
+
(cp >= 0x3400 && cp <= 0x9FFF) || // CJK Unified
|
|
93
|
+
(cp >= 0xAC00 && cp <= 0xD7FF) || // Hangul Syllables
|
|
94
|
+
(cp >= 0xF900 && cp <= 0xFAFF) || // CJK Compat Ideographs
|
|
95
|
+
(cp >= 0xFE10 && cp <= 0xFE1F) || // Vertical Forms
|
|
96
|
+
(cp >= 0xFE30 && cp <= 0xFE6F) || // CJK Compat Forms
|
|
97
|
+
(cp >= 0xFF01 && cp <= 0xFF60) || // Fullwidth ASCII
|
|
98
|
+
(cp >= 0xFFE0 && cp <= 0xFFE6) || // Fullwidth Signs
|
|
99
|
+
(cp >= 0x1F300 && cp <= 0x1FAFF) // Emoji
|
|
100
|
+
w += wide ? 2 : 1
|
|
101
|
+
}
|
|
102
|
+
return w
|
|
103
|
+
}
|
|
104
|
+
|
|
82
105
|
async function readChatInput() {
|
|
83
106
|
return new Promise((resolve) => {
|
|
84
107
|
const PFIRST = ' > '
|
|
@@ -132,9 +155,9 @@ async function readChatInput() {
|
|
|
132
155
|
if (linesFromBottom > 0) buf += `\x1b[${linesFromBottom}A`
|
|
133
156
|
buf += '\r'
|
|
134
157
|
|
|
135
|
-
// ⑤
|
|
158
|
+
// ⑤ カーソルを入力内容の末尾へ(全角文字は2カラム幅なので displayWidth を使う)
|
|
136
159
|
const prefix = curLine === 0 ? PFIRST : PCONT
|
|
137
|
-
buf += `\x1b[${prefix.length + lines[curLine]
|
|
160
|
+
buf += `\x1b[${prefix.length + displayWidth(lines[curLine]) + 1}G`
|
|
138
161
|
|
|
139
162
|
cursorLine = curLine
|
|
140
163
|
process.stdout.write(buf)
|
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import OpenAI from 'openai'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import { homedir } from 'os'
|
|
4
|
-
import { existsSync,
|
|
5
|
-
import { join
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'
|
|
5
|
+
import { join } from 'path'
|
|
6
6
|
import { toolSchemas, teamsToolSchema, executeTool } from './tools.js'
|
|
7
7
|
import { createPermissionChecker } from './permissions.js'
|
|
8
8
|
import { httpsAgent } from './tls.js'
|
|
@@ -57,17 +57,18 @@ ${userNameLine}
|
|
|
57
57
|
- 完了したら1〜2文で報告。途中経過は出さない
|
|
58
58
|
|
|
59
59
|
## メモリと文脈の継続
|
|
60
|
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
60
|
+
ワークスペースメモリは ${cwd}/.migi/memory/ に構造化して保存する:
|
|
61
|
+
- projects.md ── 進行中の仕事・状況
|
|
62
|
+
- feedback.md ── ユーザーの好み・作業スタイル・こだわり
|
|
63
|
+
- next-actions.md ── 次回やること(毎セッション更新)
|
|
64
|
+
|
|
65
|
+
グローバルメモリ: ${homedir()}/.migi/memory.md(横断的なユーザー情報)
|
|
66
|
+
|
|
67
|
+
運用ルール:
|
|
68
|
+
- セッション開始時にメモリを参照し、前回の続きから自然に入る
|
|
69
|
+
- ユーザーが「覚えておいて」と言ったら write_file で即座に該当ファイルを更新
|
|
70
|
+
- 重要な決定・好み・方針転換は言われなくても「記録しておきましょうか?」と提案
|
|
71
|
+
- 過去の記録と矛盾したら「前回と変わりましたか?」と確認する
|
|
71
72
|
|
|
72
73
|
## 環境
|
|
73
74
|
- 今日の日付: ${new Date().toISOString().split('T')[0]}
|
|
@@ -82,9 +83,8 @@ ${userNameLine}
|
|
|
82
83
|
(context ? `\n## ロードされたコンテキスト\n${context}` : '')
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
//
|
|
86
|
+
// セッション終了時にメモリファイルを構造化更新する
|
|
86
87
|
async saveSummary(cwd) {
|
|
87
|
-
// ユーザー発言が2回未満なら保存しない(短すぎるセッション)
|
|
88
88
|
const userTurns = this.history.filter(m => m.role === 'user').length
|
|
89
89
|
if (userTurns < 2) return null
|
|
90
90
|
|
|
@@ -92,6 +92,20 @@ ${userNameLine}
|
|
|
92
92
|
spinner.start('セッションを記録中…')
|
|
93
93
|
|
|
94
94
|
try {
|
|
95
|
+
const memDir = join(cwd, '.migi', 'memory')
|
|
96
|
+
const files = ['projects.md', 'feedback.md', 'next-actions.md']
|
|
97
|
+
|
|
98
|
+
// 既存ファイルの内容を読む
|
|
99
|
+
const current = {}
|
|
100
|
+
for (const f of files) {
|
|
101
|
+
const p = join(memDir, f)
|
|
102
|
+
current[f] = existsSync(p) ? readFileSync(p, 'utf-8').trim() : '(未記録)'
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const currentDump = files
|
|
106
|
+
.map(f => `### ${f}\n${current[f]}`)
|
|
107
|
+
.join('\n\n')
|
|
108
|
+
|
|
95
109
|
const response = await this.client.chat.completions.create({
|
|
96
110
|
model: this.model,
|
|
97
111
|
messages: [
|
|
@@ -99,30 +113,42 @@ ${userNameLine}
|
|
|
99
113
|
...this.history,
|
|
100
114
|
{
|
|
101
115
|
role: 'user',
|
|
102
|
-
content:
|
|
103
|
-
以下の形式で箇条書き3〜6行。日本語で簡潔に(1行50字以内)。
|
|
116
|
+
content: `このセッションの内容を踏まえ、以下のメモリファイルを更新してください。
|
|
104
117
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
- ユーザーについて学んだこと(好み・やり方など)
|
|
108
|
-
- 次回やること(あれば)
|
|
118
|
+
現在の内容:
|
|
119
|
+
${currentDump}
|
|
109
120
|
|
|
110
|
-
|
|
121
|
+
JSON形式のみで返答(他のテキスト不要):
|
|
122
|
+
{
|
|
123
|
+
"projects.md": "進行中の仕事・状況(15行以内)",
|
|
124
|
+
"feedback.md": "ユーザーの好み・作業スタイル・こだわり(15行以内)",
|
|
125
|
+
"next-actions.md": "次回やること(今回判明したもののみ・前回分は消す)"
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
ルール: 新情報は追加、古い情報は上書き、不要なものは削除。変化なければそのまま返す。`
|
|
111
129
|
}
|
|
112
|
-
]
|
|
130
|
+
],
|
|
131
|
+
response_format: { type: 'json_object' }
|
|
113
132
|
})
|
|
114
133
|
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
134
|
+
const updates = JSON.parse(response.choices[0].message.content)
|
|
135
|
+
|
|
136
|
+
if (!existsSync(memDir)) mkdirSync(memDir, { recursive: true })
|
|
137
|
+
|
|
138
|
+
const updated = []
|
|
139
|
+
for (const [filename, content] of Object.entries(updates)) {
|
|
140
|
+
if (files.includes(filename) && content?.trim() && content.trim() !== '(未記録)') {
|
|
141
|
+
writeFileSync(join(memDir, filename), content.trim() + '\n', 'utf-8')
|
|
142
|
+
updated.push(filename)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
118
145
|
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
appendFileSync(memPath, entry, 'utf-8')
|
|
146
|
+
// インデックスを更新
|
|
147
|
+
const indexLines = updated.map(f => `- [memory/${f}](memory/${f})`).join('\n')
|
|
148
|
+
writeFileSync(join(cwd, '.migi', 'memory.md'), `# メモリインデックス\n\n${indexLines}\n`, 'utf-8')
|
|
123
149
|
|
|
124
150
|
spinner.stop()
|
|
125
|
-
return
|
|
151
|
+
return memDir
|
|
126
152
|
} catch (err) {
|
|
127
153
|
spinner.stop()
|
|
128
154
|
return null
|
package/src/context.js
CHANGED
|
@@ -34,8 +34,21 @@ export async function loadContext(cwd = process.cwd()) {
|
|
|
34
34
|
// 1. グローバルメモリ (~/.migi/memory.md)
|
|
35
35
|
load('グローバルメモリ', join(homedir(), '.migi', 'memory.md'))
|
|
36
36
|
|
|
37
|
-
// 2.
|
|
38
|
-
load('ワークスペースメモリ', join(cwd, '.migi', 'memory.md'))
|
|
37
|
+
// 2. ワークスペースメモリ: インデックス + memory/ 以下の個別ファイル
|
|
38
|
+
load('ワークスペースメモリ(インデックス)', join(cwd, '.migi', 'memory.md'))
|
|
39
|
+
const memDir = join(cwd, '.migi', 'memory')
|
|
40
|
+
if (existsSync(memDir)) {
|
|
41
|
+
const memFiles = await glob('*.md', { cwd: memDir })
|
|
42
|
+
// next-actions を最後に(直近の文脈として読ませる)
|
|
43
|
+
memFiles.sort((a, b) => {
|
|
44
|
+
if (a === 'next-actions.md') return 1
|
|
45
|
+
if (b === 'next-actions.md') return -1
|
|
46
|
+
return a.localeCompare(b)
|
|
47
|
+
})
|
|
48
|
+
for (const f of memFiles) {
|
|
49
|
+
load(`メモリ/${f}`, join(memDir, f))
|
|
50
|
+
}
|
|
51
|
+
}
|
|
39
52
|
|
|
40
53
|
// 3. ルートの MIGI.md → CLAUDE.md
|
|
41
54
|
loadWithFallback('', cwd, 'CLAUDE.md')
|