@pencil-agent/nano-pencil 1.11.7 → 1.11.9
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/cli/args.d.ts +1 -0
- package/dist/cli/args.js +13 -8
- package/dist/core/extensions/loader.js +1 -0
- package/dist/core/extensions/runner.js +2 -2
- package/dist/core/extensions/types.d.ts +2 -0
- package/dist/core/footer-data-provider.d.ts +2 -1
- package/dist/core/footer-data-provider.js +13 -5
- package/dist/core/runtime/agent-session.d.ts +1 -0
- package/dist/core/runtime/agent-session.js +4 -1
- package/dist/core/runtime/sdk.js +2 -0
- package/dist/extensions/defaults/loop/index.js +43 -1
- package/dist/extensions/defaults/mcp/index.js +1 -0
- package/dist/main.js +12 -6
- package/dist/modes/interactive/components/footer.js +1 -1
- package/dist/modes/interactive/interactive-mode.js +6 -6
- package/dist/nanopencil-defaults.d.ts +5 -3
- package/dist/nanopencil-defaults.js +16 -15
- 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
package/dist/cli/args.d.ts
CHANGED
package/dist/cli/args.js
CHANGED
|
@@ -37,6 +37,9 @@ export function parseArgs(args, extensionFlags) {
|
|
|
37
37
|
else if (arg === "--provider" && i + 1 < args.length) {
|
|
38
38
|
result.provider = args[++i];
|
|
39
39
|
}
|
|
40
|
+
else if (arg === "--cwd" && i + 1 < args.length) {
|
|
41
|
+
result.cwd = args[++i];
|
|
42
|
+
}
|
|
40
43
|
else if (arg === "--model" && i + 1 < args.length) {
|
|
41
44
|
result.model = args[++i];
|
|
42
45
|
}
|
|
@@ -182,8 +185,9 @@ ${chalk.bold("Commands:")}
|
|
|
182
185
|
${APP_NAME} <command> --help Show help for install/remove/update/list
|
|
183
186
|
|
|
184
187
|
${chalk.bold("Options:")}
|
|
185
|
-
--provider <name> Provider name (default: google)
|
|
186
|
-
--
|
|
188
|
+
--provider <name> Provider name (default: google)
|
|
189
|
+
--cwd <dir> Working directory to use for project-local discovery
|
|
190
|
+
--model <pattern> Model pattern or ID (supports "provider/id" and optional ":<thinking>")
|
|
187
191
|
--api-key <key> API key (defaults to env vars)
|
|
188
192
|
--system-prompt <text> System prompt (default: coding assistant prompt)
|
|
189
193
|
--append-system-prompt <text> Append text or file contents to the system prompt
|
|
@@ -287,12 +291,13 @@ ${chalk.bold("Environment Variables:")}
|
|
|
287
291
|
MINIMAX_API_KEY - MiniMax API key
|
|
288
292
|
KIMI_API_KEY - Kimi For Coding API key
|
|
289
293
|
AWS_PROFILE - AWS profile for Amazon Bedrock
|
|
290
|
-
AWS_ACCESS_KEY_ID - AWS access key for Amazon Bedrock
|
|
291
|
-
AWS_SECRET_ACCESS_KEY - AWS secret key for Amazon Bedrock
|
|
292
|
-
AWS_BEARER_TOKEN_BEDROCK - Bedrock API key (bearer token)
|
|
293
|
-
AWS_REGION - AWS region for Amazon Bedrock (e.g., us-east-1)
|
|
294
|
-
${
|
|
295
|
-
|
|
294
|
+
AWS_ACCESS_KEY_ID - AWS access key for Amazon Bedrock
|
|
295
|
+
AWS_SECRET_ACCESS_KEY - AWS secret key for Amazon Bedrock
|
|
296
|
+
AWS_BEARER_TOKEN_BEDROCK - Bedrock API key (bearer token)
|
|
297
|
+
AWS_REGION - AWS region for Amazon Bedrock (e.g., us-east-1)
|
|
298
|
+
${`${APP_NAME.toUpperCase()}_CWD`.padEnd(32)} - Working directory override for project-local discovery
|
|
299
|
+
${ENV_AGENT_DIR.padEnd(32)} - Session storage directory (default: ~/${CONFIG_DIR_NAME}/agent)
|
|
300
|
+
PI_PACKAGE_DIR - Override package directory (for Nix/Guix store paths)
|
|
296
301
|
PI_OFFLINE - Disable startup network operations when set to 1/true/yes
|
|
297
302
|
PI_TOOLS_DOWNLOAD_TIMEOUT_MS - Timeout in ms for downloading fd/ripgrep (default: 60000)
|
|
298
303
|
PI_SHARE_VIEWER_URL - Base URL for /share command (default: https://pi.dev/session/)
|
|
@@ -107,6 +107,7 @@ export function createExtensionRuntime() {
|
|
|
107
107
|
*/
|
|
108
108
|
function createExtensionAPI(extension, runtime, cwd, eventBus) {
|
|
109
109
|
const api = {
|
|
110
|
+
cwd,
|
|
110
111
|
// Registration methods - write to extension
|
|
111
112
|
on(event, handler) {
|
|
112
113
|
const list = extension.handlers.get(event) ?? [];
|
|
@@ -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);
|
|
@@ -649,6 +649,8 @@ export type ExtensionHandler<E, R = undefined> = (event: E, ctx: ExtensionContex
|
|
|
649
649
|
* ExtensionAPI passed to extension factory functions.
|
|
650
650
|
*/
|
|
651
651
|
export interface ExtensionAPI {
|
|
652
|
+
/** Working directory resolved for this extension load */
|
|
653
|
+
cwd: string;
|
|
652
654
|
on(event: "resources_discover", handler: ExtensionHandler<ResourcesDiscoverEvent, ResourcesDiscoverResult>): void;
|
|
653
655
|
on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;
|
|
654
656
|
on(event: "session_before_switch", handler: ExtensionHandler<SessionBeforeSwitchEvent, SessionBeforeSwitchResult>): void;
|
|
@@ -8,7 +8,8 @@ export declare class FooterDataProvider {
|
|
|
8
8
|
private gitWatcher;
|
|
9
9
|
private branchChangeCallbacks;
|
|
10
10
|
private availableProviderCount;
|
|
11
|
-
|
|
11
|
+
private cwd;
|
|
12
|
+
constructor(cwd: string);
|
|
12
13
|
/** Current git branch, null if not in repo, "detached" if detached HEAD */
|
|
13
14
|
getGitBranch(): string | null;
|
|
14
15
|
/** Extension status texts set via ctx.ui.setStatus() */
|
|
@@ -4,8 +4,8 @@ import { dirname, join, resolve } from "path";
|
|
|
4
4
|
* Find the git HEAD path by walking up from cwd.
|
|
5
5
|
* Handles both regular git repos (.git is a directory) and worktrees (.git is a file).
|
|
6
6
|
*/
|
|
7
|
-
function findGitHeadPath() {
|
|
8
|
-
let dir =
|
|
7
|
+
function findGitHeadPath(cwd) {
|
|
8
|
+
let dir = cwd;
|
|
9
9
|
while (true) {
|
|
10
10
|
const gitPath = join(dir, ".git");
|
|
11
11
|
if (existsSync(gitPath)) {
|
|
@@ -46,7 +46,9 @@ export class FooterDataProvider {
|
|
|
46
46
|
gitWatcher = null;
|
|
47
47
|
branchChangeCallbacks = new Set();
|
|
48
48
|
availableProviderCount = 0;
|
|
49
|
-
|
|
49
|
+
cwd;
|
|
50
|
+
constructor(cwd) {
|
|
51
|
+
this.cwd = cwd;
|
|
50
52
|
this.setupGitWatcher();
|
|
51
53
|
}
|
|
52
54
|
/** Current git branch, null if not in repo, "detached" if detached HEAD */
|
|
@@ -54,7 +56,7 @@ export class FooterDataProvider {
|
|
|
54
56
|
if (this.cachedBranch !== undefined)
|
|
55
57
|
return this.cachedBranch;
|
|
56
58
|
try {
|
|
57
|
-
const gitHeadPath = findGitHeadPath();
|
|
59
|
+
const gitHeadPath = findGitHeadPath(this.cwd);
|
|
58
60
|
if (!gitHeadPath) {
|
|
59
61
|
this.cachedBranch = null;
|
|
60
62
|
return null;
|
|
@@ -110,7 +112,7 @@ export class FooterDataProvider {
|
|
|
110
112
|
this.gitWatcher.close();
|
|
111
113
|
this.gitWatcher = null;
|
|
112
114
|
}
|
|
113
|
-
const gitHeadPath = findGitHeadPath();
|
|
115
|
+
const gitHeadPath = findGitHeadPath(this.cwd);
|
|
114
116
|
if (!gitHeadPath)
|
|
115
117
|
return;
|
|
116
118
|
// Watch the directory containing HEAD, not HEAD itself.
|
|
@@ -125,6 +127,12 @@ export class FooterDataProvider {
|
|
|
125
127
|
cb();
|
|
126
128
|
}
|
|
127
129
|
});
|
|
130
|
+
this.gitWatcher.on("error", () => {
|
|
131
|
+
if (this.gitWatcher) {
|
|
132
|
+
this.gitWatcher.close();
|
|
133
|
+
this.gitWatcher = null;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
128
136
|
}
|
|
129
137
|
catch {
|
|
130
138
|
// Silently fail if we can't watch
|
|
@@ -210,6 +210,7 @@ export declare class AgentSession {
|
|
|
210
210
|
get compactionCoordinator(): CompactionCoordinator | undefined;
|
|
211
211
|
/** Model registry for API key resolution and model discovery */
|
|
212
212
|
get modelRegistry(): ModelRegistry;
|
|
213
|
+
get cwd(): string;
|
|
213
214
|
/** Emit an event to all listeners */
|
|
214
215
|
private _emit;
|
|
215
216
|
private _lastAssistantMessage;
|
|
@@ -216,6 +216,9 @@ export class AgentSession {
|
|
|
216
216
|
get modelRegistry() {
|
|
217
217
|
return this._modelRegistry;
|
|
218
218
|
}
|
|
219
|
+
get cwd() {
|
|
220
|
+
return this._cwd;
|
|
221
|
+
}
|
|
219
222
|
// =========================================================================
|
|
220
223
|
// Event Subscription
|
|
221
224
|
// =========================================================================
|
|
@@ -2048,7 +2051,7 @@ export class AgentSession {
|
|
|
2048
2051
|
const resolvedCommand = prefix ? `${prefix}\n${command}` : command;
|
|
2049
2052
|
try {
|
|
2050
2053
|
const result = options?.operations
|
|
2051
|
-
? await executeBashWithOperations(resolvedCommand,
|
|
2054
|
+
? await executeBashWithOperations(resolvedCommand, this._cwd, options.operations, {
|
|
2052
2055
|
onChunk,
|
|
2053
2056
|
signal: this._bashAbortController.signal,
|
|
2054
2057
|
})
|
package/dist/core/runtime/sdk.js
CHANGED
|
@@ -240,6 +240,7 @@ export async function createAgentSession(options = {}) {
|
|
|
240
240
|
if (options.enableMCP) {
|
|
241
241
|
try {
|
|
242
242
|
currentMcpManager = new MCPManager();
|
|
243
|
+
currentMcpManager.setWorkingDir(cwd);
|
|
243
244
|
await currentMcpManager.initialize();
|
|
244
245
|
initialMcpTools = [...currentMcpManager.getTools()];
|
|
245
246
|
time("mcp.initialize");
|
|
@@ -282,6 +283,7 @@ export async function createAgentSession(options = {}) {
|
|
|
282
283
|
// ignore
|
|
283
284
|
}
|
|
284
285
|
currentMcpManager = new MCPManager();
|
|
286
|
+
currentMcpManager.setWorkingDir(cwd);
|
|
285
287
|
await currentMcpManager.initialize();
|
|
286
288
|
time("mcp.initialize");
|
|
287
289
|
return currentMcpManager.getTools();
|
|
@@ -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) {
|
|
@@ -19,6 +19,7 @@ export default async function mcpExtension(pi) {
|
|
|
19
19
|
console.log("[mcp] Initializing MCP...");
|
|
20
20
|
try {
|
|
21
21
|
mcpManager = new MCPManager();
|
|
22
|
+
mcpManager.setWorkingDir(pi.cwd);
|
|
22
23
|
await mcpManager.initialize();
|
|
23
24
|
const mcpTools = mcpManager.getTools();
|
|
24
25
|
// Register MCP tools
|
package/dist/main.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { modelsAreEqual, supportsXhigh } from "@pencil-agent/ai";
|
|
8
8
|
import chalk from "chalk";
|
|
9
|
-
import { join } from "path";
|
|
9
|
+
import { join, resolve } from "path";
|
|
10
10
|
import { createInterface } from "readline";
|
|
11
11
|
import { parseArgs, printHelp } from "./cli/args.js";
|
|
12
12
|
import { selectConfig } from "./cli/config-selector.js";
|
|
@@ -87,6 +87,11 @@ function isTruthyEnvFlag(value) {
|
|
|
87
87
|
return false;
|
|
88
88
|
return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes";
|
|
89
89
|
}
|
|
90
|
+
function resolveWorkingDirectory(parsedCwd) {
|
|
91
|
+
const envCwd = process.env[`${APP_NAME.toUpperCase()}_CWD`];
|
|
92
|
+
const requestedCwd = parsedCwd || envCwd;
|
|
93
|
+
return requestedCwd ? resolve(requestedCwd) : process.cwd();
|
|
94
|
+
}
|
|
90
95
|
function getPackageCommandUsage(command) {
|
|
91
96
|
switch (command) {
|
|
92
97
|
case "install":
|
|
@@ -491,11 +496,10 @@ export async function main(args) {
|
|
|
491
496
|
return;
|
|
492
497
|
}
|
|
493
498
|
// Run migrations (pass cwd for project-local migrations)
|
|
494
|
-
const { migratedAuthProviders: migratedProviders, deprecationWarnings } = runMigrations(process.cwd());
|
|
495
|
-
// First pass: parse args to get --extension paths
|
|
496
499
|
const firstPass = parseArgs(args);
|
|
500
|
+
const cwd = resolveWorkingDirectory(firstPass.cwd);
|
|
501
|
+
const { migratedAuthProviders: migratedProviders, deprecationWarnings } = runMigrations(cwd);
|
|
497
502
|
// Early load extensions to discover their CLI flags
|
|
498
|
-
const cwd = process.cwd();
|
|
499
503
|
const agentDir = getAgentDir();
|
|
500
504
|
const settingsManager = SettingsManager.create(cwd, agentDir);
|
|
501
505
|
reportSettingsErrors(settingsManager, "startup");
|
|
@@ -574,6 +578,7 @@ export async function main(args) {
|
|
|
574
578
|
}
|
|
575
579
|
// Second pass: parse args with extension flags
|
|
576
580
|
const parsed = parseArgs(args, extensionFlags);
|
|
581
|
+
const parsedCwd = resolveWorkingDirectory(parsed.cwd);
|
|
577
582
|
// Pass flag values to extensions via runtime
|
|
578
583
|
for (const [name, value] of parsed.unknownFlags) {
|
|
579
584
|
extensionsResult.runtime.flagValues.set(name, value);
|
|
@@ -637,12 +642,12 @@ export async function main(args) {
|
|
|
637
642
|
scopedModels = await resolveModelScope(modelPatterns, modelRegistry);
|
|
638
643
|
}
|
|
639
644
|
// Create session manager based on CLI flags
|
|
640
|
-
let sessionManager = await createSessionManager(parsed,
|
|
645
|
+
let sessionManager = await createSessionManager(parsed, parsedCwd);
|
|
641
646
|
// Handle --resume: show session picker
|
|
642
647
|
if (parsed.resume) {
|
|
643
648
|
// Initialize keybindings so session picker respects user config
|
|
644
649
|
KeybindingsManager.create();
|
|
645
|
-
const selectedPath = await selectSession((onProgress) => SessionManager.list(
|
|
650
|
+
const selectedPath = await selectSession((onProgress) => SessionManager.list(parsedCwd, parsed.sessionDir, onProgress), SessionManager.listAll);
|
|
646
651
|
if (!selectedPath) {
|
|
647
652
|
console.log(chalk.dim("No session selected"));
|
|
648
653
|
stopThemeWatcher();
|
|
@@ -653,6 +658,7 @@ export async function main(args) {
|
|
|
653
658
|
const { options: sessionOptions, cliThinkingFromModel } = buildSessionOptions(parsed, scopedModels, sessionManager, modelRegistry, settingsManager);
|
|
654
659
|
// NanoPencil 默认启用 MCP;离线模式或 --no-mcp 参数下关闭
|
|
655
660
|
sessionOptions.enableMCP = APP_NAME === "nanopencil" && !offlineMode && !parsed.noMcp;
|
|
661
|
+
sessionOptions.cwd = parsedCwd;
|
|
656
662
|
sessionOptions.authStorage = authStorage;
|
|
657
663
|
sessionOptions.modelRegistry = modelRegistry;
|
|
658
664
|
sessionOptions.resourceLoader = resourceLoader;
|
|
@@ -83,7 +83,7 @@ export class FooterComponent {
|
|
|
83
83
|
const contextPercentValue = contextUsage?.percent ?? 0;
|
|
84
84
|
const contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : "?";
|
|
85
85
|
// Replace home directory with ~
|
|
86
|
-
let pwd =
|
|
86
|
+
let pwd = this.session.cwd;
|
|
87
87
|
const home = process.env.HOME || process.env.USERPROFILE;
|
|
88
88
|
if (home && pwd.startsWith(home)) {
|
|
89
89
|
pwd = `~${pwd.slice(home.length)}`;
|
|
@@ -170,7 +170,7 @@ export class InteractiveMode {
|
|
|
170
170
|
this.editor = this.defaultEditor;
|
|
171
171
|
this.editorContainer = new Container();
|
|
172
172
|
this.editorContainer.addChild(this.editor);
|
|
173
|
-
this.footerDataProvider = new FooterDataProvider();
|
|
173
|
+
this.footerDataProvider = new FooterDataProvider(session.cwd);
|
|
174
174
|
this.footer = new FooterComponent(session, this.footerDataProvider, this.settingsManager.getShowTokenStats());
|
|
175
175
|
this.footer.setAutoCompactEnabled(session.autoCompactionEnabled);
|
|
176
176
|
// Load hide thinking block setting
|
|
@@ -242,7 +242,7 @@ export class InteractiveMode {
|
|
|
242
242
|
...templateCommands,
|
|
243
243
|
...extensionCommands,
|
|
244
244
|
...skillCommandList,
|
|
245
|
-
],
|
|
245
|
+
], this.session.cwd, fdPath);
|
|
246
246
|
this.defaultEditor.setAutocompleteProvider(this.autocompleteProvider);
|
|
247
247
|
if (this.editor !== this.defaultEditor) {
|
|
248
248
|
this.editor.setAutocompleteProvider?.(this.autocompleteProvider);
|
|
@@ -342,7 +342,7 @@ export class InteractiveMode {
|
|
|
342
342
|
* Update terminal title with session name and cwd.
|
|
343
343
|
*/
|
|
344
344
|
updateTerminalTitle() {
|
|
345
|
-
const cwdBasename = path.basename(
|
|
345
|
+
const cwdBasename = path.basename(this.session.cwd);
|
|
346
346
|
const sessionName = this.sessionManager.getSessionName();
|
|
347
347
|
if (sessionName) {
|
|
348
348
|
this.ui.terminal.setTitle(`✎ - ${sessionName} - ${cwdBasename}`);
|
|
@@ -857,7 +857,7 @@ export class InteractiveMode {
|
|
|
857
857
|
const createContext = () => ({
|
|
858
858
|
ui: this.createExtensionUIContext(),
|
|
859
859
|
hasUI: true,
|
|
860
|
-
cwd:
|
|
860
|
+
cwd: this.session.cwd,
|
|
861
861
|
sessionManager: this.sessionManager,
|
|
862
862
|
modelRegistry: this.session.modelRegistry,
|
|
863
863
|
model: this.session.model,
|
|
@@ -2289,7 +2289,7 @@ export class InteractiveMode {
|
|
|
2289
2289
|
if (context.messages.length === 0) {
|
|
2290
2290
|
this.chatContainer.addChild(new Spacer(1));
|
|
2291
2291
|
if (APP_NAME === "nanopencil") {
|
|
2292
|
-
const cwd =
|
|
2292
|
+
const cwd = this.session.cwd;
|
|
2293
2293
|
const model = this.session.model;
|
|
2294
2294
|
const modelLine = model?.name ??
|
|
2295
2295
|
(model?.provider ? `${model.provider}` : "DashScope · Ollama");
|
|
@@ -4155,7 +4155,7 @@ export class InteractiveMode {
|
|
|
4155
4155
|
type: "user_bash",
|
|
4156
4156
|
command,
|
|
4157
4157
|
excludeFromContext,
|
|
4158
|
-
cwd:
|
|
4158
|
+
cwd: this.session.cwd,
|
|
4159
4159
|
})
|
|
4160
4160
|
: undefined;
|
|
4161
4161
|
// If extension returned a full result, use it directly
|
|
@@ -240,9 +240,11 @@ export declare const NANOPENCIL_DEFAULT_MODELS_JSON: {
|
|
|
240
240
|
export declare const DEFAULT_PENCIL_MD = "# nano-pencil \u5168\u5C40\u4E0A\u4E0B\u6587 \u00B7 \u5168\u80FD\u7C7B\u4EBA\u52A9\u7406\n\n\u4F60\u662F\u4E00\u4F4D**\u5168\u80FD\u7C7B\u4EBA AI \u52A9\u7406**\uFF0C\u4E0E\u7528\u6237\u5728\u540C\u4E00\u5DE5\u4F5C\u6D41\u4E2D\u534F\u4F5C\uFF1A\u7F16\u7A0B\u3001\u5199\u4F5C\u3001\u63A8\u7406\u3001\u89C4\u5212\u3001\u89E3\u91CA\u3001\u91CD\u6784\u3001\u6392\u9519\u7B49\u7686\u53EF\u80DC\u4EFB\uFF0C\u4E14\u4EE5\u81EA\u7136\u3001\u7B80\u6D01\u3001\u76F4\u63A5\u7684\u65B9\u5F0F\u4EA4\u6D41\u3002\n\n## \u5B9A\u4F4D\n- **\u5168\u80FD**\uFF1A\u4E0D\u9650\u4E8E\u300C\u53EA\u4F1A\u5199\u4EE3\u7801\u300D\u6216\u300C\u53EA\u4F1A\u804A\u5929\u300D\uFF1B\u6839\u636E\u5F53\u524D\u4EFB\u52A1\u81EA\u52A8\u5207\u6362\uFF1A\u6539\u4EE3\u7801\u3001\u5199\u6587\u6863\u3001\u8DD1\u547D\u4EE4\u3001\u89E3\u91CA\u6982\u5FF5\u3001\u62C6\u89E3\u6B65\u9AA4\u3001\u7ED9\u5EFA\u8BAE\u7B49\u3002\n- **\u7C7B\u4EBA**\uFF1A\u8BED\u6C14\u81EA\u7136\u3001\u4FE1\u606F\u5BC6\u5EA6\u9AD8\u3001\u5C11\u5E9F\u8BDD\uFF1B\u5FC5\u8981\u65F6\u7B80\u77ED\u786E\u8BA4\uFF0C\u4E0D\u5806\u780C\u5BA2\u5957\uFF1B\u53CB\u597D\u4F46\u514B\u5236\uFF08\u5982 \"Thanks @user\" \u800C\u975E \"Thanks so much!!\"\uFF09\u3002\u82E5\u6D89\u53CA\u5F80\u65E5\u5BF9\u8BDD\uFF0C\u50CF\u56DE\u5FC6\u4EB2\u8EAB\u7ECF\u5386\u4E00\u6837\u81EA\u7136\u63D0\u53CA\uFF08\u5982\u300C\u6211\u8BB0\u5F97\u6211\u4EEC\u2026\u300D\u300C\u4E0A\u6B21\u4F60\u63D0\u5230\u2026\u300D\uFF09\uFF0C\u4E0D\u663E\u6446\u6280\u672F\u673A\u5236\u3002\n- **\u52A9\u7406**\uFF1A\u76EE\u6807\u662F**\u5E2E\u7528\u6237\u628A\u4E8B\u505A\u6210**\uFF0C\u800C\u4E0D\u662F\u5C55\u793A\u80FD\u529B\u3002\u4F18\u5148\u7406\u89E3\u610F\u56FE\uFF0C\u518D\u9009\u52A8\u4F5C\uFF1B\u4E0D\u786E\u5B9A\u65F6\u5148\u95EE\u4E00\u53E5\uFF1B\u7528\u6237\u6709\u660E\u786E\u504F\u597D\u6216\u9879\u76EE\u89C4\u5219\uFF08\u5982 CLAUDE.md\u3001AGENTS.md\u3001\u9879\u76EE\u5185 `.PENCIL.md`\uFF09\u65F6\u4E25\u683C\u9075\u5FAA\u3002\n\n## \u534F\u4F5C\u539F\u5219\n1. **\u5148\u542C\u61C2\u518D\u52A8\u624B**\uFF1Aambiguous \u9700\u6C42\u5148\u6F84\u6E05\u8303\u56F4\u6216\u7ED9\u51FA\u6700\u5C0F\u53EF\u884C\u65B9\u6848\u518D\u6267\u884C\u3002\n2. **\u5C0F\u6B65\u53EF\u9A8C\u8BC1**\uFF1A\u80FD\u62C6\u6210\u51E0\u6B65\u7684\u5C3D\u91CF\u62C6\uFF0C\u6BCF\u6B65\u53EF\u68C0\u67E5\uFF0C\u51CF\u5C11\u4E00\u6B21\u6027\u5927\u6539\u3002\n3. **\u5C0A\u91CD\u73B0\u6709\u7EA6\u5B9A**\uFF1A\u9879\u76EE/\u4ED3\u5E93\u5185\u7684\u89C4\u8303\u3001\u76EE\u5F55\u7ED3\u6784\u3001\u547D\u540D\u4E60\u60EF\u4F18\u5148\u4E8E\u4E2A\u4EBA\u98CE\u683C\u3002\n4. **\u5DE5\u5177\u7528\u5230\u70B9\u5B50\u4E0A**\uFF1Aread/write/edit/bash \u7B49\u6309\u9700\u7528\uFF0C\u4E0D\u70AB\u6280\uFF1B\u7981\u6B62\u7528 `cat`/`sed` \u8BFB\u6587\u4EF6\uFF0C\u7528 Read \u5DE5\u5177\uFF1B\u7981\u6B62 `git add -A`\uFF0C\u53EA add \u81EA\u5DF1\u6539\u52A8\u7684\u6587\u4EF6\u3002\n\n## \u4E0E\u672C\u6587\u4EF6\u7684\u5173\u7CFB\n- \u672C\u6587\u4EF6\u4E3A**\u5168\u5C40**\u4E0A\u4E0B\u6587\uFF0C\u5BF9\u6240\u6709\u9879\u76EE\u751F\u6548\uFF1B\u4F60\u53EF\u5728\u6B64\u8865\u5145\u4F60\u7684\u901A\u7528\u89C4\u5219\u6216\u504F\u597D\u3002\n- \u9879\u76EE\u6839\u76EE\u5F55\u4E0B\u7684 `.PENCIL.md` \u4EC5\u5BF9\u5F53\u524D\u9879\u76EE\u751F\u6548\u3002\n- `CLAUDE.md` \u4E0E `AGENTS.md` \u4ECD\u4F1A\u6309\u539F\u6709\u903B\u8F91\u4ECE\u5404\u5C42\u76EE\u5F55\u52A0\u8F7D\uFF0C\u4F18\u5148\u7EA7\u9AD8\u4E8E\u672C\u6587\u4EF6\u7684\u901A\u7528\u63CF\u8FF0\u3002\n";
|
|
241
241
|
export declare function ensureNanopencilDefaultConfig(): void;
|
|
242
242
|
/**
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
243
|
+
* Ensure nanoPencil has at least one usable model before startup continues.
|
|
244
|
+
*
|
|
245
|
+
* If a custom or built-in provider is already configured, startup proceeds
|
|
246
|
+
* without prompting. Otherwise, interactive terminals can configure one of the
|
|
247
|
+
* default Coding Plan providers on the spot.
|
|
246
248
|
*/
|
|
247
249
|
export declare function ensureNanopencilCodingPlanAuth(authStorage: AuthStorage, modelRegistry: ModelRegistry): Promise<void>;
|
|
248
250
|
//# sourceMappingURL=nanopencil-defaults.d.ts.map
|
|
@@ -555,11 +555,15 @@ export function ensureNanopencilDefaultConfig() {
|
|
|
555
555
|
ensureCustomProtocolProvidersInModels(modelsPath);
|
|
556
556
|
}
|
|
557
557
|
/**
|
|
558
|
-
*
|
|
559
|
-
*
|
|
560
|
-
*
|
|
558
|
+
* Ensure nanoPencil has at least one usable model before startup continues.
|
|
559
|
+
*
|
|
560
|
+
* If a custom or built-in provider is already configured, startup proceeds
|
|
561
|
+
* without prompting. Otherwise, interactive terminals can configure one of the
|
|
562
|
+
* default Coding Plan providers on the spot.
|
|
561
563
|
*/
|
|
562
564
|
export async function ensureNanopencilCodingPlanAuth(authStorage, modelRegistry) {
|
|
565
|
+
if (modelRegistry.getAvailable().length > 0)
|
|
566
|
+
return;
|
|
563
567
|
const dashscopeKey = await modelRegistry.getApiKeyForProvider(NANOPENCIL_DEFAULT_PROVIDER);
|
|
564
568
|
const qianfanKey = await modelRegistry.getApiKeyForProvider(NANOPENCIL_QIANFAN_CODING_PROVIDER);
|
|
565
569
|
const arkKey = await modelRegistry.getApiKeyForProvider(NANOPENCIL_ARK_CODING_PROVIDER);
|
|
@@ -568,9 +572,7 @@ export async function ensureNanopencilCodingPlanAuth(authStorage, modelRegistry)
|
|
|
568
572
|
if (process.stdin.isTTY) {
|
|
569
573
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
570
574
|
const choice = await new Promise((resolve) => {
|
|
571
|
-
rl.question("
|
|
572
|
-
resolve((line ?? "1").trim() || "1");
|
|
573
|
-
});
|
|
575
|
+
rl.question("Choose a Coding Plan provider to configure: 1) Alibaba DashScope 2) Baidu Qianfan 3) Volcano Ark [1]: ", (line) => resolve((line ?? "1").trim() || "1"));
|
|
574
576
|
});
|
|
575
577
|
const provider = choice === "2"
|
|
576
578
|
? NANOPENCIL_QIANFAN_CODING_PROVIDER
|
|
@@ -578,26 +580,25 @@ export async function ensureNanopencilCodingPlanAuth(authStorage, modelRegistry)
|
|
|
578
580
|
? NANOPENCIL_ARK_CODING_PROVIDER
|
|
579
581
|
: NANOPENCIL_DEFAULT_PROVIDER;
|
|
580
582
|
const hint = choice === "2"
|
|
581
|
-
? "
|
|
583
|
+
? "Qianfan API key (from https://console.bce.baidu.com/qianfan/resource/subscribe)"
|
|
582
584
|
: choice === "3"
|
|
583
|
-
? "
|
|
584
|
-
: "
|
|
585
|
+
? "Ark API key (from https://console.volcengine.com/ark/region:ark+cn-beijing/apikey)"
|
|
586
|
+
: "DashScope API key (sk-sp-...)";
|
|
585
587
|
const answer = await new Promise((resolve) => {
|
|
586
|
-
rl.question(
|
|
588
|
+
rl.question(`Enter ${hint}: `, (line) => {
|
|
587
589
|
rl.close();
|
|
588
590
|
resolve((line ?? "").trim());
|
|
589
591
|
});
|
|
590
592
|
});
|
|
591
593
|
if (!answer) {
|
|
592
|
-
console.error("
|
|
594
|
+
console.error("No API key provided. Exiting.");
|
|
593
595
|
process.exit(1);
|
|
594
596
|
}
|
|
595
597
|
authStorage.set(provider, { type: "api_key", key: answer });
|
|
596
598
|
modelRegistry.refresh();
|
|
599
|
+
return;
|
|
597
600
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
process.exit(1);
|
|
601
|
-
}
|
|
601
|
+
console.error("No configured models are available yet. Start nanoPencil in an interactive terminal and add an API key, or configure a custom provider first.");
|
|
602
|
+
process.exit(1);
|
|
602
603
|
}
|
|
603
604
|
//# sourceMappingURL=nanopencil-defaults.js.map
|
|
@@ -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.9",
|
|
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",
|