@codehourra/llm-iwiki 0.1.0 → 0.1.1
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/dist/index.js +514 -0
- package/package.json +7 -8
- package/src/ai-yaml.ts +0 -82
- package/src/cli.ts +0 -485
- package/src/collectors/claude-code.ts +0 -120
- package/src/collectors/codebuddy.ts +0 -164
- package/src/collectors/codex.ts +0 -130
- package/src/collectors/cursor.ts +0 -178
- package/src/collectors/gemini.ts +0 -141
- package/src/collectors/index.ts +0 -20
- package/src/collectors/types.ts +0 -22
- package/src/collectors/util.ts +0 -58
- package/src/compaction.ts +0 -63
- package/src/config.ts +0 -57
- package/src/db.ts +0 -152
- package/src/experiences.ts +0 -235
- package/src/index.ts +0 -10
- package/src/obsidian.ts +0 -345
- package/src/paths.ts +0 -23
- package/src/projects.ts +0 -192
- package/src/sessions.ts +0 -119
- package/src/skills.ts +0 -168
- package/src/summarize.ts +0 -122
- package/src/sync.ts +0 -207
- package/src/types.ts +0 -26
package/src/projects.ts
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process'
|
|
2
|
-
import { existsSync } from 'node:fs'
|
|
3
|
-
import { basename, resolve } from 'node:path'
|
|
4
|
-
|
|
5
|
-
import type { LlmIwikiDatabase } from './db'
|
|
6
|
-
|
|
7
|
-
export interface ProjectRecord {
|
|
8
|
-
id: string
|
|
9
|
-
canonicalName: string
|
|
10
|
-
displayName: string | null
|
|
11
|
-
slug: string
|
|
12
|
-
canonicalRepoUrl: string | null
|
|
13
|
-
identitySource: string
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function canonicalizeRemoteUrl(remoteUrl: string): string {
|
|
17
|
-
return remoteUrl
|
|
18
|
-
.trim()
|
|
19
|
-
.replace(/\/+$/, '')
|
|
20
|
-
.replace(/^ssh:\/\/git@([^/]+)\//, '$1/')
|
|
21
|
-
.replace(/^git@([^:]+):/, '$1/')
|
|
22
|
-
.replace(/^https?:\/\//, '')
|
|
23
|
-
.replace(/\.git$/, '')
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function slugifyProjectName(name: string): string {
|
|
27
|
-
const ascii = name
|
|
28
|
-
.normalize('NFKD')
|
|
29
|
-
.replace(/[^\w\s./-]/g, '')
|
|
30
|
-
.trim()
|
|
31
|
-
.toLowerCase()
|
|
32
|
-
.replace(/[\s._/]+/g, '-')
|
|
33
|
-
.replace(/-+/g, '-')
|
|
34
|
-
.replace(/^-|-$/g, '')
|
|
35
|
-
|
|
36
|
-
return ascii || 'project'
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function git(cwd: string, args: string[]): string | null {
|
|
40
|
-
const result = spawnSync('git', args, { cwd, encoding: 'utf8' })
|
|
41
|
-
if (result.status !== 0) return null
|
|
42
|
-
return result.stdout.trim() || null
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface ProjectIdentity {
|
|
46
|
-
id: string
|
|
47
|
-
canonicalName: string
|
|
48
|
-
slug: string
|
|
49
|
-
canonicalRepoUrl: string | null
|
|
50
|
-
identitySource: string
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function computeProjectIdentity(localPath: string): ProjectIdentity {
|
|
54
|
-
const gitRoot = git(localPath, ['rev-parse', '--show-toplevel'])
|
|
55
|
-
const remote = git(localPath, ['config', '--get', 'remote.origin.url'])
|
|
56
|
-
const canonicalRepoUrl = remote ? canonicalizeRemoteUrl(remote) : null
|
|
57
|
-
const canonicalName = canonicalRepoUrl ?? basename(gitRoot ?? localPath)
|
|
58
|
-
const slug = slugifyProjectName(canonicalName)
|
|
59
|
-
const id = `proj_${Bun.hash(canonicalRepoUrl ?? gitRoot ?? localPath).toString(16)}`
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
id,
|
|
63
|
-
canonicalName,
|
|
64
|
-
slug,
|
|
65
|
-
canonicalRepoUrl,
|
|
66
|
-
identitySource: canonicalRepoUrl ? 'git_remote' : 'path',
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function upsertProjectIdentity(db: LlmIwikiDatabase, identity: ProjectIdentity): ProjectRecord {
|
|
71
|
-
const now = new Date().toISOString()
|
|
72
|
-
db.query(`
|
|
73
|
-
INSERT INTO projects (id, canonical_name, display_name, slug, canonical_repo_url, provider, identity_source, created_at, updated_at)
|
|
74
|
-
VALUES ($id, $canonicalName, NULL, $slug, $canonicalRepoUrl, NULL, $identitySource, $now, $now)
|
|
75
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
76
|
-
canonical_name = excluded.canonical_name,
|
|
77
|
-
slug = excluded.slug,
|
|
78
|
-
canonical_repo_url = excluded.canonical_repo_url,
|
|
79
|
-
identity_source = excluded.identity_source,
|
|
80
|
-
updated_at = excluded.updated_at
|
|
81
|
-
`).run({
|
|
82
|
-
$id: identity.id,
|
|
83
|
-
$canonicalName: identity.canonicalName,
|
|
84
|
-
$slug: identity.slug,
|
|
85
|
-
$canonicalRepoUrl: identity.canonicalRepoUrl,
|
|
86
|
-
$identitySource: identity.identitySource,
|
|
87
|
-
$now: now,
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
return getProject(db, identity.id)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function resolveProject(db: LlmIwikiDatabase, checkoutPath: string): ProjectRecord {
|
|
94
|
-
const localPath = resolve(checkoutPath)
|
|
95
|
-
if (!existsSync(localPath)) throw new Error(`Path does not exist: ${localPath}`)
|
|
96
|
-
return upsertProjectIdentity(db, computeProjectIdentity(localPath))
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function resolveProjectByPath(db: LlmIwikiDatabase, rawPath: string): ProjectRecord {
|
|
100
|
-
const localPath = resolve(rawPath)
|
|
101
|
-
return upsertProjectIdentity(db, computeProjectIdentity(localPath))
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function renameProject(db: LlmIwikiDatabase, projectId: string, displayName: string): ProjectRecord {
|
|
105
|
-
const now = new Date().toISOString()
|
|
106
|
-
const changes = db
|
|
107
|
-
.query('UPDATE projects SET display_name = $displayName, updated_at = $now WHERE id = $projectId')
|
|
108
|
-
.run({
|
|
109
|
-
$displayName: displayName,
|
|
110
|
-
$projectId: projectId,
|
|
111
|
-
$now: now,
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
if (changes.changes === 0) throw new Error(`Project not found: ${projectId}`)
|
|
115
|
-
|
|
116
|
-
return getProject(db, projectId)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export interface ProjectSummaryRow extends ProjectRecord {
|
|
120
|
-
sessionCount: number
|
|
121
|
-
lastSeenAt: string | null
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function listProjects(db: LlmIwikiDatabase): ProjectSummaryRow[] {
|
|
125
|
-
const rows = db
|
|
126
|
-
.query<
|
|
127
|
-
{
|
|
128
|
-
id: string
|
|
129
|
-
canonical_name: string
|
|
130
|
-
display_name: string | null
|
|
131
|
-
slug: string
|
|
132
|
-
canonical_repo_url: string | null
|
|
133
|
-
identity_source: string
|
|
134
|
-
session_count: number
|
|
135
|
-
last_seen_at: string | null
|
|
136
|
-
},
|
|
137
|
-
[]
|
|
138
|
-
>(`
|
|
139
|
-
SELECT
|
|
140
|
-
p.id,
|
|
141
|
-
p.canonical_name,
|
|
142
|
-
p.display_name,
|
|
143
|
-
p.slug,
|
|
144
|
-
p.canonical_repo_url,
|
|
145
|
-
p.identity_source,
|
|
146
|
-
COUNT(s.id) AS session_count,
|
|
147
|
-
MAX(s.last_seen_at) AS last_seen_at
|
|
148
|
-
FROM projects p
|
|
149
|
-
LEFT JOIN sessions s ON s.project_id = p.id
|
|
150
|
-
GROUP BY p.id
|
|
151
|
-
ORDER BY session_count DESC, p.canonical_name ASC
|
|
152
|
-
`)
|
|
153
|
-
.all()
|
|
154
|
-
|
|
155
|
-
return rows.map((row) => ({
|
|
156
|
-
id: row.id,
|
|
157
|
-
canonicalName: row.canonical_name,
|
|
158
|
-
displayName: row.display_name,
|
|
159
|
-
slug: row.slug,
|
|
160
|
-
canonicalRepoUrl: row.canonical_repo_url,
|
|
161
|
-
identitySource: row.identity_source,
|
|
162
|
-
sessionCount: row.session_count,
|
|
163
|
-
lastSeenAt: row.last_seen_at,
|
|
164
|
-
}))
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
export function getProject(db: LlmIwikiDatabase, projectId: string): ProjectRecord {
|
|
168
|
-
const row = db
|
|
169
|
-
.query<
|
|
170
|
-
{
|
|
171
|
-
id: string
|
|
172
|
-
canonical_name: string
|
|
173
|
-
display_name: string | null
|
|
174
|
-
slug: string
|
|
175
|
-
canonical_repo_url: string | null
|
|
176
|
-
identity_source: string
|
|
177
|
-
},
|
|
178
|
-
[string]
|
|
179
|
-
>('SELECT * FROM projects WHERE id = ?')
|
|
180
|
-
.get(projectId)
|
|
181
|
-
|
|
182
|
-
if (!row) throw new Error(`Project not found: ${projectId}`)
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
id: row.id,
|
|
186
|
-
canonicalName: row.canonical_name,
|
|
187
|
-
displayName: row.display_name,
|
|
188
|
-
slug: row.slug,
|
|
189
|
-
canonicalRepoUrl: row.canonical_repo_url,
|
|
190
|
-
identitySource: row.identity_source,
|
|
191
|
-
}
|
|
192
|
-
}
|
package/src/sessions.ts
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import type { LlmIwikiDatabase } from './db'
|
|
2
|
-
|
|
3
|
-
export interface SessionRow {
|
|
4
|
-
id: string
|
|
5
|
-
sourceId: string
|
|
6
|
-
sourceSessionId: string
|
|
7
|
-
title: string | null
|
|
8
|
-
messageCount: number
|
|
9
|
-
status: string
|
|
10
|
-
rawProjectPath: string | null
|
|
11
|
-
updatedAt: string | null
|
|
12
|
-
lastSeenAt: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface SourceBreakdown {
|
|
16
|
-
source: string
|
|
17
|
-
sessionCount: number
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface ProjectInspection {
|
|
21
|
-
sources: SourceBreakdown[]
|
|
22
|
-
sessions: SessionRow[]
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function mapRow(row: {
|
|
26
|
-
id: string
|
|
27
|
-
source_id: string
|
|
28
|
-
source_session_id: string
|
|
29
|
-
title: string | null
|
|
30
|
-
message_count: number
|
|
31
|
-
status: string
|
|
32
|
-
raw_project_path: string | null
|
|
33
|
-
updated_at: string | null
|
|
34
|
-
last_seen_at: string
|
|
35
|
-
}): SessionRow {
|
|
36
|
-
return {
|
|
37
|
-
id: row.id,
|
|
38
|
-
sourceId: row.source_id,
|
|
39
|
-
sourceSessionId: row.source_session_id,
|
|
40
|
-
title: row.title,
|
|
41
|
-
messageCount: row.message_count,
|
|
42
|
-
status: row.status,
|
|
43
|
-
rawProjectPath: row.raw_project_path,
|
|
44
|
-
updatedAt: row.updated_at,
|
|
45
|
-
lastSeenAt: row.last_seen_at,
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function listSessionsByProject(db: LlmIwikiDatabase, projectId: string, limit = 100): SessionRow[] {
|
|
50
|
-
return db
|
|
51
|
-
.query<Parameters<typeof mapRow>[0], [string, number]>(`
|
|
52
|
-
SELECT id, source_id, source_session_id, title, message_count, status, raw_project_path, updated_at, last_seen_at
|
|
53
|
-
FROM sessions
|
|
54
|
-
WHERE project_id = ?
|
|
55
|
-
ORDER BY COALESCE(updated_at, last_seen_at) DESC
|
|
56
|
-
LIMIT ?
|
|
57
|
-
`)
|
|
58
|
-
.all(projectId, limit)
|
|
59
|
-
.map(mapRow)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export interface StoredMessage {
|
|
63
|
-
role: string
|
|
64
|
-
content: string
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function getSessionMessages(db: LlmIwikiDatabase, sessionId: string): StoredMessage[] {
|
|
68
|
-
return db
|
|
69
|
-
.query<{ role: string; content: string }, [string]>(
|
|
70
|
-
'SELECT role, content FROM messages WHERE session_id = ? ORDER BY seq_order ASC',
|
|
71
|
-
)
|
|
72
|
-
.all(sessionId)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function getSession(db: LlmIwikiDatabase, sessionId: string): SessionRow | null {
|
|
76
|
-
const row = db
|
|
77
|
-
.query<Parameters<typeof mapRow>[0], [string]>(`
|
|
78
|
-
SELECT id, source_id, source_session_id, title, message_count, status, raw_project_path, updated_at, last_seen_at
|
|
79
|
-
FROM sessions
|
|
80
|
-
WHERE id = ?
|
|
81
|
-
`)
|
|
82
|
-
.get(sessionId)
|
|
83
|
-
return row ? mapRow(row) : null
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function listSessionsToSummarize(
|
|
87
|
-
db: LlmIwikiDatabase,
|
|
88
|
-
projectId: string,
|
|
89
|
-
scope: 'changed' | 'all',
|
|
90
|
-
): SessionRow[] {
|
|
91
|
-
const statusClause = scope === 'changed' ? "AND status IN ('new', 'changed')" : ''
|
|
92
|
-
return db
|
|
93
|
-
.query<Parameters<typeof mapRow>[0], [string]>(`
|
|
94
|
-
SELECT id, source_id, source_session_id, title, message_count, status, raw_project_path, updated_at, last_seen_at
|
|
95
|
-
FROM sessions
|
|
96
|
-
WHERE project_id = ? ${statusClause}
|
|
97
|
-
ORDER BY COALESCE(updated_at, last_seen_at) DESC
|
|
98
|
-
`)
|
|
99
|
-
.all(projectId)
|
|
100
|
-
.map(mapRow)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function inspectProject(db: LlmIwikiDatabase, projectId: string): ProjectInspection {
|
|
104
|
-
const sources = db
|
|
105
|
-
.query<{ source_id: string; session_count: number }, [string]>(`
|
|
106
|
-
SELECT source_id, COUNT(*) AS session_count
|
|
107
|
-
FROM sessions
|
|
108
|
-
WHERE project_id = ?
|
|
109
|
-
GROUP BY source_id
|
|
110
|
-
ORDER BY session_count DESC
|
|
111
|
-
`)
|
|
112
|
-
.all(projectId)
|
|
113
|
-
.map((row) => ({ source: row.source_id, sessionCount: row.session_count }))
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
sources,
|
|
117
|
-
sessions: listSessionsByProject(db, projectId),
|
|
118
|
-
}
|
|
119
|
-
}
|
package/src/skills.ts
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
2
|
-
import { dirname, join } from 'node:path'
|
|
3
|
-
|
|
4
|
-
export type SkillTarget = 'codex' | 'claude-code' | 'cursor'
|
|
5
|
-
|
|
6
|
-
export const SKILL_TARGETS = ['codex', 'claude-code', 'cursor'] as const satisfies readonly SkillTarget[]
|
|
7
|
-
|
|
8
|
-
export interface InitSkillsOptions {
|
|
9
|
-
cwd: string
|
|
10
|
-
target: SkillTarget | null
|
|
11
|
-
force: boolean
|
|
12
|
-
dryRun: boolean
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface InitSkillsResult {
|
|
16
|
-
written: string[]
|
|
17
|
-
skipped: string[]
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface SkillTemplate {
|
|
21
|
-
directory: string
|
|
22
|
-
content: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const TARGET_NAMES: Record<SkillTarget, string> = {
|
|
26
|
-
codex: 'Codex',
|
|
27
|
-
'claude-code': 'Claude Code',
|
|
28
|
-
cursor: 'Cursor',
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const SKILL_TEMPLATES: SkillTemplate[] = [
|
|
32
|
-
{
|
|
33
|
-
directory: 'aiwiki-after-session',
|
|
34
|
-
content: `---
|
|
35
|
-
name: aiwiki-after-session
|
|
36
|
-
description: Capture session knowledge into llm-iwiki summaries, experiences, and Obsidian export after project work.
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
# AIWiki After Session
|
|
40
|
-
|
|
41
|
-
Use this skill after a meaningful coding or design session in the current project.
|
|
42
|
-
|
|
43
|
-
## Steps
|
|
44
|
-
|
|
45
|
-
1. Sync the current project:
|
|
46
|
-
\`\`\`bash
|
|
47
|
-
llm-iwiki sync --project .
|
|
48
|
-
\`\`\`
|
|
49
|
-
2. Prepare changed summaries:
|
|
50
|
-
\`\`\`bash
|
|
51
|
-
llm-iwiki summarize prepare changed --project . --out .llm-iwiki/tasks/summaries-task.md
|
|
52
|
-
\`\`\`
|
|
53
|
-
3. Generate \`.llm-iwiki/tasks/summaries.yaml\` from the prepared task.
|
|
54
|
-
4. Apply the summaries:
|
|
55
|
-
\`\`\`bash
|
|
56
|
-
llm-iwiki summarize apply --project . --file .llm-iwiki/tasks/summaries.yaml
|
|
57
|
-
\`\`\`
|
|
58
|
-
5. Prepare experience proposals from changed summaries:
|
|
59
|
-
\`\`\`bash
|
|
60
|
-
llm-iwiki experiences prepare --project . --from changed-summaries --out .llm-iwiki/tasks/experiences-task.md
|
|
61
|
-
\`\`\`
|
|
62
|
-
6. Generate \`.llm-iwiki/tasks/experiences.yaml\` from the prepared task.
|
|
63
|
-
7. Propose the experiences:
|
|
64
|
-
\`\`\`bash
|
|
65
|
-
llm-iwiki experiences propose --project . --file .llm-iwiki/tasks/experiences.yaml
|
|
66
|
-
\`\`\`
|
|
67
|
-
8. Export project knowledge to Obsidian:
|
|
68
|
-
\`\`\`bash
|
|
69
|
-
llm-iwiki obsidian export --project .
|
|
70
|
-
\`\`\`
|
|
71
|
-
`,
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
directory: 'aiwiki-before-debug',
|
|
75
|
-
content: `---
|
|
76
|
-
name: aiwiki-before-debug
|
|
77
|
-
description: Search project memory before debugging so related history is visible before changing code.
|
|
78
|
-
---
|
|
79
|
-
|
|
80
|
-
# AIWiki Before Debug
|
|
81
|
-
|
|
82
|
-
Use this skill before investigating a bug, failure, error message, or confusing behavior.
|
|
83
|
-
|
|
84
|
-
## Steps
|
|
85
|
-
|
|
86
|
-
1. Search the full project knowledge index:
|
|
87
|
-
\`\`\`bash
|
|
88
|
-
llm-iwiki search "<error or topic>" --project . --index all
|
|
89
|
-
\`\`\`
|
|
90
|
-
2. Read any related summaries, experiences, or prior decisions before editing code.
|
|
91
|
-
3. Report whether related history was found, and name the most relevant item when it was found.
|
|
92
|
-
`,
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
directory: 'aiwiki-project-retrospective',
|
|
96
|
-
content: `---
|
|
97
|
-
name: aiwiki-project-retrospective
|
|
98
|
-
description: Review recent project knowledge and extract retrospective themes from llm-iwiki.
|
|
99
|
-
---
|
|
100
|
-
|
|
101
|
-
# AIWiki Project Retrospective
|
|
102
|
-
|
|
103
|
-
Use this skill when preparing a project retrospective or looking for recent repeated lessons.
|
|
104
|
-
|
|
105
|
-
## Steps
|
|
106
|
-
|
|
107
|
-
1. Prepare a retrospective from recent project history:
|
|
108
|
-
\`\`\`bash
|
|
109
|
-
llm-iwiki experiences prepare --project . --from all-recent --since 30d --out .llm-iwiki/tasks/retrospective-task.md
|
|
110
|
-
\`\`\`
|
|
111
|
-
2. Review the prepared task for repeated themes, unresolved questions, and process improvements.
|
|
112
|
-
3. Capture any accepted learnings through the normal llm-iwiki experience proposal flow.
|
|
113
|
-
`,
|
|
114
|
-
},
|
|
115
|
-
]
|
|
116
|
-
|
|
117
|
-
function appendTargetGuidance(content: string, target: SkillTarget | null): string {
|
|
118
|
-
if (!target) return content
|
|
119
|
-
|
|
120
|
-
return `${content}
|
|
121
|
-
## Tool Target
|
|
122
|
-
|
|
123
|
-
This skill is initialized for ${TARGET_NAMES[target]}.
|
|
124
|
-
`
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function isFileExistsError(error: unknown): boolean {
|
|
128
|
-
return error instanceof Error && 'code' in error && error.code === 'EEXIST'
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export function initSkills(options: InitSkillsOptions): InitSkillsResult {
|
|
132
|
-
const written: string[] = []
|
|
133
|
-
const skipped: string[] = []
|
|
134
|
-
|
|
135
|
-
for (const template of SKILL_TEMPLATES) {
|
|
136
|
-
const filePath = join(options.cwd, '.agents', 'skills', template.directory, 'SKILL.md')
|
|
137
|
-
|
|
138
|
-
if (existsSync(filePath) && !options.force) {
|
|
139
|
-
skipped.push(filePath)
|
|
140
|
-
continue
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
written.push(filePath)
|
|
144
|
-
|
|
145
|
-
if (options.dryRun) {
|
|
146
|
-
continue
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
mkdirSync(dirname(filePath), { recursive: true })
|
|
150
|
-
const content = appendTargetGuidance(template.content, options.target)
|
|
151
|
-
if (options.force) {
|
|
152
|
-
writeFileSync(filePath, content)
|
|
153
|
-
continue
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
writeFileSync(filePath, content, { flag: 'wx' })
|
|
158
|
-
} catch (error) {
|
|
159
|
-
if (!isFileExistsError(error)) {
|
|
160
|
-
throw error
|
|
161
|
-
}
|
|
162
|
-
written.pop()
|
|
163
|
-
skipped.push(filePath)
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return { written, skipped }
|
|
168
|
-
}
|
package/src/summarize.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import type { ParsedSummariesYaml } from './types'
|
|
2
|
-
import type { LlmIwikiDatabase } from './db'
|
|
3
|
-
import { compactTranscript } from './compaction'
|
|
4
|
-
import { getSessionMessages, listSessionsToSummarize } from './sessions'
|
|
5
|
-
|
|
6
|
-
export interface PrepareSummariesResult {
|
|
7
|
-
markdown: string
|
|
8
|
-
sessionCount: number
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const SUMMARIES_FORMAT = `## 输出格式
|
|
12
|
-
|
|
13
|
-
请阅读下面每个会话的压缩记录,为有价值的会话生成一份 \`summaries.yaml\`,写入
|
|
14
|
-
\`.llm-iwiki/tasks/summaries.yaml\`,再运行 \`llm-iwiki summarize apply\`。
|
|
15
|
-
|
|
16
|
-
\`\`\`yaml
|
|
17
|
-
project_id: <见下方 project_id>
|
|
18
|
-
summaries:
|
|
19
|
-
- session_id: <会话的 session_id,原样照抄>
|
|
20
|
-
title: <一句话标题>
|
|
21
|
-
value: none | low | medium | high
|
|
22
|
-
summary_markdown: |
|
|
23
|
-
用 Markdown 概括这次会话解决了什么问题、关键决策、结论。
|
|
24
|
-
\`\`\`
|
|
25
|
-
|
|
26
|
-
要求:
|
|
27
|
-
- \`value\` 表示这次会话的沉淀价值,\`medium\` / \`high\` 会进入经验提取。
|
|
28
|
-
- \`session_id\` 必须与下面给出的完全一致。
|
|
29
|
-
- 没有价值的会话可以省略,不必每个都写。`
|
|
30
|
-
|
|
31
|
-
export function prepareSummariesTask(
|
|
32
|
-
db: LlmIwikiDatabase,
|
|
33
|
-
projectId: string,
|
|
34
|
-
scope: 'changed' | 'all',
|
|
35
|
-
): PrepareSummariesResult {
|
|
36
|
-
const sessions = listSessionsToSummarize(db, projectId, scope)
|
|
37
|
-
|
|
38
|
-
const blocks: string[] = []
|
|
39
|
-
for (const session of sessions) {
|
|
40
|
-
const messages = getSessionMessages(db, session.id)
|
|
41
|
-
if (messages.length === 0) continue
|
|
42
|
-
const transcript = compactTranscript(messages)
|
|
43
|
-
blocks.push(
|
|
44
|
-
[
|
|
45
|
-
`### ${session.id}`,
|
|
46
|
-
`- source: ${session.sourceId}`,
|
|
47
|
-
`- title: ${session.title ?? '(无标题)'}`,
|
|
48
|
-
`- messages: ${session.messageCount}`,
|
|
49
|
-
'',
|
|
50
|
-
transcript,
|
|
51
|
-
].join('\n'),
|
|
52
|
-
)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const markdown = [
|
|
56
|
-
`# Summaries Task`,
|
|
57
|
-
'',
|
|
58
|
-
`project_id: ${projectId}`,
|
|
59
|
-
`scope: ${scope}`,
|
|
60
|
-
`sessions: ${blocks.length}`,
|
|
61
|
-
'',
|
|
62
|
-
SUMMARIES_FORMAT,
|
|
63
|
-
'',
|
|
64
|
-
'---',
|
|
65
|
-
'',
|
|
66
|
-
blocks.join('\n\n---\n\n'),
|
|
67
|
-
'',
|
|
68
|
-
].join('\n')
|
|
69
|
-
|
|
70
|
-
return { markdown, sessionCount: blocks.length }
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export interface ApplySummariesResult {
|
|
74
|
-
written: number
|
|
75
|
-
skipped: string[]
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function hash(value: string): string {
|
|
79
|
-
return Bun.hash(value).toString(16)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function applySummaries(db: LlmIwikiDatabase, parsed: ParsedSummariesYaml): ApplySummariesResult {
|
|
83
|
-
const now = new Date().toISOString()
|
|
84
|
-
let written = 0
|
|
85
|
-
const skipped: string[] = []
|
|
86
|
-
|
|
87
|
-
const apply = db.transaction(() => {
|
|
88
|
-
for (const summary of parsed.summaries) {
|
|
89
|
-
const session = db
|
|
90
|
-
.query<{ project_id: string | null }, [string]>('SELECT project_id FROM sessions WHERE id = ?')
|
|
91
|
-
.get(summary.sessionId)
|
|
92
|
-
if (!session) {
|
|
93
|
-
skipped.push(summary.sessionId)
|
|
94
|
-
continue
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
db.query(`
|
|
98
|
-
INSERT INTO session_summaries (id, session_id, project_id, title, value, summary_markdown, metadata_json, created_at, updated_at)
|
|
99
|
-
VALUES ($id, $sessionId, $projectId, $title, $value, $summaryMarkdown, $metadata, $now, $now)
|
|
100
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
101
|
-
title = excluded.title,
|
|
102
|
-
value = excluded.value,
|
|
103
|
-
summary_markdown = excluded.summary_markdown,
|
|
104
|
-
metadata_json = excluded.metadata_json,
|
|
105
|
-
updated_at = excluded.updated_at
|
|
106
|
-
`).run({
|
|
107
|
-
$id: `sum_${hash(summary.sessionId)}`,
|
|
108
|
-
$sessionId: summary.sessionId,
|
|
109
|
-
$projectId: session.project_id ?? parsed.projectId,
|
|
110
|
-
$title: summary.title,
|
|
111
|
-
$value: summary.value,
|
|
112
|
-
$summaryMarkdown: summary.summaryMarkdown,
|
|
113
|
-
$metadata: JSON.stringify(summary.metadata),
|
|
114
|
-
$now: now,
|
|
115
|
-
})
|
|
116
|
-
written += 1
|
|
117
|
-
}
|
|
118
|
-
})
|
|
119
|
-
apply()
|
|
120
|
-
|
|
121
|
-
return { written, skipped }
|
|
122
|
-
}
|