@pencil-agent/nano-pencil 1.11.7 → 1.11.8
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/core/extensions/runner.js +2 -2
- package/dist/core/footer-data-provider.js +6 -0
- package/dist/extensions/defaults/loop/index.js +43 -1
- package/dist/packages/mem-core/cli.js +7 -2
- package/dist/packages/mem-core/engine.d.ts +2 -1
- package/dist/packages/mem-core/engine.js +1 -0
- package/dist/packages/mem-core/extension.js +7 -2
- package/dist/packages/mem-core/full-insights-html.js +125 -15
- package/dist/packages/mem-core/human-insights.d.ts +17 -9
- package/dist/packages/mem-core/human-insights.js +176 -109
- package/package.json +6 -6
|
@@ -115,10 +115,10 @@ export class ExtensionRunner {
|
|
|
115
115
|
this.sessionManager = sessionManager;
|
|
116
116
|
this.modelRegistry = modelRegistry;
|
|
117
117
|
}
|
|
118
|
-
async withTimeout(
|
|
118
|
+
async withTimeout(valueOrPromise, timeoutMs) {
|
|
119
119
|
return new Promise((resolve) => {
|
|
120
120
|
const timer = setTimeout(() => resolve(this.beforeAgentStartTimeoutSentinel), timeoutMs);
|
|
121
|
-
|
|
121
|
+
Promise.resolve(valueOrPromise)
|
|
122
122
|
.then((value) => {
|
|
123
123
|
clearTimeout(timer);
|
|
124
124
|
resolve(value);
|
|
@@ -125,6 +125,12 @@ export class FooterDataProvider {
|
|
|
125
125
|
cb();
|
|
126
126
|
}
|
|
127
127
|
});
|
|
128
|
+
this.gitWatcher.on("error", () => {
|
|
129
|
+
if (this.gitWatcher) {
|
|
130
|
+
this.gitWatcher.close();
|
|
131
|
+
this.gitWatcher = null;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
128
134
|
}
|
|
129
135
|
catch {
|
|
130
136
|
// Silently fail if we can't watch
|
|
@@ -175,7 +175,49 @@ function extractLoopDecision(text) {
|
|
|
175
175
|
};
|
|
176
176
|
}
|
|
177
177
|
catch {
|
|
178
|
-
|
|
178
|
+
const lines = payload
|
|
179
|
+
.split(/\r?\n/)
|
|
180
|
+
.map((line) => line.trim())
|
|
181
|
+
.filter(Boolean);
|
|
182
|
+
if (lines.length === 0) {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
const getValue = (...prefixes) => {
|
|
186
|
+
const line = lines.find((entry) => prefixes.some((prefix) => entry.toLowerCase().startsWith(prefix.toLowerCase())));
|
|
187
|
+
if (!line) {
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
const separatorIndex = line.indexOf(":");
|
|
191
|
+
if (separatorIndex === -1) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
return line.slice(separatorIndex + 1).trim();
|
|
195
|
+
};
|
|
196
|
+
const rawStatus = getValue("status:", "状态:");
|
|
197
|
+
const normalizedStatus = rawStatus === "complete" || rawStatus === "完成"
|
|
198
|
+
? "complete"
|
|
199
|
+
: rawStatus === "continue" || rawStatus === "继续" || rawStatus === "in_progress"
|
|
200
|
+
? "continue"
|
|
201
|
+
: rawStatus === "blocked" || rawStatus === "阻塞"
|
|
202
|
+
? "blocked"
|
|
203
|
+
: undefined;
|
|
204
|
+
if (!normalizedStatus) {
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
const summary = getValue("summary:", "摘要:", "已完成工作:", "completed work:") ??
|
|
208
|
+
lines.filter((line) => !line.startsWith("-")).slice(1).join(" ").trim();
|
|
209
|
+
if (!summary) {
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
const nextStep = getValue("next step:", "下一步:");
|
|
213
|
+
if (normalizedStatus === "continue" && !nextStep) {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
status: normalizedStatus,
|
|
218
|
+
summary,
|
|
219
|
+
nextStep,
|
|
220
|
+
};
|
|
179
221
|
}
|
|
180
222
|
}
|
|
181
223
|
function describeDecision(decision) {
|
|
@@ -94,8 +94,13 @@ Usage:
|
|
|
94
94
|
writeFileSync(outputPath, html, "utf-8");
|
|
95
95
|
}
|
|
96
96
|
else {
|
|
97
|
-
const
|
|
98
|
-
const html = renderFullInsightsHtml(
|
|
97
|
+
const enhanced = await engine.generateEnhancedInsights();
|
|
98
|
+
const html = renderFullInsightsHtml({
|
|
99
|
+
...enhanced.report,
|
|
100
|
+
persona: enhanced.persona,
|
|
101
|
+
humanInsights: enhanced.humanInsights,
|
|
102
|
+
rootCauses: enhanced.rootCauses,
|
|
103
|
+
}, engine.cfg.locale);
|
|
99
104
|
writeFileSync(outputPath, html, "utf-8");
|
|
100
105
|
}
|
|
101
106
|
console.log(`Insights report written to: ${outputPath}`);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* No dependency on any specific AI framework — LLM is pluggable.
|
|
8
8
|
*/
|
|
9
9
|
import { type NanomemConfig } from "./config.js";
|
|
10
|
-
import type { Episode, ExtractedItem, FullInsightsReport, HumanInsight, InsightsReport, LlmFn, MemoryEntry, MemoryScope, Meta, RootCauseInsight, WorkEntry } from "./types.js";
|
|
10
|
+
import type { DeveloperPersona, Episode, ExtractedItem, FullInsightsReport, HumanInsight, InsightsReport, LlmFn, MemoryEntry, MemoryScope, Meta, RootCauseInsight, WorkEntry } from "./types.js";
|
|
11
11
|
export declare class NanoMemEngine {
|
|
12
12
|
readonly cfg: NanomemConfig;
|
|
13
13
|
private llmFn?;
|
|
@@ -74,6 +74,7 @@ export declare class NanoMemEngine {
|
|
|
74
74
|
*/
|
|
75
75
|
generateEnhancedInsights(): Promise<{
|
|
76
76
|
report: FullInsightsReport;
|
|
77
|
+
persona?: DeveloperPersona;
|
|
77
78
|
humanInsights: HumanInsight[];
|
|
78
79
|
rootCauses: RootCauseInsight[];
|
|
79
80
|
}>;
|
|
@@ -255,8 +255,13 @@ export default function nanomemExtension(pi) {
|
|
|
255
255
|
}
|
|
256
256
|
const outputPath = args?.trim() || "./nanomem-insights.html";
|
|
257
257
|
ctx.ui.notify("NanoMem: Generating full insights report...", "info");
|
|
258
|
-
const
|
|
259
|
-
const html = renderFullInsightsHtml(
|
|
258
|
+
const enhanced = await engine.generateEnhancedInsights();
|
|
259
|
+
const html = renderFullInsightsHtml({
|
|
260
|
+
...enhanced.report,
|
|
261
|
+
persona: enhanced.persona,
|
|
262
|
+
humanInsights: enhanced.humanInsights,
|
|
263
|
+
rootCauses: enhanced.rootCauses,
|
|
264
|
+
}, engine.cfg.locale);
|
|
260
265
|
writeFileSync(outputPath, html, "utf-8");
|
|
261
266
|
ctx.ui.notify(`NanoMem: Insights report written to ${outputPath}`, "info");
|
|
262
267
|
},
|
|
@@ -39,12 +39,28 @@ function renderBarRows(chart) {
|
|
|
39
39
|
export function renderFullInsightsHtml(report, locale) {
|
|
40
40
|
const p = PROMPTS[locale] ?? PROMPTS.en;
|
|
41
41
|
const lang = locale === "zh" ? "zh-CN" : "en";
|
|
42
|
+
const enhancedReport = report;
|
|
42
43
|
const sections = [];
|
|
43
44
|
// TOC links (only for sections we might render)
|
|
44
45
|
const tocLinks = [
|
|
45
46
|
'<a href="#section-glance"><i class="ri-dashboard-line"></i> ' + escapeHtml(p.fullInsightsAtAGlance) + "</a>",
|
|
46
47
|
'<a href="#section-work"><i class="ri-briefcase-4-line"></i> ' + escapeHtml(p.fullInsightsWorkOn) + "</a>",
|
|
47
48
|
];
|
|
49
|
+
if (enhancedReport.persona) {
|
|
50
|
+
tocLinks.push('<a href="#section-persona"><i class="ri-user-star-line"></i> ' +
|
|
51
|
+
escapeHtml(p.humanInsightsSectionPersona) +
|
|
52
|
+
"</a>");
|
|
53
|
+
}
|
|
54
|
+
if (enhancedReport.humanInsights?.length) {
|
|
55
|
+
tocLinks.push('<a href="#section-human-insights"><i class="ri-robot-2-line"></i> ' +
|
|
56
|
+
escapeHtml(p.humanInsightsSectionInsights) +
|
|
57
|
+
"</a>");
|
|
58
|
+
}
|
|
59
|
+
if (enhancedReport.rootCauses?.length) {
|
|
60
|
+
tocLinks.push('<a href="#section-root-causes"><i class="ri-stethoscope-line"></i> ' +
|
|
61
|
+
escapeHtml(p.humanInsightsSectionRootCauses) +
|
|
62
|
+
"</a>");
|
|
63
|
+
}
|
|
48
64
|
if (report.charts.length)
|
|
49
65
|
tocLinks.push('<a href="#section-charts"><i class="ri-bar-chart-box-line"></i> Charts</a>');
|
|
50
66
|
if (report.wins.length)
|
|
@@ -70,15 +86,78 @@ export function renderFullInsightsHtml(report, locale) {
|
|
|
70
86
|
${statItems.map((s) => `<div class="stat"><i class="${s.icon} stat-icon"></i><div class="stat-value">${s.value}</div><div class="stat-label">${escapeHtml(s.label)}</div></div>`).join("\n")}
|
|
71
87
|
</section>`;
|
|
72
88
|
// At a Glance
|
|
73
|
-
const glanceHtml = `<section id="section-glance" class="at-a-glance">
|
|
74
|
-
<h2 class="glance-title"><i class="ri-dashboard-line"></i> ${escapeHtml(p.fullInsightsAtAGlance)}</h2>
|
|
75
|
-
<div class="glance-grid">
|
|
89
|
+
const glanceHtml = `<section id="section-glance" class="at-a-glance">
|
|
90
|
+
<h2 class="glance-title"><i class="ri-dashboard-line"></i> ${escapeHtml(p.fullInsightsAtAGlance)}</h2>
|
|
91
|
+
<div class="glance-grid">
|
|
76
92
|
<article class="glance-card"><h3><i class="ri-checkbox-circle-line"></i> What's working</h3><p>${escapeHtml(report.atAGlance.working)}</p></article>
|
|
77
93
|
<article class="glance-card warn"><h3><i class="ri-error-warning-line"></i> What's hindering</h3><p>${escapeHtml(report.atAGlance.hindering)}</p></article>
|
|
78
94
|
<article class="glance-card"><h3><i class="ri-lightbulb-line"></i> Quick wins</h3><p>${escapeHtml(report.atAGlance.quickWins)}</p></article>
|
|
79
95
|
<article class="glance-card"><h3><i class="ri-rocket-line"></i> Ambitious</h3><p>${escapeHtml(report.atAGlance.ambitious)}</p></article>
|
|
80
|
-
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</section>`;
|
|
98
|
+
let personaHtml = "";
|
|
99
|
+
if (enhancedReport.persona) {
|
|
100
|
+
const persona = enhancedReport.persona;
|
|
101
|
+
personaHtml = `<section id="section-persona" class="section">
|
|
102
|
+
<h2><i class="ri-user-star-line"></i> ${escapeHtml(p.humanInsightsSectionPersona)}</h2>
|
|
103
|
+
<div class="persona-grid">
|
|
104
|
+
<article class="persona-card persona-lead">
|
|
105
|
+
<div class="persona-kicker">${escapeHtml(persona.summary)}</div>
|
|
106
|
+
<div class="persona-text">${escapeHtml(persona.whatTheyDo)}</div>
|
|
107
|
+
<div class="persona-text">${escapeHtml(persona.workStyle)}</div>
|
|
108
|
+
<div class="persona-meta">${escapeHtml(persona.experienceLevel)}</div>
|
|
109
|
+
</article>
|
|
110
|
+
<article class="persona-card">
|
|
111
|
+
<div class="persona-card-title">Strengths</div>
|
|
112
|
+
<ul class="persona-list">${persona.superpowers.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>
|
|
113
|
+
</article>
|
|
114
|
+
<article class="persona-card">
|
|
115
|
+
<div class="persona-card-title">Watchouts</div>
|
|
116
|
+
<ul class="persona-list">${persona.painPoints.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>
|
|
117
|
+
</article>
|
|
118
|
+
</div>
|
|
119
|
+
</section>`;
|
|
120
|
+
}
|
|
121
|
+
let humanInsightsHtml = "";
|
|
122
|
+
if (enhancedReport.humanInsights?.length) {
|
|
123
|
+
humanInsightsHtml = `<section id="section-human-insights" class="section">
|
|
124
|
+
<h2><i class="ri-robot-2-line"></i> ${escapeHtml(p.humanInsightsSectionInsights)}</h2>
|
|
125
|
+
<div class="insight-review-list">
|
|
126
|
+
${enhancedReport.humanInsights
|
|
127
|
+
.map((insight) => ` <article class="insight-review-card priority-${escapeHtml(insight.utility)}">
|
|
128
|
+
<div class="insight-review-header">
|
|
129
|
+
<div class="insight-review-icon">${escapeHtml(insight.icon)}</div>
|
|
130
|
+
<div>
|
|
131
|
+
<div class="insight-review-title">${escapeHtml(insight.title)}</div>
|
|
132
|
+
<div class="insight-review-priority">${escapeHtml(insight.utility.toUpperCase())}</div>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
<div class="insight-review-content">${escapeHtml(insight.content)}</div>
|
|
136
|
+
${insight.tags.length ? `<div class="insight-tags">${insight.tags.map((tag) => `<span>${escapeHtml(tag)}</span>`).join("")}</div>` : ""}
|
|
137
|
+
</article>`)
|
|
138
|
+
.join("\n")}
|
|
139
|
+
</div>
|
|
140
|
+
</section>`;
|
|
141
|
+
}
|
|
142
|
+
let rootCausesHtml = "";
|
|
143
|
+
if (enhancedReport.rootCauses?.length) {
|
|
144
|
+
rootCausesHtml = `<section id="section-root-causes" class="section">
|
|
145
|
+
<h2><i class="ri-stethoscope-line"></i> ${escapeHtml(p.humanInsightsSectionRootCauses)}</h2>
|
|
146
|
+
<div class="root-cause-list">
|
|
147
|
+
${enhancedReport.rootCauses
|
|
148
|
+
.map((item) => ` <article class="root-cause-card">
|
|
149
|
+
<div class="root-cause-label">Recurring symptom</div>
|
|
150
|
+
<div class="root-cause-title">${escapeHtml(item.symptom)}</div>
|
|
151
|
+
<div class="root-cause-label">Likely cause</div>
|
|
152
|
+
<div class="root-cause-body">${escapeHtml(item.rootCause)}</div>
|
|
153
|
+
${item.evidence.length ? `<div class="root-cause-label">Evidence</div><ul class="root-cause-evidence">${item.evidence.map((fact) => `<li>${escapeHtml(fact)}</li>`).join("")}</ul>` : ""}
|
|
154
|
+
<div class="root-cause-label">Recommended fix</div>
|
|
155
|
+
<div class="root-cause-body">${escapeHtml(item.suggestion)}</div>
|
|
156
|
+
</article>`)
|
|
157
|
+
.join("\n")}
|
|
158
|
+
</div>
|
|
81
159
|
</section>`;
|
|
160
|
+
}
|
|
82
161
|
// What You Work On
|
|
83
162
|
let workHtml = "";
|
|
84
163
|
if (report.projectAreas.length) {
|
|
@@ -201,10 +280,37 @@ h2 .ri{vertical-align:middle;margin-right:6px}
|
|
|
201
280
|
.subtitle{color:#64748b;font-size:15px;margin-bottom:24px}
|
|
202
281
|
.nav-toc{display:flex;flex-wrap:wrap;gap:8px;margin:24px 0 32px;padding:16px;background:#fff;border-radius:8px;border:1px solid #e2e8f0}
|
|
203
282
|
.nav-toc a{font-size:12px;color:#64748b;text-decoration:none;padding:6px 12px;border-radius:6px;background:#f1f5f9;transition:all .15s}
|
|
204
|
-
.nav-toc a:hover{background:#e2e8f0;color:#334155}
|
|
205
|
-
.nav-toc .ri{margin-right:4px;vertical-align:middle}
|
|
206
|
-
.stats-row{display:flex;gap:24px;margin-bottom:32px;padding:20px 0;border-top:1px solid #e2e8f0;border-bottom:1px solid #e2e8f0;flex-wrap:wrap}
|
|
207
|
-
.
|
|
283
|
+
.nav-toc a:hover{background:#e2e8f0;color:#334155}
|
|
284
|
+
.nav-toc .ri{margin-right:4px;vertical-align:middle}
|
|
285
|
+
.stats-row{display:flex;gap:24px;margin-bottom:32px;padding:20px 0;border-top:1px solid #e2e8f0;border-bottom:1px solid #e2e8f0;flex-wrap:wrap}
|
|
286
|
+
.persona-grid{display:grid;grid-template-columns:2fr 1fr 1fr;gap:16px}
|
|
287
|
+
.persona-card{background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;padding:16px}
|
|
288
|
+
.persona-lead{background:linear-gradient(135deg,#fff7ed 0%,#ffedd5 100%);border-color:#fdba74}
|
|
289
|
+
.persona-kicker{font-size:18px;font-weight:700;color:#9a3412;margin-bottom:10px}
|
|
290
|
+
.persona-text{font-size:14px;color:#334155;line-height:1.6;margin-bottom:8px}
|
|
291
|
+
.persona-meta{font-size:12px;color:#7c2d12;text-transform:uppercase;letter-spacing:.04em}
|
|
292
|
+
.persona-card-title{font-size:12px;font-weight:700;color:#64748b;text-transform:uppercase;margin-bottom:10px}
|
|
293
|
+
.persona-list{margin:0;padding-left:18px}
|
|
294
|
+
.persona-list li{margin-bottom:8px;font-size:14px;color:#334155}
|
|
295
|
+
.insight-review-list,.root-cause-list{display:flex;flex-direction:column;gap:16px}
|
|
296
|
+
.insight-review-card{border-radius:10px;padding:18px;border:1px solid #dbeafe;background:#f8fbff}
|
|
297
|
+
.insight-review-card.priority-high{border-color:#93c5fd;background:#eff6ff}
|
|
298
|
+
.insight-review-card.priority-medium{border-color:#cbd5e1;background:#f8fafc}
|
|
299
|
+
.insight-review-card.priority-low{border-color:#d1fae5;background:#f0fdf4}
|
|
300
|
+
.insight-review-header{display:flex;align-items:center;gap:12px;margin-bottom:12px}
|
|
301
|
+
.insight-review-icon{font-size:24px;line-height:1}
|
|
302
|
+
.insight-review-title{font-size:16px;font-weight:700;color:#0f172a}
|
|
303
|
+
.insight-review-priority{font-size:11px;color:#475569;text-transform:uppercase;letter-spacing:.08em}
|
|
304
|
+
.insight-review-content{font-size:14px;color:#334155;line-height:1.7}
|
|
305
|
+
.insight-tags{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
|
|
306
|
+
.insight-tags span{font-size:11px;color:#475569;background:#e2e8f0;border-radius:999px;padding:4px 8px}
|
|
307
|
+
.root-cause-card{border-radius:10px;padding:18px;border:1px solid #fecaca;background:#fff7f7}
|
|
308
|
+
.root-cause-label{font-size:11px;font-weight:700;color:#991b1b;text-transform:uppercase;letter-spacing:.06em;margin-bottom:6px}
|
|
309
|
+
.root-cause-title{font-size:16px;font-weight:700;color:#7f1d1d;margin-bottom:10px}
|
|
310
|
+
.root-cause-body{font-size:14px;color:#334155;line-height:1.7;margin-bottom:12px}
|
|
311
|
+
.root-cause-evidence{margin:0 0 12px 18px}
|
|
312
|
+
.root-cause-evidence li{margin-bottom:6px;font-size:13px;color:#475569}
|
|
313
|
+
.stat{text-align:center}
|
|
208
314
|
.stat-icon{font-size:20px;color:#64748b;display:block;margin-bottom:4px}
|
|
209
315
|
.stat-value{font-size:24px;font-weight:700;color:#0f172a}
|
|
210
316
|
.stat-label{font-size:11px;color:#64748b;text-transform:uppercase}
|
|
@@ -254,8 +360,9 @@ h2 .ri{vertical-align:middle;margin-right:6px}
|
|
|
254
360
|
.copy-btn:hover{background:#cbd5e1}
|
|
255
361
|
.pattern-list{margin:0;padding-left:20px}
|
|
256
362
|
.pattern-list li{margin-bottom:6px;font-size:14px;color:#334155}
|
|
257
|
-
footer{margin-top:32px;text-align:center;font-size:12px;color:#94a3b8}
|
|
258
|
-
@media (max-width:640px){.charts-row{grid-template-columns:1fr}.stats-row{justify-content:center}}
|
|
363
|
+
footer{margin-top:32px;text-align:center;font-size:12px;color:#94a3b8}
|
|
364
|
+
@media (max-width:640px){.charts-row{grid-template-columns:1fr}.stats-row{justify-content:center}}
|
|
365
|
+
@media (max-width:900px){.persona-grid{grid-template-columns:1fr}}
|
|
259
366
|
`;
|
|
260
367
|
const copyScript = `
|
|
261
368
|
document.querySelectorAll('.copy-btn').forEach(function(btn){
|
|
@@ -291,11 +398,14 @@ document.querySelectorAll('.copy-btn').forEach(function(btn){
|
|
|
291
398
|
${tocLinks.map((link) => " " + link).join("\n")}
|
|
292
399
|
</nav>
|
|
293
400
|
|
|
294
|
-
${statsHtml}
|
|
295
|
-
${glanceHtml}
|
|
296
|
-
${workHtml}
|
|
297
|
-
${
|
|
298
|
-
${
|
|
401
|
+
${statsHtml}
|
|
402
|
+
${glanceHtml}
|
|
403
|
+
${workHtml}
|
|
404
|
+
${personaHtml}
|
|
405
|
+
${humanInsightsHtml}
|
|
406
|
+
${rootCausesHtml}
|
|
407
|
+
${chartsHtml}
|
|
408
|
+
${winsHtml}
|
|
299
409
|
${frictionsHtml}
|
|
300
410
|
${recHtml}
|
|
301
411
|
${featuresHtml}
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* [INPUT]: ExportAllResult, LlmFn, locale
|
|
3
|
-
* [OUTPUT]:
|
|
4
|
-
* [POS]: LLM-powered
|
|
5
|
-
*/
|
|
6
|
-
import type { DeveloperPersona, EnhancedInsightsReport, ExportAllResult, HumanInsight, LlmFn, RootCauseInsight } from "./types.js";
|
|
7
|
-
/**
|
|
8
|
-
* 生成大白话版洞察报告
|
|
3
|
+
* [OUTPUT]: developer persona, evidence-backed insights, and root-cause analysis
|
|
4
|
+
* [POS]: LLM-powered usage review generation
|
|
9
5
|
*/
|
|
6
|
+
import type { DeveloperPersona, EnhancedInsightsReport, HumanInsight, LlmFn, MemoryEntry, Episode, RootCauseInsight, WorkEntry } from "./types.js";
|
|
7
|
+
interface ExportAllResult {
|
|
8
|
+
knowledge: MemoryEntry[];
|
|
9
|
+
lessons: MemoryEntry[];
|
|
10
|
+
preferences: MemoryEntry[];
|
|
11
|
+
facets: MemoryEntry[];
|
|
12
|
+
work: WorkEntry[];
|
|
13
|
+
episodes: Episode[];
|
|
14
|
+
meta: {
|
|
15
|
+
totalSessions: number;
|
|
16
|
+
lastConsolidation?: string;
|
|
17
|
+
version: number;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
10
20
|
export declare function generateHumanInsights(all: ExportAllResult, llmFn: LlmFn | undefined, locale: string): Promise<{
|
|
11
21
|
persona?: DeveloperPersona;
|
|
12
22
|
humanInsights: HumanInsight[];
|
|
13
23
|
rootCauses: RootCauseInsight[];
|
|
14
24
|
}>;
|
|
15
|
-
/**
|
|
16
|
-
* 将人类可读洞察合并到 FullInsightsReport 生成流程中
|
|
17
|
-
*/
|
|
18
25
|
export declare function buildEnhancedInsightsReport(all: ExportAllResult, llmFn: LlmFn | undefined, locale: string): Promise<EnhancedInsightsReport>;
|
|
26
|
+
export {};
|
|
19
27
|
//# sourceMappingURL=human-insights.d.ts.map
|
|
@@ -1,118 +1,203 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* [INPUT]: ExportAllResult, LlmFn, locale
|
|
3
|
-
* [OUTPUT]:
|
|
4
|
-
* [POS]: LLM-powered
|
|
3
|
+
* [OUTPUT]: developer persona, evidence-backed insights, and root-cause analysis
|
|
4
|
+
* [POS]: LLM-powered usage review generation
|
|
5
5
|
*/
|
|
6
|
-
|
|
6
|
+
const HUMAN_INSIGHTS_SYSTEM_PROMPT = `You are an elite AI product analyst and developer workflow coach.
|
|
7
|
+
|
|
8
|
+
You are reviewing one specific user's real usage history over time.
|
|
9
|
+
Write like a warm, observant expert who deeply understands how experienced AI users actually work.
|
|
10
|
+
|
|
11
|
+
Goals:
|
|
12
|
+
- Sound human, perceptive, and respectful rather than robotic or generic
|
|
13
|
+
- Give clear corrections when the user's habits are inefficient
|
|
14
|
+
- Back every major conclusion with concrete evidence from the supplied data
|
|
15
|
+
- Prefer precise language, plain English, and practical recommendations
|
|
16
|
+
- Explain what the user is doing well, where they are losing time, and what they should change next
|
|
17
|
+
|
|
18
|
+
Output requirements:
|
|
19
|
+
- Output ONLY valid JSON
|
|
20
|
+
- Do not use markdown or code fences
|
|
21
|
+
- Use the supplied data only
|
|
22
|
+
- Be specific, not motivational fluff
|
|
23
|
+
- Each insight should feel like part of a thoughtful performance review
|
|
24
|
+
- Recommendations should be direct, concrete, and easy to act on
|
|
25
|
+
- Evidence should reference counts, repeated behaviors, or recurring issues when possible
|
|
26
|
+
- If locale is "zh", write the JSON string values in Simplified Chinese; otherwise write in English
|
|
27
|
+
|
|
28
|
+
Return JSON matching this schema:
|
|
29
|
+
{
|
|
30
|
+
"persona": {
|
|
31
|
+
"whatTheyDo": "1-2 sentences",
|
|
32
|
+
"experienceLevel": "1 sentence",
|
|
33
|
+
"superpowers": ["...", "..."],
|
|
34
|
+
"painPoints": ["...", "..."],
|
|
35
|
+
"workStyle": "1-2 sentences",
|
|
36
|
+
"summary": "1 sentence"
|
|
37
|
+
},
|
|
38
|
+
"insights": [
|
|
39
|
+
{
|
|
40
|
+
"title": "short title",
|
|
41
|
+
"content": "3-5 sentences combining observation, evidence, correction, and advice",
|
|
42
|
+
"icon": "emoji",
|
|
43
|
+
"utility": "high|medium|low",
|
|
44
|
+
"tags": ["tag1", "tag2"]
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"rootCauses": [
|
|
48
|
+
{
|
|
49
|
+
"symptom": "what keeps happening",
|
|
50
|
+
"rootCause": "why it likely happens",
|
|
51
|
+
"evidence": ["fact 1", "fact 2"],
|
|
52
|
+
"suggestion": "what to change next"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}`.trim();
|
|
56
|
+
function summarizeCounts(rows, formatter) {
|
|
57
|
+
return rows.map(([label, value]) => formatter(label, value));
|
|
58
|
+
}
|
|
7
59
|
function buildHumanInsightsData(all) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
return acc;
|
|
15
|
-
}, {}))
|
|
16
|
-
.sort((a, b) => b[1] - a[1])
|
|
17
|
-
.slice(0, 10)
|
|
18
|
-
.map(([t, c]) => `${t} (${c}次)`)
|
|
19
|
-
.join(", ")
|
|
20
|
-
: "暂无数据";
|
|
21
|
-
// 语言统计
|
|
22
|
-
const langCounts = {};
|
|
23
|
-
for (const ep of all.episodes) {
|
|
24
|
-
for (const f of ep.filesModified || []) {
|
|
25
|
-
const ext = f.includes(".") ? f.split(".").pop()?.toLowerCase() ?? "other" : "other";
|
|
26
|
-
langCounts[ext] = (langCounts[ext] || 0) + 1;
|
|
60
|
+
const totalToolUses = all.episodes.reduce((total, episode) => total +
|
|
61
|
+
Object.values(episode.toolsUsed ?? {}).reduce((sum, count) => sum + count, 0), 0);
|
|
62
|
+
const toolCounts = all.episodes.reduce((acc, episode) => {
|
|
63
|
+
for (const [tool, count] of Object.entries(episode.toolsUsed ?? {})) {
|
|
64
|
+
acc[tool] = (acc[tool] ?? 0) + count;
|
|
27
65
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
.
|
|
33
|
-
|
|
34
|
-
.join(", ")
|
|
35
|
-
: "暂无数据";
|
|
36
|
-
// 已解决的问题 (wins)
|
|
37
|
-
const wins = all.facets
|
|
38
|
-
.filter((f) => f.type === "struggle" && f.facetData?.kind === "struggle" && f.facetData.solution)
|
|
39
|
-
.slice(0, 8)
|
|
40
|
-
.map((f) => f.summary || f.facetData?.kind === "struggle" && f.facetData.problem)
|
|
41
|
-
.filter(Boolean)
|
|
42
|
-
.join("; ") || "暂无记录";
|
|
43
|
-
// 未解决的问题 (struggles)
|
|
44
|
-
const struggles = all.facets
|
|
45
|
-
.filter((f) => f.type === "struggle" && (!f.facetData || (f.facetData.kind === "struggle" && !f.facetData.solution)))
|
|
46
|
-
.slice(0, 8)
|
|
47
|
-
.map((f) => f.facetData?.kind === "struggle" ? f.facetData.problem : (f.summary || f.detail || ""))
|
|
48
|
-
.filter(Boolean)
|
|
49
|
-
.join("; ") || "暂无记录";
|
|
50
|
-
// 经验教训
|
|
51
|
-
const lessons = all.lessons
|
|
52
|
-
.slice(0, 10)
|
|
53
|
-
.map((l) => l.summary || l.detail || l.content || "")
|
|
54
|
-
.filter(Boolean)
|
|
55
|
-
.join("; ") || "暂无记录";
|
|
56
|
-
// 错误统计
|
|
57
|
-
const errorCounts = {};
|
|
58
|
-
for (const ep of all.episodes) {
|
|
59
|
-
for (const err of ep.errors || []) {
|
|
60
|
-
const key = err.slice(0, 50).trim();
|
|
61
|
-
errorCounts[key] = (errorCounts[key] || 0) + 1;
|
|
66
|
+
return acc;
|
|
67
|
+
}, {});
|
|
68
|
+
const languageCounts = all.episodes.reduce((acc, episode) => {
|
|
69
|
+
for (const file of episode.filesModified ?? []) {
|
|
70
|
+
const ext = file.includes(".") ? file.split(".").pop()?.toLowerCase() ?? "other" : "other";
|
|
71
|
+
acc[ext] = (acc[ext] ?? 0) + 1;
|
|
62
72
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
return acc;
|
|
74
|
+
}, {});
|
|
75
|
+
const errorCounts = all.episodes.reduce((acc, episode) => {
|
|
76
|
+
for (const error of episode.errors ?? []) {
|
|
77
|
+
const key = error.replace(/\s+/g, " ").trim().slice(0, 120);
|
|
78
|
+
if (!key)
|
|
79
|
+
continue;
|
|
80
|
+
acc[key] = (acc[key] ?? 0) + 1;
|
|
81
|
+
}
|
|
82
|
+
return acc;
|
|
83
|
+
}, {});
|
|
84
|
+
const resolvedStruggles = all.facets.filter((entry) => entry.type === "struggle" && entry.facetData?.kind === "struggle" && !!entry.facetData.solution);
|
|
85
|
+
const unresolvedStruggles = all.facets.filter((entry) => entry.type === "struggle" && entry.facetData?.kind === "struggle" && !entry.facetData.solution);
|
|
86
|
+
const patternEntries = all.facets.filter((entry) => entry.type === "pattern" && entry.facetData?.kind === "pattern");
|
|
87
|
+
const topTools = Object.entries(toolCounts)
|
|
88
|
+
.sort((a, b) => b[1] - a[1])
|
|
89
|
+
.slice(0, 8);
|
|
90
|
+
const topLanguages = Object.entries(languageCounts)
|
|
91
|
+
.sort((a, b) => b[1] - a[1])
|
|
92
|
+
.slice(0, 8);
|
|
93
|
+
const topErrors = Object.entries(errorCounts)
|
|
94
|
+
.sort((a, b) => b[1] - a[1])
|
|
95
|
+
.slice(0, 8);
|
|
96
|
+
const topPatterns = patternEntries
|
|
97
|
+
.slice()
|
|
98
|
+
.sort((a, b) => (b.accessCount + 1) * b.importance - (a.accessCount + 1) * a.importance)
|
|
99
|
+
.slice(0, 6)
|
|
100
|
+
.map((entry) => ({
|
|
101
|
+
trigger: entry.facetData?.kind === "pattern" ? entry.facetData.trigger : "",
|
|
102
|
+
behavior: entry.facetData?.kind === "pattern" ? entry.facetData.behavior : "",
|
|
103
|
+
importance: entry.importance,
|
|
104
|
+
accessCount: entry.accessCount,
|
|
105
|
+
}));
|
|
106
|
+
const notableWins = resolvedStruggles.slice(0, 6).map((entry) => ({
|
|
107
|
+
problem: entry.facetData?.kind === "struggle" ? entry.facetData.problem : entry.summary || "",
|
|
108
|
+
solution: entry.facetData?.kind === "struggle" ? entry.facetData.solution : "",
|
|
109
|
+
importance: entry.importance,
|
|
110
|
+
}));
|
|
111
|
+
const notableFrictions = unresolvedStruggles.slice(0, 6).map((entry) => ({
|
|
112
|
+
problem: entry.facetData?.kind === "struggle" ? entry.facetData.problem : entry.summary || "",
|
|
113
|
+
attempts: entry.facetData?.kind === "struggle" ? entry.facetData.attempts : [],
|
|
114
|
+
importance: entry.importance,
|
|
115
|
+
}));
|
|
116
|
+
const topLessons = all.lessons
|
|
117
|
+
.slice()
|
|
118
|
+
.sort((a, b) => (b.accessCount + 1) * b.importance - (a.accessCount + 1) * a.importance)
|
|
119
|
+
.slice(0, 8)
|
|
120
|
+
.map((entry) => entry.summary || entry.detail || entry.content || "")
|
|
121
|
+
.filter(Boolean);
|
|
122
|
+
const projectCounts = all.episodes.reduce((acc, episode) => {
|
|
123
|
+
const key = episode.project || "default";
|
|
124
|
+
acc[key] = (acc[key] ?? 0) + 1;
|
|
125
|
+
return acc;
|
|
126
|
+
}, {});
|
|
127
|
+
return {
|
|
128
|
+
overview: {
|
|
129
|
+
totalSessions: all.meta.totalSessions,
|
|
130
|
+
episodes: all.episodes.length,
|
|
131
|
+
workEntries: all.work.length,
|
|
132
|
+
knowledgeEntries: all.knowledge.length,
|
|
133
|
+
lessonEntries: all.lessons.length,
|
|
134
|
+
preferenceEntries: all.preferences.length,
|
|
135
|
+
facetEntries: all.facets.length,
|
|
136
|
+
totalToolUses,
|
|
137
|
+
resolvedStruggleCount: resolvedStruggles.length,
|
|
138
|
+
unresolvedStruggleCount: unresolvedStruggles.length,
|
|
139
|
+
},
|
|
140
|
+
topTools: topTools.map(([tool, count]) => ({
|
|
141
|
+
tool,
|
|
142
|
+
count,
|
|
143
|
+
share: totalToolUses > 0 ? Number(((count / totalToolUses) * 100).toFixed(1)) : 0,
|
|
144
|
+
})),
|
|
145
|
+
topLanguages: topLanguages.map(([language, fileCount]) => ({ language, fileCount })),
|
|
146
|
+
topErrors: topErrors.map(([error, count]) => ({ error, count })),
|
|
147
|
+
topPatterns,
|
|
148
|
+
notableWins,
|
|
149
|
+
notableFrictions,
|
|
150
|
+
topLessons,
|
|
151
|
+
projectDistribution: Object.entries(projectCounts)
|
|
66
152
|
.sort((a, b) => b[1] - a[1])
|
|
67
|
-
.slice(0,
|
|
68
|
-
.map(([
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
153
|
+
.slice(0, 6)
|
|
154
|
+
.map(([project, sessions]) => ({ project, sessions })),
|
|
155
|
+
evidenceDigest: {
|
|
156
|
+
tools: summarizeCounts(topTools, (tool, count) => `${tool}: ${count} uses`),
|
|
157
|
+
languages: summarizeCounts(topLanguages, (language, count) => `${language}: ${count} files`),
|
|
158
|
+
errors: summarizeCounts(topErrors, (error, count) => `${error}: ${count} times`),
|
|
159
|
+
},
|
|
160
|
+
};
|
|
72
161
|
}
|
|
73
162
|
function parseHumanInsightsResponse(raw) {
|
|
74
163
|
try {
|
|
75
164
|
const cleaned = raw.replace(/```json?\n?/g, "").replace(/```/g, "").trim();
|
|
76
165
|
const parsed = JSON.parse(cleaned);
|
|
77
|
-
|
|
78
|
-
if (typeof parsed !== "object" || parsed === null)
|
|
166
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
79
167
|
return null;
|
|
168
|
+
}
|
|
80
169
|
const persona = parsed.persona
|
|
81
170
|
? {
|
|
82
171
|
whatTheyDo: String(parsed.persona.whatTheyDo || ""),
|
|
83
172
|
experienceLevel: String(parsed.persona.experienceLevel || ""),
|
|
84
|
-
superpowers: Array.isArray(parsed.persona.superpowers)
|
|
85
|
-
|
|
86
|
-
: [],
|
|
87
|
-
painPoints: Array.isArray(parsed.persona.painPoints)
|
|
88
|
-
? parsed.persona.painPoints.map(String)
|
|
89
|
-
: [],
|
|
173
|
+
superpowers: Array.isArray(parsed.persona.superpowers) ? parsed.persona.superpowers.map(String) : [],
|
|
174
|
+
painPoints: Array.isArray(parsed.persona.painPoints) ? parsed.persona.painPoints.map(String) : [],
|
|
90
175
|
workStyle: String(parsed.persona.workStyle || ""),
|
|
91
176
|
summary: String(parsed.persona.summary || ""),
|
|
92
177
|
}
|
|
93
178
|
: undefined;
|
|
94
179
|
const insights = Array.isArray(parsed.insights)
|
|
95
|
-
? parsed.insights
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
180
|
+
? parsed.insights
|
|
181
|
+
.map((item) => ({
|
|
182
|
+
title: String(item.title || "").trim(),
|
|
183
|
+
content: String(item.content || "").trim(),
|
|
184
|
+
icon: String(item.icon || "Insight").trim(),
|
|
185
|
+
utility: ["high", "medium", "low"].includes(String(item.utility))
|
|
186
|
+
? String(item.utility)
|
|
101
187
|
: "medium",
|
|
102
|
-
tags: Array.isArray(
|
|
103
|
-
? i.tags.map(String)
|
|
104
|
-
: [],
|
|
188
|
+
tags: Array.isArray(item.tags) ? item.tags.map(String) : [],
|
|
105
189
|
}))
|
|
190
|
+
.filter((item) => item.title && item.content)
|
|
106
191
|
: [];
|
|
107
192
|
const rootCauses = Array.isArray(parsed.rootCauses)
|
|
108
|
-
? parsed.rootCauses
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
suggestion: String(r.suggestion || ""),
|
|
193
|
+
? parsed.rootCauses
|
|
194
|
+
.map((item) => ({
|
|
195
|
+
symptom: String(item.symptom || "").trim(),
|
|
196
|
+
rootCause: String(item.rootCause || "").trim(),
|
|
197
|
+
evidence: Array.isArray(item.evidence) ? item.evidence.map(String) : [],
|
|
198
|
+
suggestion: String(item.suggestion || "").trim(),
|
|
115
199
|
}))
|
|
200
|
+
.filter((item) => item.symptom && item.rootCause)
|
|
116
201
|
: [];
|
|
117
202
|
return { persona, insights, rootCauses };
|
|
118
203
|
}
|
|
@@ -120,26 +205,14 @@ function parseHumanInsightsResponse(raw) {
|
|
|
120
205
|
return null;
|
|
121
206
|
}
|
|
122
207
|
}
|
|
123
|
-
/**
|
|
124
|
-
* 生成大白话版洞察报告
|
|
125
|
-
*/
|
|
126
208
|
export async function generateHumanInsights(all, llmFn, locale) {
|
|
127
|
-
// 如果没有 LLM,返回空结果
|
|
128
209
|
if (!llmFn) {
|
|
129
210
|
return { humanInsights: [], rootCauses: [] };
|
|
130
211
|
}
|
|
131
|
-
const p = PROMPTS[locale] || PROMPTS.en;
|
|
132
212
|
const data = buildHumanInsightsData(all);
|
|
133
|
-
|
|
134
|
-
let userPrompt = p.humanInsightsUserTemplate;
|
|
135
|
-
userPrompt = userPrompt.replace("{{tools}}", data.tools);
|
|
136
|
-
userPrompt = userPrompt.replace("{{languages}}", data.languages);
|
|
137
|
-
userPrompt = userPrompt.replace("{{wins}}", data.wins);
|
|
138
|
-
userPrompt = userPrompt.replace("{{struggles}}", data.struggles);
|
|
139
|
-
userPrompt = userPrompt.replace("{{lessons}}", data.lessons);
|
|
140
|
-
userPrompt = userPrompt.replace("{{errors}}", data.errors);
|
|
213
|
+
const userPrompt = JSON.stringify({ locale, reviewData: data });
|
|
141
214
|
try {
|
|
142
|
-
const raw = await llmFn(
|
|
215
|
+
const raw = await llmFn(HUMAN_INSIGHTS_SYSTEM_PROMPT, userPrompt);
|
|
143
216
|
const parsed = parseHumanInsightsResponse(raw);
|
|
144
217
|
if (parsed) {
|
|
145
218
|
return {
|
|
@@ -150,18 +223,12 @@ export async function generateHumanInsights(all, llmFn, locale) {
|
|
|
150
223
|
}
|
|
151
224
|
}
|
|
152
225
|
catch {
|
|
153
|
-
//
|
|
226
|
+
// Fall back to empty enhanced insights when the LLM path is unavailable.
|
|
154
227
|
}
|
|
155
228
|
return { humanInsights: [], rootCauses: [] };
|
|
156
229
|
}
|
|
157
|
-
/**
|
|
158
|
-
* 将人类可读洞察合并到 FullInsightsReport 生成流程中
|
|
159
|
-
*/
|
|
160
230
|
export async function buildEnhancedInsightsReport(all, llmFn, locale) {
|
|
161
|
-
// 这个函数会在 engine.ts 中被调用来生成完整报告
|
|
162
|
-
// 目前 placeholder - 实际逻辑在对应的调用处
|
|
163
231
|
const humanData = await generateHumanInsights(all, llmFn, locale);
|
|
164
|
-
// 返回一个基础结构,实际的完整报告会在调用处构建
|
|
165
232
|
return {
|
|
166
233
|
stats: {
|
|
167
234
|
knowledge: all.knowledge.length,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pencil-agent/nano-pencil",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.8",
|
|
4
4
|
"description": "CLI writing agent with read, bash, edit, write tools and session management. Based on pi; supports DashScope Coding Plan. Soul enabled by default for AI personality evolution.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -65,11 +65,11 @@
|
|
|
65
65
|
"hosted-git-info": "^9.0.2",
|
|
66
66
|
"ignore": "^7.0.5",
|
|
67
67
|
"marked": "^15.0.12",
|
|
68
|
-
"minimatch": "^10.1.1",
|
|
69
|
-
"proper-lockfile": "^4.1.2",
|
|
70
|
-
"yaml": "^2.8.2",
|
|
71
|
-
"zod": "^4.3.6"
|
|
72
|
-
},
|
|
68
|
+
"minimatch": "^10.1.1",
|
|
69
|
+
"proper-lockfile": "^4.1.2",
|
|
70
|
+
"yaml": "^2.8.2",
|
|
71
|
+
"zod": "^4.3.6"
|
|
72
|
+
},
|
|
73
73
|
"bundledDependencies": [
|
|
74
74
|
"@pencil-agent/agent-core",
|
|
75
75
|
"@pencil-agent/ai",
|