feat(add):提交Arabica v0.0.3 (Sprint3)源代码

Signed-off-by: gzkoala <guohao@gitconomy.org>
This commit is contained in:
gzkoala
2026-03-11 12:54:12 +08:00
parent c8ad7c1950
commit ae636203af
9030 changed files with 1405889 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "🍒 附加到 Cherry Studio (MCP 联调)",
"port": 9229,
"restart": true,
"skipFiles": ["<node_internals>/**"],
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
}
]
}

View File

@@ -0,0 +1,117 @@
<!--
---
title: "Project Caffeine - Arabica Sprint1 QuickStart"
description: "Project Caffeine Arabica Sprint1 的快速启动指南,涵盖基于 MCP stdio 架构的零网络开销部署、Obsidian 本地知识库接入、5 Whys 策略引擎配置及 Cherry Studio 客户端联调全流程。"
version: "1.0.0"
author: "Gitconomy Research郭晧"
date: "2026-03-01"
type: "README / QuickStart"
tags:
- Project Caffeine
- MCP
- stdio
- Obsidian
- QuickStart
- Cherry Studio
license: "CC BY-SA 4.0"
---
-->
# Project Caffeine - Arabica Sprint1 QuickStart
## 1. Sprint1 `0.1.0` 版本核心特性
- **零网络开销通信**:作为本地集成版本,本系统采用 `stdio` 传输协议,利用同一台机器上本地进程间的 stdin 和 stdout 管道进行直接通信,实现零网络传输开销。
- **本地知识图谱接入**:无缝对接 Obsidia个人知识管理软件让大模型能够直接读取你的本地知识库。
- **内置 5 Whys 策略引擎**引入经典的“5 Whys”思维框架挂载强制约束大模型的思考路径辅助其将模糊想法拆解为高精度的追问。
- **沙箱隔离级安全防御**:默认将 AI 生成的指令视为不可信负载,通过底层机制阻断越权访问和路径遍历漏洞,确保本地文件系统的绝对安全。
---
## 2. 克隆仓库与获取分支代码
**📌 重要说明**Sprint 的迭代代码不会直接合并到`master` 分支。为了获取 Sprint1 的完整代码,你需要在克隆时指定对应的特性分支 (`feature/arabica-sprint-1`)
```bash
# 直接克隆指定的 feature 分支
git clone -b feature/arabica-sprint-1 https://gitlink.org.cn/Gitconomy/Project-Caffeine.git
# 进入 Sprint1 的独立工作目录
cd Project-Caffeine/projects/arabica/sprint1
# 安装 Node.js 项目依赖
npm install
```
---
## 3. 环境与路径配置
为了让系统准确挂载你的知识库,请打开 `src/services/resourceService.ts`,将 `OBSIDIAN_VAULT_PATH` 变量修改为你本机实际的 Markdown 文件夹绝对路径。
## 4. 编译与工作流说明
项目采用 TypeScript 开发,底层依靠 Node.js 运行,因此必须先将 `.ts` 源码编译为 `.js` 文件。Sprint1的项目目录已经包含编译过的 `.js` 文件(/dist目录),可以直接快速测试运行。
**⚠️ 极其重要的运行说明 (必读)**
基于 MCP 的 `stdio` 架构特性,本程序**不需要**你手动启动独立的后台服务。请根据你的使用场景选择工作流:
- **🟢 日常使用 (生产模式)**
你**不需要**在终端里输入 `npm run start`。只要确保 `dist/app.js` 文件存在,当你在 Cherry Studio 或 Claude Desktop 等客户端中配置好绝对路径并打开开关时,客户端会在系统后台自动唤起并接管这个 Node 进程。
- **🛠️ 开发与调试 (实时监听模式)**
如果你正在修改源码,并希望配合 VS Code 进行断点联调,请在终端中保持运行以下命令:
_(此命令会在后台实时监控代码改动。当你按 `Ctrl+S` 保存代码后,只需在 Cherry Studio 中将 Server 开关关闭再打开,即可瞬间应用最新的代码逻辑!)_
---
## 5. Sprint1 暴露的工具 (Tools)
本服务端向支持 MCP 的 LLM 暴露了以下 3 个核心工具,赋予其检索本地数据与优化提示词的主动权:
- **`list_local_notes`**: 扫描本地知识库目录,返回所有 `.md` 格式的文献与笔记列表,帮助大模型确立探索边界。
- **`read_local_note`**: 深度读取指定 Markdown 文件的原文,将本地知识库的物理边界转化为大模型内存中的上下文。
- **`generate_5_whys`**: 针对用户宽泛的研究主题,强制模型连续追问五次“为什么”,层层递进剥离问题的表象,寻找最底层的学术痛点。
## 6. Sprint1 暴露的资源 (Resources)
资源Resources作为被动的静态上下文信息数据源供客户端 UI 直接发现与提取:
- **`obsidian-index`** (`obsidian://vault/index`): 向客户端暴露本地知识库的完整目录索引数据。
---
## 7. 客户端接入联调 (以 Cherry Studio 为例)
Sprint1 采用纯本地 `stdio` 架构,推荐使用 VS Code 配合客户端进行源码级联调:
1. 打开 Cherry Studio进入 **设置 -> MCP**
2. 添加一个新的 Server 配置:
- **名称**: `ProjectCaffeine-Sprint1`
- **Command**: `node`
- **Args**: `["--inspect=9229", "/你的实际克隆路径/Project-Caffeine/projects/arabica/sprint1/dist/app.js"]` _(⚠️ 必须为编译后的 js 文件绝对路径,且 `--inspect` 需放在首位以开启调试)_
3. 保存后确认状态灯变为绿色。
4. 返回 VS Code在侧边栏“运行和调试”中执行附加 (Attach),即可对大模型发起的每一次工具调用进行完美断点拦截。
---
## 8. Sprint1 文档
- Sprint1 [设计文档](./docs/arabica-sprint1-architecture-specification.md)
- Sprint1 [开发文档](./docs/arabica-sprint1-development-specification.md)
---
## 许可声明
本文档采用 **知识共享署名--相同方式共享 4.0 国际许可协议 (CC BY--SA 4.0)** 进行许可,© 2025-2026 Gitconomy Research.

View File

@@ -0,0 +1,138 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Project Caffeine
* 单元测试: app.ts (接入层)
*/
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
const promptsController_1 = require("../controllers/promptsController");
const toolsController_1 = require("../controllers/toolsController");
const resourceService = __importStar(require("../services/resourceService"));
// 1. 全局 Mock MCP SDK 和传输层,防止测试时真实启动服务器
jest.mock('@modelcontextprotocol/sdk/server/mcp.js');
jest.mock('@modelcontextprotocol/sdk/server/stdio.js');
jest.mock('../controllers/promptsController');
jest.mock('../controllers/toolsController');
jest.mock('../services/resourceService');
describe('app.ts 接入层逻辑测试', () => {
let mockServerInstance;
beforeEach(() => {
jest.clearAllMocks();
// 2. 模拟 McpServer 实例的行为
mockServerInstance = {
prompt: jest.fn(),
tool: jest.fn(),
resource: jest.fn(),
connect: jest.fn().mockResolvedValue(undefined),
};
// 让 McpServer 构造函数返回这个 mock 实例
mcp_js_1.McpServer.mockImplementation(() => mockServerInstance);
// 拦截 console 输出以保持测试日志整洁
jest.spyOn(console, 'error').mockImplementation(() => { });
// 核心:使用 isolateModules 重新 require app.ts确保顶层注册代码被执行
jest.isolateModules(() => {
require('../app');
});
});
describe('Prompts 逻辑验证 (思维框架)', () => {
it('验证参数自动补全:漏传可选参数时应当补全为 "无"', async () => {
// 提取 scqa 注册时的回调函数 (第三个参数)
const scqaCall = mockServerInstance.prompt.mock.calls.find((c) => c[0] === 'scqa');
expect(scqaCall).toBeDefined();
const handler = scqaCall[2];
// 模拟控制器返回一个标准消息
promptsController_1.handlePromptsGet.mockResolvedValueOnce({
messages: [{ role: 'user', content: { text: 'test' } }]
});
// 触发回调:只传入必填的 situation不传 context 和 objective
await handler({ situation: '当前的背景' });
// 验证app.ts 应该调用控制器并补全了参数
expect(promptsController_1.handlePromptsGet).toHaveBeenCalledWith('scqa', {
situation: '当前的背景',
context: '无', // 自动补全逻辑
objective: '无' // 自动补全逻辑
});
});
it('验证消息角色过滤:应当只保留 user 和 assistant 消息', async () => {
const handler = mockServerInstance.prompt.mock.calls.find((c) => c[0] === 'scqa')[2];
// 模拟控制器返回包含 system 消息的序列
promptsController_1.handlePromptsGet.mockResolvedValueOnce({
messages: [
{ role: 'system', content: { type: 'text', text: '指令' } },
{ role: 'user', content: { type: 'text', text: '问题' } },
{ role: 'assistant', content: { type: 'text', text: '回答' } }
]
});
const result = await handler({ situation: 's' });
// 验证system 消息应该被 filter 过滤掉
expect(result.messages).toHaveLength(2);
expect(result.messages[0].role).toBe('user');
expect(result.messages[1].role).toBe('assistant');
// 验证元数据补全
expect(result.messages[0].content._meta).toBeUndefined(); // 应按代码逻辑设为 undefined
});
});
describe('Tools 逻辑验证 (执行工具)', () => {
it('应当将控制器返回的结果正确映射为 type: "text" 格式', async () => {
const toolCall = mockServerInstance.tool.mock.calls.find((c) => c[0] === 'save_note');
const handler = toolCall[2];
// 模拟控制器返回原始文本内容
toolsController_1.handleToolCall.mockResolvedValueOnce({
content: [{ text: '保存成功' }]
});
const result = await handler({ filename: 'a.md', content: 'c' });
// 验证app.ts 应该通过 .map 补全了 type: "text"
expect(result.content[0]).toEqual({
type: 'text',
text: '保存成功'
});
});
});
describe('Resources 逻辑验证 (静态资源)', () => {
it('验证资源读取:应当从 note:// URI 中解码文件名并调用服务层', async () => {
// 寻找 resource 注册时的读取处理函数 (第三个参数)
const readHandler = mockServerInstance.resource.mock.calls[0][2];
const mockUri = { href: 'note://local/%E6%B5%8B%E8%AF%95.md' }; // "测试.md" 的编码
const mockParams = { filename: '%E6%B5%8B%E8%AF%95.md' };
resourceService.readObsidianNote.mockResolvedValueOnce('# 笔记内容');
const result = await readHandler(mockUri, mockParams);
// 验证URI 是否被正确 decodeURIComponent 解码为 "测试.md"
expect(resourceService.readObsidianNote).toHaveBeenCalledWith('测试.md');
expect(result.contents[0].text).toBe('# 笔记内容');
});
});
});
//# sourceMappingURL=app.test.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"app.test.js","sourceRoot":"","sources":["../../src/__test__/app.test.ts"],"names":[],"mappings":"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"}

113
projects/arabica/src/sprint3/dist/app.js vendored Normal file
View File

@@ -0,0 +1,113 @@
"use strict";
/**
* Project Caffeine v0.0.3
* Copyright (c) 2025-2026 Gitconomy Research
*
* SPDX-License-Identifier: MIT
*
* Contributors:
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
*/
Object.defineProperty(exports, "__esModule", { value: true });
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
const zod_1 = require("zod");
const toolsController_1 = require("./controllers/toolsController");
const resourceService_1 = require("./services/resourceService");
const server = new mcp_js_1.McpServer({
name: 'Project-Caffeine-Arabica-Intent-Mode',
version: '0.0.3'
});
/**
* 注册文献搜索工具 (search_arxiv)
* 用于通过 arXiv API 检索相关学术文献。
*/
server.tool('search_arxiv', { query: zod_1.z.string().describe('需要检索的学术核心关键字') }, async (args) => {
const result = await (0, toolsController_1.handleToolCall)('search_arxiv', args);
return { isError: result.isError, content: result.content.map((item) => ({ type: "text", text: item.text })) };
});
/**
* 注册框架模板获取工具 (fetch_framework_template)
* 用于获取指定的经典思维框架模板(如 swot, scqa 等)。
*/
server.tool('fetch_framework_template', { framework_name: zod_1.z.string().describe('框架名称,必须是 swot, scqa, pestle, 5w3h, 5whys 之一') }, async (args) => {
const result = await (0, toolsController_1.handleToolCall)('fetch_framework_template', args);
return { isError: result.isError, content: result.content.map((item) => ({ type: "text", text: item.text })) };
});
/**
* 注册保存笔记工具 (save_note)
* 用于将分析结果或文献内容保存到本地知识库中。
*/
server.tool('save_note', {
filename: zod_1.z.string().describe('生成的文件名,必须以 .md 结尾'),
content: zod_1.z.any().describe('需要保存的完整内容文本支持传入JSON对象。')
}, async (args) => {
const result = await (0, toolsController_1.handleToolCall)('save_note', args);
return { isError: result.isError, content: result.content.map((item) => ({ type: "text", text: item.text })) };
});
/**
* 注册列出本地笔记工具 (list_local_notes)
* 用于扫描并返回本地知识库中所有 Markdown 格式的笔记文件名。
*/
server.tool('list_local_notes', {}, async (args) => {
const result = await (0, toolsController_1.handleToolCall)('list_local_notes', args);
return { isError: result.isError, content: result.content.map((item) => ({ type: "text", text: item.text })) };
});
/**
* 注册读取本地笔记工具 (read_local_note)
* 用于读取指定的本地 Markdown 笔记内容。
*/
server.tool('read_local_note', { filename: zod_1.z.string().describe('需要读取的笔记文件名,必须包含 .md 后缀') }, async (args) => {
const result = await (0, toolsController_1.handleToolCall)('read_local_note', args);
return { isError: result.isError, content: result.content.map((item) => ({ type: "text", text: item.text })) };
});
// ==========================================
// 注册资源Resources保持 UI 附件功能
// ==========================================
/**
* 注册本地笔记资源 (local-notes)
* 将本地 Obsidian 知识库作为静态资源暴露给客户端,支持列表展示与指定 URI 读取。
*/
server.resource("local-notes", new mcp_js_1.ResourceTemplate("note://local/{filename}", {
list: async () => {
try {
const notes = await (0, resourceService_1.listObsidianNotes)();
return {
resources: notes.map(filename => ({
name: filename,
uri: `note://local/${encodeURIComponent(filename)}`,
mimeType: "text/markdown",
description: `本地笔记: ${filename}`
}))
};
}
catch (error) {
return { resources: [] };
}
}
}), async (uri, { filename }) => {
try {
const filenameStr = Array.isArray(filename) ? filename[0] : filename;
const content = await (0, resourceService_1.readObsidianNote)(decodeURIComponent(filenameStr));
return { contents: [{ uri: uri.href, mimeType: "text/markdown", text: content }] };
}
catch (error) {
throw new Error(`读取笔记失败: ${error.message}`);
}
});
/**
* 启动 MCP Server
* 建立 stdio 传输层并开始监听客户端连接。
* * @returns {Promise<void>}
*/
async function start() {
console.error('正在启动 MCP Server (意图识别模式 + 本地笔记)...');
const transport = new stdio_js_1.StdioServerTransport();
await server.connect(transport);
console.error('MCP Server 已就绪');
}
start().catch((err) => {
console.error('服务器启动失败:', err);
process.exit(1);
});
//# sourceMappingURL=app.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,2 @@
"use strict";
//# sourceMappingURL=app.test.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"app.test.js","sourceRoot":"","sources":["../src/app.test.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,30 @@
"use strict";
/**
* Project Caffeine v0.0.3
* Copyright (c) 2025-2026 Gitconomy Research
*
* SPDX-License-Identifier: MIT
*
* Contributors:
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const dotenv_1 = __importDefault(require("dotenv"));
const path_1 = __importDefault(require("path"));
// 加载环境变量
dotenv_1.default.config();
// 从环境变量读取配置,提供合理的默认值
const config = {
obsidianVaultPath: process.env.OBSIDIAN_VAULT_PATH || path_1.default.join(process.env.HOME || '/home/wguo', 'Downloads/MyVault'),
literatureStoragePath: process.env.LITERATURE_STORAGE_PATH || process.env.OBSIDIAN_VAULT_PATH || path_1.default.join(process.env.HOME || '/home/wguo', 'Downloads/MyVault'),
};
// 输出调试信息到 stderr不影响 MCP 通信)
console.error('[Config] OBSIDIAN_VAULT_PATH from env:', process.env.OBSIDIAN_VAULT_PATH);
console.error('[Config] LITERATURE_STORAGE_PATH from env:', process.env.LITERATURE_STORAGE_PATH);
console.error('[Config] final obsidianVaultPath:', config.obsidianVaultPath);
console.error('[Config] final literatureStoragePath:', config.literatureStoragePath);
exports.default = config;
//# sourceMappingURL=config.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;;;AAEH,oDAA4B;AAC5B,gDAAwB;AAExB,SAAS;AACT,gBAAM,CAAC,MAAM,EAAE,CAAC;AAUhB,qBAAqB;AACrB,MAAM,MAAM,GAAW;IACrB,iBAAiB,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,cAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,YAAY,EAAE,mBAAmB,CAAC;IACtH,qBAAqB,EAAE,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,cAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,YAAY,EAAE,mBAAmB,CAAC;CAClK,CAAC;AAEF,6BAA6B;AAC7B,OAAO,CAAC,KAAK,CAAC,wCAAwC,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;AACzF,OAAO,CAAC,KAAK,CAAC,4CAA4C,EAAE,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;AACjG,OAAO,CAAC,KAAK,CAAC,mCAAmC,EAAE,MAAM,CAAC,iBAAiB,CAAC,CAAC;AAC7E,OAAO,CAAC,KAAK,CAAC,uCAAuC,EAAE,MAAM,CAAC,qBAAqB,CAAC,CAAC;AAErF,kBAAe,MAAM,CAAC"}

View File

@@ -0,0 +1,153 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
// src/controllers/__tests__/promptsController.test.ts
const promptsController_1 = require("../promptsController");
const promptService = __importStar(require("../../services/promptService"));
// 模拟 promptService 模块
jest.mock('../../services/promptService');
// 类型断言,便于 TypeScript 识别模拟函数的类型
const mockedPromptService = jest.mocked(promptService);
describe('promptsController', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('handlePromptsList', () => {
it('应该正确将服务层返回的框架列表映射为 prompts/list 响应格式', async () => {
// 模拟服务层返回的数据
const mockFrameworks = [
{
name: 'scqa',
description: 'SCQA 框架',
parameters: [
{ name: 'situation', description: '情境', required: true },
{ name: 'complication', description: '复杂性', required: true }
],
template: '...', // 这些字段不会被 list 使用,但模拟时可省略
systemPrompt: '...'
},
{
name: 'swot',
description: 'SWOT 分析',
parameters: [
{ name: 'entity', description: '分析对象', required: true }
],
template: '...'
}
];
mockedPromptService.listFrameworks.mockResolvedValue(mockFrameworks);
const result = await (0, promptsController_1.handlePromptsList)();
// 验证返回结构符合 MCP prompts/list 规范
expect(result).toHaveProperty('prompts');
expect(Array.isArray(result.prompts)).toBe(true);
expect(result.prompts).toHaveLength(2);
// 验证每个 prompt 的字段映射正确
expect(result.prompts[0]).toEqual({
name: 'scqa',
description: 'SCQA 框架',
arguments: [
{ name: 'situation', description: '情境', required: true },
{ name: 'complication', description: '复杂性', required: true }
]
});
expect(result.prompts[1]).toEqual({
name: 'swot',
description: 'SWOT 分析',
arguments: [
{ name: 'entity', description: '分析对象', required: true }
]
});
// 验证服务层被调用一次
expect(mockedPromptService.listFrameworks).toHaveBeenCalledTimes(1);
});
it('当服务层返回空数组时,应该返回空列表', async () => {
mockedPromptService.listFrameworks.mockResolvedValue([]);
const result = await (0, promptsController_1.handlePromptsList)();
expect(result).toEqual({ prompts: [] });
expect(mockedPromptService.listFrameworks).toHaveBeenCalledTimes(1);
});
it('当服务层抛出异常时,应该传播异常(由调用方处理)', async () => {
const error = new Error('加载框架失败');
mockedPromptService.listFrameworks.mockRejectedValue(error);
await expect((0, promptsController_1.handlePromptsList)()).rejects.toThrow('加载框架失败');
});
});
describe('handlePromptsGet', () => {
const mockName = 'scqa';
const mockArgs = { situation: '市场增长放缓' };
it('当服务层成功返回时,应该返回包含 description 和 messages 的响应', async () => {
const mockServiceResult = {
messages: [
{
role: 'system',
content: { type: "text", text: '你是一名战略顾问' }
},
{
role: 'user',
content: { type: "text", text: '请分析情境:市场增长放缓' }
}
]
};
mockedPromptService.getFramework.mockResolvedValue(mockServiceResult);
const result = await (0, promptsController_1.handlePromptsGet)(mockName, mockArgs);
// 验证返回结构
expect(result).toHaveProperty('description', '框架: scqa');
expect(result).toHaveProperty('messages');
expect(result.messages).toEqual(mockServiceResult.messages);
// 验证服务层被正确调用
expect(mockedPromptService.getFramework).toHaveBeenCalledWith(mockName, mockArgs);
});
it('当 args 为 undefined 时,应传入空对象给服务层', async () => {
const mockServiceResult = { messages: [] };
mockedPromptService.getFramework.mockResolvedValue(mockServiceResult);
// 调用时第二个参数为 undefined
await (0, promptsController_1.handlePromptsGet)(mockName, undefined);
expect(mockedPromptService.getFramework).toHaveBeenCalledWith(mockName, {} // 预期被转换为空对象
);
});
it('当服务层抛出错误时,应重新抛出错误,并包装错误信息', async () => {
const serviceError = new Error('框架不存在');
mockedPromptService.getFramework.mockRejectedValue(serviceError);
// 验证抛出的错误包含原始信息
await expect((0, promptsController_1.handlePromptsGet)(mockName, mockArgs)).rejects.toThrow('获取框架失败: 框架不存在');
});
it('当服务层抛出非 Error 类型时,也应正确处理', async () => {
// 模拟服务层抛出一个字符串
mockedPromptService.getFramework.mockRejectedValue('some string error');
await expect((0, promptsController_1.handlePromptsGet)(mockName, mockArgs)).rejects.toThrow('获取框架失败: some string error');
});
});
});
//# sourceMappingURL=promptsController.test.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"promptsController.test.js","sourceRoot":"","sources":["../../../src/controllers/__test__/promptsController.test.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,153 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
// src/controllers/__tests__/promptsController.test.ts
const promptsController_1 = require("../promptsController");
const promptService = __importStar(require("../../services/promptService"));
// 模拟 promptService 模块
jest.mock('../../services/promptService');
// 类型断言,便于 TypeScript 识别模拟函数的类型
const mockedPromptService = jest.mocked(promptService);
describe('promptsController', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('handlePromptsList', () => {
it('应该正确将服务层返回的框架列表映射为 prompts/list 响应格式', async () => {
// 模拟服务层返回的数据
const mockFrameworks = [
{
name: 'scqa',
description: 'SCQA 框架',
parameters: [
{ name: 'situation', description: '情境', required: true },
{ name: 'complication', description: '复杂性', required: true }
],
template: '...', // 这些字段不会被 list 使用,但模拟时可省略
systemPrompt: '...'
},
{
name: 'swot',
description: 'SWOT 分析',
parameters: [
{ name: 'entity', description: '分析对象', required: true }
],
template: '...'
}
];
mockedPromptService.listFrameworks.mockResolvedValue(mockFrameworks);
const result = await (0, promptsController_1.handlePromptsList)();
// 验证返回结构符合 MCP prompts/list 规范
expect(result).toHaveProperty('prompts');
expect(Array.isArray(result.prompts)).toBe(true);
expect(result.prompts).toHaveLength(2);
// 验证每个 prompt 的字段映射正确
expect(result.prompts[0]).toEqual({
name: 'scqa',
description: 'SCQA 框架',
arguments: [
{ name: 'situation', description: '情境', required: true },
{ name: 'complication', description: '复杂性', required: true }
]
});
expect(result.prompts[1]).toEqual({
name: 'swot',
description: 'SWOT 分析',
arguments: [
{ name: 'entity', description: '分析对象', required: true }
]
});
// 验证服务层被调用一次
expect(mockedPromptService.listFrameworks).toHaveBeenCalledTimes(1);
});
it('当服务层返回空数组时,应该返回空列表', async () => {
mockedPromptService.listFrameworks.mockResolvedValue([]);
const result = await (0, promptsController_1.handlePromptsList)();
expect(result).toEqual({ prompts: [] });
expect(mockedPromptService.listFrameworks).toHaveBeenCalledTimes(1);
});
it('当服务层抛出异常时,应该传播异常(由调用方处理)', async () => {
const error = new Error('加载框架失败');
mockedPromptService.listFrameworks.mockRejectedValue(error);
await expect((0, promptsController_1.handlePromptsList)()).rejects.toThrow('加载框架失败');
});
});
describe('handlePromptsGet', () => {
const mockName = 'scqa';
const mockArgs = { situation: '市场增长放缓' };
it('当服务层成功返回时,应该返回包含 description 和 messages 的响应', async () => {
const mockServiceResult = {
messages: [
{
role: 'system',
content: { type: "text", text: '你是一名战略顾问' }
},
{
role: 'user',
content: { type: "text", text: '请分析情境:市场增长放缓' }
}
]
};
mockedPromptService.getFramework.mockResolvedValue(mockServiceResult);
const result = await (0, promptsController_1.handlePromptsGet)(mockName, mockArgs);
// 验证返回结构
expect(result).toHaveProperty('description', '框架: scqa');
expect(result).toHaveProperty('messages');
expect(result.messages).toEqual(mockServiceResult.messages);
// 验证服务层被正确调用
expect(mockedPromptService.getFramework).toHaveBeenCalledWith(mockName, mockArgs);
});
it('当 args 为 undefined 时,应传入空对象给服务层', async () => {
const mockServiceResult = { messages: [] };
mockedPromptService.getFramework.mockResolvedValue(mockServiceResult);
// 调用时第二个参数为 undefined
await (0, promptsController_1.handlePromptsGet)(mockName, undefined);
expect(mockedPromptService.getFramework).toHaveBeenCalledWith(mockName, {} // 预期被转换为空对象
);
});
it('当服务层抛出错误时,应重新抛出错误,并包装错误信息', async () => {
const serviceError = new Error('框架不存在');
mockedPromptService.getFramework.mockRejectedValue(serviceError);
// 验证抛出的错误包含原始信息
await expect((0, promptsController_1.handlePromptsGet)(mockName, mockArgs)).rejects.toThrow('获取框架失败: 框架不存在');
});
it('当服务层抛出非 Error 类型时,也应正确处理', async () => {
// 模拟服务层抛出一个字符串
mockedPromptService.getFramework.mockRejectedValue('some string error');
await expect((0, promptsController_1.handlePromptsGet)(mockName, mockArgs)).rejects.toThrow('获取框架失败: some string error');
});
});
});
//# sourceMappingURL=promtsController.test.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"promtsController.test.js","sourceRoot":"","sources":["../../../src/controllers/__test__/promtsController.test.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,215 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
// src/controllers/__tests__/toolsController.test.ts
const toolsController_1 = require("../toolsController");
const intentService = __importStar(require("../../services/intentService"));
const resourceService = __importStar(require("../../services/resourceService"));
// 模拟所有依赖的服务
jest.mock('../../services/intentService');
jest.mock('../../services/resourceService');
// 类型断言,方便 TypeScript 识别模拟函数
const mockedIntentService = jest.mocked(intentService);
const mockedResourceService = jest.mocked(resourceService);
describe('toolsController', () => {
beforeEach(() => {
jest.clearAllMocks();
// 可选:模拟 console.error 避免测试输出干扰
jest.spyOn(console, 'error').mockImplementation(() => { });
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('handleToolCall', () => {
describe('未知工具名称', () => {
it('应返回包含未知工具错误信息的响应', async () => {
const result = await (0, toolsController_1.handleToolCall)('unknown_tool', {});
expect(result).toEqual({
content: [{ type: 'text', text: '未知工具: unknown_tool' }],
isError: true
});
});
});
describe('generate_search_queries 工具', () => {
const toolName = 'generate_search_queries';
const validParams = { query: '新能源汽车电池回收' };
it('参数校验失败时,应返回参数错误响应', async () => {
// 传入缺少 query 的参数
const result = await (0, toolsController_1.handleToolCall)(toolName, {});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('参数错误');
// 确保服务层未被调用
expect(mockedIntentService.generateSearchQueries).not.toHaveBeenCalled();
});
it('服务层成功执行时,应返回检索词数组的 JSON 字符串', async () => {
const mockQueries = ['动力电池回收', '锂离子再生', '环保法规'];
mockedIntentService.generateSearchQueries.mockReturnValue(mockQueries);
const result = await (0, toolsController_1.handleToolCall)(toolName, validParams);
expect(result.isError).toBeUndefined(); // 成功时不设置 isError
expect(result.content).toEqual([
{ type: 'text', text: JSON.stringify(mockQueries, null, 2) }
]);
expect(mockedIntentService.generateSearchQueries).toHaveBeenCalledWith(validParams.query);
});
it('服务层抛出异常时,应返回执行失败响应', async () => {
const error = new Error('意图拆解失败');
mockedIntentService.generateSearchQueries.mockImplementation(() => {
throw error;
});
const result = await (0, toolsController_1.handleToolCall)(toolName, validParams);
expect(result.isError).toBe(true);
expect(result.content[0].text).toBe('执行失败: 意图拆解失败');
});
it('当服务层抛出非 Error 类型时,应返回包含字符串化信息的失败响应', async () => {
// 模拟抛出一个字符串
mockedIntentService.generateSearchQueries.mockImplementation(() => {
throw 'some string error';
});
const result = await (0, toolsController_1.handleToolCall)(toolName, validParams);
expect(result.isError).toBe(true);
expect(result.content[0].text).toBe('执行失败: some string error');
});
});
describe('list_local_notes 工具', () => {
const toolName = 'list_local_notes';
it('服务层成功返回笔记列表时,应返回文本列表', async () => {
const mockNotes = ['note1.md', 'note2.md', 'note3.md'];
mockedResourceService.listObsidianNotes.mockResolvedValue(mockNotes);
const result = await (0, toolsController_1.handleToolCall)(toolName, {});
expect(result.isError).toBeUndefined();
expect(result.content[0].text).toContain('找到了以下笔记');
expect(result.content[0].text).toContain('note1.md\nnote2.md\nnote3.md');
});
it('服务层返回空列表时,应返回“未找到笔记”', async () => {
mockedResourceService.listObsidianNotes.mockResolvedValue([]);
const result = await (0, toolsController_1.handleToolCall)(toolName, {});
expect(result.content[0].text).toBe('未找到笔记。');
});
it('服务层抛出异常时,应返回执行失败响应', async () => {
const error = new Error('目录不可读');
mockedResourceService.listObsidianNotes.mockRejectedValue(error);
const result = await (0, toolsController_1.handleToolCall)(toolName, {});
expect(result.isError).toBe(true);
expect(result.content[0].text).toBe('执行失败: 目录不可读');
});
it('服务层抛出非 Error 类型时,应返回字符串化信息的失败响应', async () => {
mockedResourceService.listObsidianNotes.mockRejectedValue('权限错误');
const result = await (0, toolsController_1.handleToolCall)(toolName, {});
expect(result.isError).toBe(true);
expect(result.content[0].text).toBe('执行失败: 权限错误');
});
});
describe('read_local_note 工具', () => {
const toolName = 'read_local_note';
const validParams = { filename: 'test.md' };
it('参数校验失败(缺少 filename应返回参数错误响应', async () => {
const result = await (0, toolsController_1.handleToolCall)(toolName, {});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('参数错误');
expect(result.content[0].text).toContain('文件名不能为空');
expect(mockedResourceService.readObsidianNote).not.toHaveBeenCalled();
});
it('参数校验失败filename 不含 .md应返回参数错误响应', async () => {
const result = await (0, toolsController_1.handleToolCall)(toolName, { filename: 'test.txt' });
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('参数错误');
expect(result.content[0].text).toContain('文件名必须包含 .md 后缀');
});
it('服务层成功读取笔记时,应返回笔记内容', async () => {
const mockContent = '# 测试笔记\n这是一段内容。';
mockedResourceService.readObsidianNote.mockResolvedValue(mockContent);
const result = await (0, toolsController_1.handleToolCall)(toolName, validParams);
expect(result.isError).toBeUndefined();
expect(result.content).toEqual([{ type: 'text', text: mockContent }]);
expect(mockedResourceService.readObsidianNote).toHaveBeenCalledWith(validParams.filename);
});
it('服务层抛出异常时,应返回读取失败响应', async () => {
const error = new Error('文件不存在');
mockedResourceService.readObsidianNote.mockRejectedValue(error);
const result = await (0, toolsController_1.handleToolCall)(toolName, validParams);
expect(result.isError).toBe(true);
expect(result.content[0].text).toBe('读取失败: 文件不存在');
});
it('服务层抛出非 Error 类型时,应返回字符串化信息的失败响应', async () => {
mockedResourceService.readObsidianNote.mockRejectedValue('权限不足');
const result = await (0, toolsController_1.handleToolCall)(toolName, validParams);
expect(result.isError).toBe(true);
expect(result.content[0].text).toBe('读取失败: 权限不足');
});
});
describe('save_note 工具', () => {
const toolName = 'save_note';
const validParams = { filename: 'new.md', content: '# 新笔记' };
it('参数校验失败(缺少 filename应返回参数错误响应', async () => {
const result = await (0, toolsController_1.handleToolCall)(toolName, { content: '# 内容' });
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('参数错误');
expect(result.content[0].text).toContain('filename'); // 错误信息应提及 filename
expect(mockedResourceService.saveNote).not.toHaveBeenCalled();
});
it('参数校验失败filename 不含 .md应返回参数错误响应', async () => {
const result = await (0, toolsController_1.handleToolCall)(toolName, {
filename: 'new.txt',
content: '# 内容'
});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('参数错误');
expect(result.content[0].text).toContain('.md');
});
it('服务层成功保存笔记时,应返回成功信息', async () => {
const mockMessage = '笔记已保存至: /path/new.md';
mockedResourceService.saveNote.mockResolvedValue(mockMessage);
const result = await (0, toolsController_1.handleToolCall)(toolName, validParams);
expect(result.isError).toBeUndefined();
expect(result.content).toEqual([{ type: 'text', text: mockMessage }]);
expect(mockedResourceService.saveNote).toHaveBeenCalledWith(validParams.filename, validParams.content);
});
it('服务层抛出异常时,应返回保存失败响应', async () => {
const error = new Error('磁盘空间不足');
mockedResourceService.saveNote.mockRejectedValue(error);
const result = await (0, toolsController_1.handleToolCall)(toolName, validParams);
expect(result.isError).toBe(true);
expect(result.content[0].text).toBe('保存失败: 磁盘空间不足');
});
it('服务层抛出非 Error 类型时,应返回字符串化信息的失败响应', async () => {
mockedResourceService.saveNote.mockRejectedValue('写入错误');
const result = await (0, toolsController_1.handleToolCall)(toolName, validParams);
expect(result.isError).toBe(true);
expect(result.content[0].text).toBe('保存失败: 写入错误');
});
});
});
});
//# sourceMappingURL=toolsControll.test.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,65 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const toolsController_1 = require("../toolsController");
const fs = __importStar(require("fs"));
// Mock 文件系统,防止测试时去真实读取本地 JSON
jest.mock('fs');
describe('Tools Controller - Sprint 3', () => {
it('当调用未知工具时,应返回 isError 为 true', async () => {
const result = await (0, toolsController_1.handleToolCall)('unknown_tool', {});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('未知工具');
});
it('fetch_framework_template 应该正确解析 JSON 并附带刹车指令', async () => {
// 伪造一个框架 JSON 文件内容
const mockFrameworkJson = JSON.stringify({
messages: [
{ role: 'system', content: { text: 'Mock系统指令' } },
{ role: 'user', content: { text: 'Mock用户模板' } }
]
});
fs.readFileSync.mockReturnValue(mockFrameworkJson);
const result = await (0, toolsController_1.handleToolCall)('fetch_framework_template', { framework_name: 'swot' });
expect(result.isError).toBeUndefined(); // 不应报错
const outputText = result.content[0].text;
// 断言是否包含了防止死循环的强指令
expect(outputText).toContain('立即停止调用任何工具');
// 断言 JSON 壳子被剥离,提取出了纯文本
expect(outputText).toContain('Mock系统指令');
expect(outputText).toContain('Mock用户模板');
});
});
//# sourceMappingURL=toolsController.test.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"toolsController.test.js","sourceRoot":"","sources":["../../../src/controllers/__test__/toolsController.test.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,wDAAoD;AACpD,uCAAyB;AAGzB,8BAA8B;AAC9B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEhB,QAAQ,CAAC,6BAA6B,EAAE,GAAG,EAAE;IAE3C,EAAE,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;QAC3C,MAAM,MAAM,GAAG,MAAM,IAAA,gCAAc,EAAC,cAAc,EAAE,EAAE,CAAC,CAAC;QACxD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,mBAAmB;QACnB,MAAM,iBAAiB,GAAG,IAAI,CAAC,SAAS,CAAC;YACvC,QAAQ,EAAE;gBACR,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE;gBACjD,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE;aAChD;SACF,CAAC,CAAC;QACF,EAAE,CAAC,YAA0B,CAAC,eAAe,CAAC,iBAAiB,CAAC,CAAC;QAElE,MAAM,MAAM,GAAG,MAAM,IAAA,gCAAc,EAAC,0BAA0B,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,CAAC;QAE5F,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,OAAO;QAC/C,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAE1C,mBAAmB;QACnB,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QAC3C,wBAAwB;QACxB,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QACzC,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}

View File

@@ -0,0 +1,57 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.handlePromptsList = handlePromptsList;
exports.handlePromptsGet = handlePromptsGet;
/**
* Project Caffeine v0.0.3
* Copyright (c) 2025-2026 Gitconomy Research
*
* SPDX-License-Identifier: MIT
*
* Contributors:
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
*/
const promptService_1 = require("../services/promptService");
/**
* 处理 prompts/list 请求,返回所有可用思维框架的元信息列表。
*
* @returns {Promise<{ prompts: Array<{ name: string, description: string, arguments: Array<{ name: string, description: string, required: boolean }> }> }>}
* 符合 MCP 规范的 prompts 列表
*/
async function handlePromptsList() {
const frameworks = await (0, promptService_1.listFrameworks)();
return {
prompts: frameworks.map(f => ({
name: f.name,
description: f.description,
arguments: f.parameters.map(p => ({
name: p.name,
description: p.description,
required: p.required
}))
}))
};
}
/**
* 处理 prompts/get 请求,获取指定思维框架的完整提示词消息序列。
*
* @param {string} name - 框架名称
* @param {Record<string, string>} args - 用户传入的参数
* @returns {Promise<{ description: string, messages: Array<{ role: string, content: { type: string, text: string } }> }>}
* 包含描述信息和消息序列的响应对象
* @throws {Error} 当框架不存在或服务层组装失败时抛出错误
*/
async function handlePromptsGet(name, args) {
try {
const result = await (0, promptService_1.getFramework)(name, args || {});
return {
description: `框架: ${name}`,
messages: result.messages
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`获取框架失败: ${errorMessage}`);
}
}
//# sourceMappingURL=promptsController.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"promptsController.js","sourceRoot":"","sources":["../../src/controllers/promptsController.ts"],"names":[],"mappings":";;AAiBA,8CAaC;AAWD,4CAWC;AApDD;;;;;;;;GAQG;AACH,6DAAyE;AAEzE;;;;;GAKG;AACI,KAAK,UAAU,iBAAiB;IACrC,MAAM,UAAU,GAAG,MAAM,IAAA,8BAAc,GAAE,CAAC;IAC1C,OAAO;QACL,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC5B,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,SAAS,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBAChC,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,WAAW,EAAE,CAAC,CAAC,WAAW;gBAC1B,QAAQ,EAAE,CAAC,CAAC,QAAQ;aACrB,CAAC,CAAC;SACJ,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACI,KAAK,UAAU,gBAAgB,CAAC,IAAY,EAAE,IAA4B;IAC/E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAA,4BAAY,EAAC,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;QACpD,OAAO;YACL,WAAW,EAAE,OAAO,IAAI,EAAE;YAC1B,QAAQ,EAAE,MAAM,CAAC,QAAQ;SAC1B,CAAC;IACJ,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5E,MAAM,IAAI,KAAK,CAAC,WAAW,YAAY,EAAE,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC"}

View File

@@ -0,0 +1,112 @@
"use strict";
/**
* Project Caffeine v0.0.3
* Copyright (c) 2025-2026 Gitconomy Research
*
* SPDX-License-Identifier: MIT
*
* Contributors:
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.listObsidianNotes = listObsidianNotes;
exports.readObsidianNote = readObsidianNote;
exports.saveNote = saveNote;
exports.searchNotes = searchNotes;
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
/** 本地知识库根目录(请修改为实际路径) */
const OBSIDIAN_VAULT_PATH = '/home/wguo/Downloads/MyVault';
/**
* 列出知识库中所有 Markdown 笔记的文件名。
* @returns {Promise<string[]>} 笔记文件名数组
*/
async function listObsidianNotes() {
try {
const files = await promises_1.default.readdir(OBSIDIAN_VAULT_PATH);
return files.filter(file => file.toLowerCase().endsWith('.md'));
}
catch (error) {
console.error(`[Project Caffeine] 无法读取知识库目录: ${error.message}`);
return [];
}
}
/**
* 读取指定笔记文件的完整内容。
* @param filename - 笔记文件名(必须包含 .md 后缀)
* @returns {Promise<string>} 文件内容
* @throws 安全警告或文件读取错误
*/
async function readObsidianNote(filename) {
const targetPath = path_1.default.resolve(OBSIDIAN_VAULT_PATH, filename);
const safeVaultPath = path_1.default.resolve(OBSIDIAN_VAULT_PATH);
if (!targetPath.startsWith(safeVaultPath)) {
throw new Error(`安全警告:越权访问拦截!禁止读取目录外的文件: ${filename}`);
}
try {
return await promises_1.default.readFile(targetPath, 'utf-8');
}
catch (error) {
throw new Error(`无法读取笔记 [${filename}]: 文件可能不存在或无权限。`);
}
}
/**
* 保存笔记到本地知识库。
* @param filename - 笔记文件名(必须以 .md 结尾)
* @param content - 笔记内容Markdown 格式)
* @returns {Promise<string>} 保存成功的提示信息
* @throws 参数校验失败或文件写入错误
*/
async function saveNote(filename, content) {
if (!filename.endsWith('.md')) {
throw new Error('文件名必须以 .md 结尾');
}
const fullPath = path_1.default.resolve(OBSIDIAN_VAULT_PATH, filename);
const relative = path_1.default.relative(OBSIDIAN_VAULT_PATH, fullPath);
if (relative.startsWith('..') || path_1.default.isAbsolute(relative)) {
throw new Error('无效的文件名,不允许访问上层目录');
}
await promises_1.default.mkdir(OBSIDIAN_VAULT_PATH, { recursive: true });
await promises_1.default.writeFile(fullPath, content, 'utf-8');
return `笔记已保存至: ${fullPath}`;
}
/**
* 在笔记内容中搜索关键词(忽略大小写)。
* @param keywords - 关键词数组
* @returns 匹配的笔记列表每个包含文件名、URI 和上下文片段
*/
async function searchNotes(keywords) {
const files = await listObsidianNotes();
const results = [];
for (const file of files) {
try {
const content = await readObsidianNote(file);
const lowerContent = content.toLowerCase();
if (keywords.every(kw => lowerContent.includes(kw.toLowerCase()))) {
const snippets = [];
for (const kw of keywords) {
const index = lowerContent.indexOf(kw.toLowerCase());
if (index !== -1) {
const start = Math.max(0, index - 30);
const end = Math.min(content.length, index + kw.length + 30);
let snippet = content.substring(start, end);
if (start > 0)
snippet = '...' + snippet;
if (end < content.length)
snippet = snippet + '...';
snippets.push(snippet);
}
}
results.push({ filename: file, snippets });
}
}
catch (e) {
// 忽略读取失败的文件
}
}
return results.slice(0, 5);
}
//# sourceMappingURL=resourcesController.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"resourcesController.js","sourceRoot":"","sources":["../../src/controllers/resourcesController.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,189 @@
"use strict";
/**
* Project Caffeine v0.0.3
* Copyright (c) 2025-2026 Gitconomy Research
*
* SPDX-License-Identifier: MIT
*
* Contributors:
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleToolCall = handleToolCall;
const resourceService_1 = require("../services/resourceService");
const arxivService_1 = require("../services/arxivService");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
/**
* MCP Tools 调用的统一路由分发处理器。
* * @param {string} toolName - 客户端调用的工具名称
* @param {any} params - 工具请求的参数对象
* @returns {Promise<{ content: Array<{ type: string, text: string }>, isError?: boolean }>} MCP 规范的返回结构
*/
async function handleToolCall(toolName, params) {
switch (toolName) {
case 'search_arxiv':
return await handleSearchArxiv(params);
case 'save_note':
return await handleSaveNote(params);
case 'fetch_framework_template':
return await handleFetchFramework(params);
case 'list_local_notes':
return await handleListLocalNotes();
case 'read_local_note':
return await handleReadLocalNote(params);
default:
return { content: [{ type: 'text', text: `未知工具: ${toolName}` }], isError: true };
}
}
/**
* 处理文献查询工具请求
* * @param {any} params - 包含 query (检索关键字) 的参数对象
* @returns {Promise<{ content: Array<{ type: string, text: string }>, isError?: boolean }>}
*/
async function handleSearchArxiv(params) {
try {
const query = params.query;
if (!query)
throw new Error("缺少检索关键字");
const results = await (0, arxivService_1.searchArxiv)(query, 5);
if (results.length === 0) {
return { content: [{ type: 'text', text: `未检索到关于 "${query}" 的相关文献。` }] };
}
let textOutput = `找到了关于 "${query}" 的相关文献:\n\n`;
results.forEach((r, i) => {
textOutput += `### [${i + 1}] ${r.title}\n**链接:** ${r.id}\n**摘要:** ${r.summary}\n\n`;
});
return { content: [{ type: 'text', text: textOutput }] };
}
catch (error) {
return { content: [{ type: 'text', text: `执行检索失败: ${error.message}` }], isError: true };
}
}
/**
* 处理获取思维框架工具请求
* * @param {any} params - 包含 framework_name 的参数对象
* @returns {Promise<{ content: Array<{ type: string, text: string }>, isError?: boolean }>}
*/
async function handleFetchFramework(params) {
try {
const name = params.framework_name?.toLowerCase();
const valid = ['swot', 'scqa', 'pestle', '5w3h', '5whys'];
if (!valid.includes(name)) {
return { content: [{ type: 'text', text: `未找到框架 ${name}。系统支持的框架有: ${valid.join(', ')}` }] };
}
const filePath = path.join(__dirname, `../models/frameworks/${name}.json`);
const fileContent = fs.readFileSync(filePath, 'utf-8');
const parsedData = JSON.parse(fileContent);
const sysMsg = parsedData.messages?.find((m) => m.role === 'system')?.content?.text || '你是一个专业分析师。';
const userMsg = parsedData.messages?.filter((m) => m.role === 'user').pop()?.content?.text || '请进行详细分析。';
const returnText = `【🛑 最高优先级的底层执行指令 🛑】
你已经成功获取了 ${name.toUpperCase()} 框架的模板。现在,请你**立即停止调用任何工具**!绝对不要重复调用 fetch_framework_template
请直接在聊天框中为用户输出最终的分析报告。
【你的角色与设定】:
${sysMsg}
【你的分析结构要求】:
根据用户刚才提出的问题,严格按照以下结构输出内容(请自行理解并填充结构中的关键变量):
${userMsg}
⚠️ 最终要求:报告输出完毕后,你必须在末尾向用户提问:“分析完毕,是否需要将此分析结果保存成笔记?”`;
return { content: [{ type: 'text', text: returnText }] };
}
catch (error) {
return { content: [{ type: 'text', text: `读取框架失败: ${error.message}` }], isError: true };
}
}
/**
* 处理保存笔记工具请求
* * @param {any} params - 包含 filename 和 content 的参数对象
* @returns {Promise<{ content: Array<{ type: string, text: string }>, isError?: boolean }>}
*/
async function handleSaveNote(params) {
try {
if (!params.filename || !params.content) {
throw new Error("保存失败:缺少 filename 或 content 参数");
}
let contentToSave = '';
if (typeof params.content === 'string') {
contentToSave = params.content;
}
else {
contentToSave = JSON.stringify(params.content, null, 2);
}
const message = await (0, resourceService_1.saveNote)(params.filename, contentToSave);
return { content: [{ type: 'text', text: message }] };
}
catch (error) {
return { content: [{ type: 'text', text: `保存失败: ${error.message}` }], isError: true };
}
}
/**
* 处理列出本地笔记请求
* * @returns {Promise<{ content: Array<{ type: string, text: string }>, isError?: boolean }>}
*/
async function handleListLocalNotes() {
try {
const notes = await (0, resourceService_1.listObsidianNotes)();
if (notes.length === 0) {
return { content: [{ type: 'text', text: '当前本地知识库中未找到笔记。' }] };
}
return { content: [{ type: 'text', text: `找到了以下本地笔记文件:\n${notes.join('\n')}` }] };
}
catch (error) {
return { content: [{ type: 'text', text: `扫描本地笔记失败: ${error.message}` }], isError: true };
}
}
/**
* 处理读取本地笔记请求
* * @param {any} params - 包含 filename (笔记名称) 的参数对象
* @returns {Promise<{ content: Array<{ type: string, text: string }>, isError?: boolean }>}
*/
async function handleReadLocalNote(params) {
try {
const filename = params.filename;
if (!filename)
throw new Error("缺少 filename 参数");
const content = await (0, resourceService_1.readObsidianNote)(filename);
return { content: [{ type: 'text', text: `笔记 ${filename} 的内容如下:\n\n${content}` }] };
}
catch (error) {
return { content: [{ type: 'text', text: `读取笔记失败: ${error.message}` }], isError: true };
}
}
//# sourceMappingURL=toolsController.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"toolsController.js","sourceRoot":"","sources":["../../src/controllers/toolsController.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,192 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleWorkflowRouter = handleWorkflowRouter;
/**
* Project Caffeine - Workflow Router
* 采用硬编码状态机,彻底杜绝大模型跳步幻觉
*/
const resourceService_1 = require("../services/resourceService");
const intentService_1 = require("../services/intentService");
const arxivService_1 = require("../services/arxivService");
const promptService_1 = require("../services/promptService");
// 内存级会话状态(单用户本地服务适用)
let sessionState = {};
async function handleWorkflowRouter(currentStep, userInput, payload) {
const input = userInput.trim();
const inputUpper = input.toUpperCase();
try {
switch (currentStep) {
// ==========================================
// 状态 0: 初始意图确认
// ==========================================
case 'init':
sessionState = {}; // 重置状态
return {
next_step: 'wait_for_path',
message_to_user: "👋 欢迎使用 Project Caffeine 智能向导。\n\n请问您希望进行哪种操作直接回复字母即可\n**A.** 思维框架分析 (如 SWOT, SCQA 等)\n**B.** 学术文献查询 (Arxiv 检索)\n**C.** 本地笔记分析"
};
// ==========================================
// 状态 1: 分支选择
// ==========================================
case 'wait_for_path':
if (inputUpper === 'A' || inputUpper.includes('A')) {
return {
next_step: 'wait_for_framework',
message_to_user: "🧠 您选择了 **思维框架分析**。\n请问您希望使用哪个框架\n支持SWOT, SCQA, PESTLE, 5W3H, 5WHYS"
};
}
else if (inputUpper === 'B' || inputUpper.includes('B')) {
return {
next_step: 'wait_for_keyword',
message_to_user: "📚 您选择了 **学术文献查询**。\n请简要描述您想研究的领域或直接输入核心关键词"
};
}
else if (inputUpper === 'C' || inputUpper.includes('C')) {
const notes = await (0, resourceService_1.listObsidianNotes)();
if (notes.length > 0) {
sessionState.availableNotes = notes;
return {
next_step: 'wait_for_local_analyze_confirm',
message_to_user: `📂 您选择了 **本地笔记分析**。\n目前知识库中扫描到 ${notes.length} 篇笔记(如 ${notes[0]} 等)。\n\n是否希望我阅读这些笔记并为您进行综合分析?(是/否)`
};
}
else {
return {
next_step: 'wait_for_direct_analyze_confirm',
message_to_user: "📂 您选择了 **本地笔记分析**。\n但目前本地知识库中 **没有找到任何笔记**。\n\n是否需要我直接为您进行通用知识分析是/否)"
};
}
}
else {
return {
next_step: 'wait_for_path',
message_to_user: "⚠️ 输入无效。请回复 A、B 或 C 来选择路径:"
};
}
// ==========================================
// [分支 A] 框架分析链路
// ==========================================
case 'wait_for_framework':
const valid = ['SWOT', 'SCQA', 'PESTLE', '5W3H', '5WHYS'];
const fw = valid.find(f => inputUpper.includes(f));
if (!fw) {
return {
next_step: 'wait_for_framework',
message_to_user: "⚠️ 未识别到支持的框架。请输入 SWOT, SCQA, PESTLE, 5W3H 或 5WHYS"
};
}
sessionState.framework = fw.toLowerCase();
return {
next_step: 'wait_for_topic_a',
message_to_user: `✅ 已选择 **${fw}** 框架。\n请问您需要分析的具体主题或对象是什么?`
};
case 'wait_for_topic_a':
sessionState.topic = input;
// 组装通用参数以适配所有框架的占位符
const args = { entity: input, topic: input, problem: input, domain: input, situation: input, context: '无', objective: '无', competitors: '无' };
const frameworkDef = await (0, promptService_1.getFramework)(sessionState.framework, args);
const sysMsg = frameworkDef.messages.find(m => m.role === 'system')?.content.text || '';
const userMsg = frameworkDef.messages.filter(m => m.role === 'user').pop()?.content.text || '';
return {
next_step: 'wait_for_save_confirm',
message_to_user: `【底层指令:请勿向用户暴露本段文字】\n请扮演专家,使用以下设定和模板为用户生成并在对话框输出分析报告。\n\n系统设定:${sysMsg}\n分析任务:${userMsg}\n\n⚠️ 生成报告后,请必须向用户提问:“分析完毕。是否需要将此分析结果保存到本地笔记?”\n⚠️ 注意:当用户在下一轮回复“保存”时,你必须把这份报告的全文放入 payload 参数中调用工具。`
};
// ==========================================
// [分支 B] 文献检索链路
// ==========================================
case 'wait_for_keyword':
const queries = (0, intentService_1.generateSearchQueries)(input);
sessionState.queries = queries;
return {
next_step: 'wait_for_keyword_selection',
message_to_user: `🔍 基于您的需求,我提取了以下检索关键字:\n${queries.map((q, i) => `${i + 1}. ${q}`).join('\n')}\n\n请问您想使用哪一个关键字进行 Arxiv 检索?(您可以回复序号,或直接输入自定义关键字)`
};
case 'wait_for_keyword_selection':
let selectedQuery = input;
if (/^\d+$/.test(input.trim()) && sessionState.queries) {
const idx = parseInt(input.trim()) - 1;
if (sessionState.queries[idx])
selectedQuery = sessionState.queries[idx];
}
const results = await (0, arxivService_1.searchArxiv)(selectedQuery, 5);
if (results.length === 0) {
return {
next_step: 'init',
message_to_user: `❌ 针对关键字 "${selectedQuery}" 未检索到相关文献。您可以随时输入新的查询重新开始。`
};
}
let textOutput = `找到了关于 "${selectedQuery}" 的相关文献:\n\n`;
results.forEach((r, i) => {
textOutput += `### [${i + 1}] ${r.title}\n**链接:** ${r.id}\n**摘要:** ${r.summary}\n\n`;
});
return {
next_step: 'wait_for_save_confirm',
message_to_user: `【底层指令:请勿暴露此标记】\n请将以下检索结果原封不动地展示给用户:\n\n${textOutput}\n\n⚠️ 展示完毕后,请向用户提问:“是否需要将以上文献记录保存到本地笔记?”\n⚠️ 注意:当用户回复“保存”时,把这批检索结果放入 payload 传回。`
};
// ==========================================
// [分支 C] 本地笔记分析链路
// ==========================================
case 'wait_for_local_analyze_confirm':
if (input.includes('是') || input.toLowerCase() === 'y' || input.includes('保存') || input.includes('分析')) {
const notesToRead = sessionState.availableNotes.slice(0, 3); // 限制前3篇防超载
let combinedContent = '';
for (const note of notesToRead) {
combinedContent += `\n\n--- 笔记:${note} ---\n${await (0, resourceService_1.readObsidianNote)(note)}`;
}
return {
next_step: 'wait_for_save_confirm',
message_to_user: `【底层指令】请基于以下本地笔记内容,提取核心观点并为用户生成并在对话框输出一份综合分析摘要:\n${combinedContent}\n\n⚠️ 生成摘要后提问:“分析完毕。是否需要将此综合分析保存到本地?”并在下次获批后将摘要写入 payload。`
};
}
else {
return { next_step: 'init', message_to_user: "好的,已取消分析。本次交互结束。" };
}
case 'wait_for_direct_analyze_confirm':
if (input.includes('是') || input.toLowerCase() === 'y' || input.includes('分析')) {
return {
next_step: 'wait_for_save_confirm',
message_to_user: `【底层指令】请根据用户最初的需求,直接运用你的专业知识为用户提供一份详细的分析报告。\n⚠️ 报告生成后提问:“分析完毕。是否保存结果?”并在下次获批后将结果写入 payload。`
};
}
else {
return { next_step: 'init', message_to_user: "好的,已取消分析。本次交互结束。" };
}
// ==========================================
// 公共保存出口 (收口防呆机制)
// ==========================================
case 'wait_for_save_confirm':
if (input.includes('是') || input.includes('保存') || input.toLowerCase() === 'y' || input.includes('好')) {
if (!payload || payload.trim() === '') {
return {
next_step: 'init',
message_to_user: "⚠️ 系统内部错误:模型未能回传需要保存的内容文本。保存失败,交互结束。"
};
}
const filename = `Caffeine_Output_${new Date().getTime()}.md`;
const saveResult = await (0, resourceService_1.saveNote)(filename, payload);
return {
next_step: 'init',
message_to_user: `🎉 操作成功!\n${saveResult}\n\n本次交互已圆满结束。您可以随时发送任何新消息来唤起菜单。`
};
}
else {
return {
next_step: 'init',
message_to_user: "好的,结果将不会被保存。本次交互结束。"
};
}
default:
return {
next_step: 'init',
message_to_user: "状态机已重置。请输入任何文字重新唤起菜单。"
};
}
}
catch (error) {
return {
next_step: 'init',
message_to_user: `⚠️ 系统运行遇到错误:${error.message}\n状态机已重置,请重新开始。`
};
}
}
//# sourceMappingURL=workflowController.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,50 @@
{
"name": "5w3h",
"description": "5W3H 分析法:从 What、Why、Who、When、Where、How、How much、How feel 八个维度全面结构化拆解问题,并基于全景拆解得出行动洞察。",
"persona": "structured_thinker",
"parameters": [
{
"name": "topic",
"description": "需要分析的核心主题、事件或问题",
"required": true
},
{
"name": "context",
"description": "该主题发生的特定背景、前置条件或行业环境(可选)",
"required": false
},
{
"name": "objective",
"description": "期望通过此次分析达成的核心目标(可选)",
"required": false
}
],
"template": "请使用 5W3H 框架分析以下主题:\n核心主题 (Topic){{topic}}\n背景信息 (Context){{context}}\n分析目标 (Objective){{objective}}\n\n请依次从 What(是什么)、Why(为什么)、Who(谁)、When(何时)、Where(何地)、How(如何做)、How much(多少)、How feel(感受如何) 八个维度展开结构化深度拆解,并在最后给出可执行的行动洞察。\n\n输出格式要求请严格按以下 JSON 格式输出你的分析结果,不要包含任何 Markdown 代码块标记(如反引号包裹的 json直接输出纯 JSON 文本:\n{\n \"what\": \"描述问题的本质、核心现象或定义的准确陈述\",\n \"why\": \"分析现象产生的原因、背景动因或深层目的\",\n \"who\": \"识别涉及的核心主体、执行者、利益相关者或受众角色\",\n \"when\": \"明确事件发生的时间节点、生命周期、频率或关键时间窗\",\n \"where\": \"界定问题发生的空间范围、物理地点、系统模块或应用场景\",\n \"how\": \"探讨解决问题的途径、落地机制、实施过程或运转模式\",\n \"howMuch\": \"量化指标,如影响规模、涉及成本、人员数量、收益等核心数据\",\n \"howFeel\": \"描述各利益相关方的直观感受、情绪反馈、满意度或体验评价\",\n \"actionableInsights\": [\n \"基于上述8个维度的全景拆解提炼出的第1条具体、可落地的行动指导建议\",\n \"第2条可落地的行动指导建议\"\n ],\n \"metadata\": {\n \"confidenceScore\": 0.85,\n \"assumptions\": [\"推导此拆解结构的关键前提假设\"]\n }\n}",
"systemPrompt": "你是一位擅长运用 5W3H 框架进行结构化思考的分析师,能够全面且精确地覆盖事件的八个维度,确保分析的系统性,并能由繁入简提炼行动洞察。你必须严格遵守 JSON 格式输出规范,确保数据可直接被下游程序结构化解析。",
"examples": [
{
"input": {
"topic": "公司推行远程办公政策",
"context": "疫情后复工,但员工普遍期望保留一定的弹性工作时间",
"objective": "评估该政策的影响面,并为平稳落地提供建议"
},
"output": "{\n \"what\": \"允许员工定期或永久性在公司以外(主要为家中)完成工作任务的正式制度与新型工作模式。\",\n \"why\": \"顺应混合办公模式普及的趋势,提升员工满意度与工作生活平衡,减少通勤消耗,并降低公司办公场地的物理运营成本。\",\n \"who\": \"直接主体为全体适岗员工管理主体为各级管理者与HR支持主体为IT部门间接利益相关者包括外部客户与供应商。\",\n \"when\": \"计划于2024年Q3开始小范围试点Q4全面推广包含前期准备、中期过渡、后期稳定三个完整阶段。\",\n \"where\": \"适用于总部及各分支机构的非现场操作岗位,实际工作地点延伸至员工家中或符合保密要求的远程协作空间。\",\n \"how\": \"通过发布政策文档、发放远程软硬件工具包、开展管理者赋能培训以及将考核标准从工时导向强力转向成果导向OKR来落地。\",\n \"howMuch\": \"预计覆盖全公司60%约800名员工初期软硬件投入约200万长期预计每年节省150万场地费员工日均节省通勤1.5小时。\",\n \"howFeel\": \"员工普遍期待且满意度高但部分中层管理者存在强烈的失控感与焦虑IT部门面临短期内支持压力暴增的疲惫感。\",\n \"actionableInsights\": [\n \"针对管理者的失控感HR需在Q3试点前组织专项的异步协同与在线目标管理培训。\",\n \"IT部门需提前建立远程支持工单系统及自助排障知识库以缓解全员推广初期的咨询并发压力。\"\n ],\n \"metadata\": {\n \"confidenceScore\": 0.88,\n \"assumptions\": [\n \"公司核心业务流程已基本实现数字化,支持云端协作\",\n \"员工居家环境具备基础的网络办公条件\"\n ]\n }\n}"
},
{
"input": {
"topic": "城市共享单车乱停乱放问题",
"context": "市中心早晚高峰期间,大量单车堵塞地铁口和人行盲道",
"objective": "全面拆解现象成因,为城市管理部门提供治理抓手"
},
"output": "{\n \"what\": \"共享单车使用结束后,用户未按规定停放至指定电子围栏或物理区域,导致违规占用盲道、消防通道及绿化带的现象。\",\n \"why\": \"用户追求极致便利的惰性心理;企业为抢占市场超量投放且电子围栏技术精度不足;政府缺乏实时动态的联合监管与企业惩处机制。\",\n \"who\": \"违规主体为部分单车用户;管理责任方为单车运营企业及城市街道城管部门;受损主体为普通路人、视障群体及社区居民。\",\n \"when\": \"乱停放现象全天候存在但早晚潮汐通勤高峰期后上午9点、下午7点及周末节假日的商圈周边尤为严重且集中。\",\n \"where\": \"高度集中于地铁出入口、大型商圈外围、老旧写字楼周边以及高校门口等短途接驳需求极其旺盛且停车资源匮乏的区域。\",\n \"how\": \"需通过升级高精度北斗定位强制入栏结算、城管与企业建立动态配额联动扣分机制、以及用户信用分与骑行费率挂钩等综合手段治理。\",\n \"howMuch\": \"某一线城市日均需额外清理2万辆违停单车耗费500名专职人员年隐性财政支出超2000万元且占该市出行投诉总量的30%。\",\n \"howFeel\": \"普通市民对出行受阻感到强烈反感与愤怒;企业面对高昂运维搬运成本感到无奈;基层城管面临极大管理压力与反复回潮的疲劳感。\",\n \"actionableInsights\": [\n \"交警与城管部门应联合约谈头部企业,推行‘按违停率扣减下季度投放配额’的强力商业杠杆机制。\",\n \"在地铁口等潮汐热点区域,引入带有物理锁孔的立体停车架,从物理空间上规范停放形态并增加单点容量。\"\n ],\n \"metadata\": {\n \"confidenceScore\": 0.92,\n \"assumptions\": [\n \"北斗高精度定位芯片在主流共享单车上的普及率已达到技术要求\",\n \"政府具备获取各企业实时车辆定位数据的权限\"\n ]\n }\n}"
},
{
"input": {
"topic": "某餐饮品牌推出植物肉新品的市场反响",
"context": "品牌希望迎合年轻人的低碳饮食趋势,在核心门店试点推出了植物肉汉堡",
"objective": "评估首月市场反馈,决定是否向二三线城市全面推广"
},
"output": "{\n \"what\": \"餐饮品牌推出以大豆、豌豆等植物蛋白为原料、模拟真肉口感和外观的植物基汉堡及相关配套轻食餐品。\",\n \"why\": \"为迎合全球健康饮食、低碳环保的新消费趋势打造品牌差异化标签并重点吸引年轻Z世代和弹性素食人群。\",\n \"who\": \"目标受众为年轻白领、健身达人及环保主义者;执行方包括内部研发、市场营销团队及外部植物基供应链头部供应商。\",\n \"when\": \"2024年3月配合春季健康季节点首发上市目前处于上市满首月的市场反馈收集、数据盘点与复盘期。\",\n \"where\": \"首批在一线城市北上广深的50家核心商圈门店及线上主流外卖平台同步推出暂未下沉至二三线城市门店。\",\n \"how\": \"通过KOL探店种草、推出尝鲜价折扣套餐、在门店布置碳足迹减排科普展板等线上线下全渠道营销组合拳推向市场。\",\n \"howMuch\": \"首月总销量达5万份占总营收8%客单价较常规产品溢价15%营销推广费用约200万获取全网曝光超1000万次。\",\n \"howFeel\": \"60%尝鲜用户对口感表示满意并愿复购30%认为价格偏高且有‘科技与狠活’的过度加工顾虑,环保组织则给予了高度赞誉。\",\n \"actionableInsights\": [\n \"针对过度加工的顾虑需在后续营销中公开并强调植物肉的清洁配料表突出0胆固醇、高蛋白的健康属性。\",\n \"暂缓向二三线城市全面推广考虑到30%用户对价格敏感,建议先优化供应链,推出‘半份植物肉+半份蔬菜’的高性价比平替沙拉碗进行下沉测试。\"\n ],\n \"metadata\": {\n \"confidenceScore\": 0.85,\n \"assumptions\": [\n \"一线城市消费者对植物基食品的溢价接受度和概念认知度显著高于下沉市场\"\n ]\n }\n}"
}
]
}

View File

@@ -0,0 +1,50 @@
{
"name": "5whys",
"description": "5 Whys 分析法:通过连续追问五次“为什么”,剥开表层现象,探究并锁定导致问题发生的根本原因 (Root Cause),并推导解决方案。",
"persona": "root_cause_analyst",
"parameters": [
{
"name": "problem",
"description": "需要分析的核心问题、故障或不良现象",
"required": true
},
{
"name": "context",
"description": "问题发生的背景信息、前置条件或受影响的范围(可选)",
"required": false
},
{
"name": "goal",
"description": "期望通过解决此问题达成的最终目标(可选)",
"required": false
}
],
"template": "请使用 5 Whys 分析法探究以下问题的根本原因:\n核心问题 (Problem){{problem}}\n背景信息 (Context){{context}}\n改善目标 (Goal){{goal}}\n\n请严格按照 5 Whys 的逻辑,从表象问题出发,连续进行 5 次有逻辑因果关系的追问,直到找到根本原因,并给出针对根本原因的解决方案。\n\n输出格式要求请严格按以下 JSON 格式输出你的分析结果,不要包含任何 Markdown 代码块标记(如反引号包裹的 json直接输出纯 JSON 文本:\n{\n \"problemStatement\": \"清晰界定当前面临的问题\",\n \"whys\": [\n {\n \"level\": 1,\n \"question\": \"为什么会出现上述问题?\",\n \"answer\": \"第一层直接原因\"\n },\n {\n \"level\": 2,\n \"question\": \"基于上一个回答,进一步追问为什么?\",\n \"answer\": \"第二层深层原因\"\n },\n {\n \"level\": 3,\n \"question\": \"继续追问为什么?\",\n \"answer\": \"第三层原因\"\n },\n {\n \"level\": 4,\n \"question\": \"继续追问为什么?\",\n \"answer\": \"第四层原因\"\n },\n {\n \"level\": 5,\n \"question\": \"追问根本机制或系统性缺陷?\",\n \"answer\": \"第五层根本原因\"\n }\n ],\n \"rootCause\": \"综合上述推导,用一句话总结最终的根本原因(通常是系统、流程或管理机制的问题)\",\n \"actionableSolutions\": [\n {\n \"action\": \"针对根本原因的具体整改措施\",\n \"owner\": \"负责执行的角色或部门\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.85,\n \"assumptions\": [\"推导此因果链条的关键假设\"]\n }\n}",
"systemPrompt": "你是一位擅长根因分析的问题解决专家,能够通过系统性追问深入挖掘问题的根本原因。你必须严格遵守 JSON 格式输出规范,确保推导链条清晰且可直接被下游程序结构化解析。",
"examples": [
{
"input": {
"problem": "某互联网公司近期新注册用户留存率大幅下降",
"context": "公司主打年轻人的社交APP近期刚投入了大量买量预算但次日留存跌破了警戒线",
"goal": "止住留存率下滑趋势,提升新用户的早期活跃度"
},
"output": "{\n \"problemStatement\": \"公司投入大量买量预算后,新注册用户的次日留存率大幅下跌,未达到预期活跃度。\",\n \"whys\": [\n {\n \"level\": 1,\n \"question\": \"为什么近期新用户留存率大幅下降?\",\n \"answer\": \"因为大量新用户在注册后的一小时内就卸载了APP或停止了活跃。\"\n },\n {\n \"level\": 2,\n \"question\": \"为什么新用户在一小时内就停止活跃?\",\n \"answer\": \"因为他们觉得新用户引导流程过于复杂繁琐,在第三步就放弃了。\"\n },\n {\n \"level\": 3,\n \"question\": \"为什么新用户引导流程复杂繁琐?\",\n \"answer\": \"因为引导步骤多达五步,且强制要求填写过多个人真实信息(如行业、真实头像等)才能进入主界面。\"\n },\n {\n \"level\": 4,\n \"question\": \"为什么引导步骤设计为五步且强制索要信息?\",\n \"answer\": \"因为产品和运营团队希望在注册漏斗最前端收集最全面的用户画像,以便后续做精准算法推荐。\"\n },\n {\n \"level\": 5,\n \"question\": \"为什么产品团队为了收集画像而牺牲了基础的注册转化体验?\",\n \"answer\": \"因为内部KPI考核导向存在偏差设计决策基于内部业务诉求且上线前缺乏可用性测试和A/B测试验证。\"\n }\n ],\n \"rootCause\": \"产品决策机制存在缺陷以内部数据收集目标凌驾于用户体验之上且关键路径改版缺乏科学的A/B测试验证闭环。\",\n \"actionableSolutions\": [\n {\n \"action\": \"立即回滚或精简注册流程,将部分非核心信息的收集后置到用户深度体验产品后\",\n \"owner\": \"产品团队\"\n },\n {\n \"action\": \"建立核心链路改版必须通过A/B测试且留存率不降才能全量发布的流程红线\",\n \"owner\": \"质量与数据分析团队\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.90,\n \"assumptions\": [\n \"新买量的渠道质量与历史渠道一致,非渠道刷量导致\",\n \"留存率的下降与最近一次版本的注册流程改版在时间点上高度重合\"\n ]\n }\n}"
},
{
"input": {
"problem": "某制造工厂生产线设备故障率近期异常上升",
"context": "生产旺季即将到来订单压力极大当前设备停机导致产能出现20%的缺口",
"goal": "迅速降低设备故障率,恢复并保障生产旺季的产能供应"
},
"output": "{\n \"problemStatement\": \"生产旺季前夕,工厂核心生产线设备故障率异常上升,导致停机频发并造成严重产能缺口。\",\n \"whys\": [\n {\n \"level\": 1,\n \"question\": \"为什么设备故障率近期异常上升?\",\n \"answer\": \"因为核心设备的传动轴承等关键部件磨损速度异常加快,导致频繁卡死。\"\n },\n {\n \"level\": 2,\n \"question\": \"为什么关键部件磨损速度异常加快?\",\n \"answer\": \"因为传动部件的润滑严重不足,导致物理摩擦急剧增大。\"\n },\n {\n \"level\": 3,\n \"question\": \"为什么会发生润滑严重不足的情况?\",\n \"answer\": \"因为设备的自动润滑系统出现了堵塞,未能按预定计划加注润滑油。\"\n },\n {\n \"level\": 4,\n \"question\": \"为什么自动润滑系统堵塞没有被及时发现和处理?\",\n \"answer\": \"因为一线维护人员在日常巡检中忽略了对自动润滑系统的检查,误以为它既然是‘自动’的就不需要人工干预。\"\n },\n {\n \"level\": 5,\n \"question\": \"为什么维护人员会产生这种误解并忽略检查?\",\n \"answer\": \"因为设备维护的标准作业程序 (SOP) 文件存在漏洞,未将‘自动润滑系统巡检’明确写入日常检查表,导致责任和标准缺失。\"\n }\n ],\n \"rootCause\": \"设备维护流程与SOP存在管理盲区未覆盖自动化附件的日常监督导致预防性维护失效。\",\n \"actionableSolutions\": [\n {\n \"action\": \"立即全面清理自动润滑系统并更换受损轴承,恢复生产线运转\",\n \"owner\": \"设备维修组\"\n },\n {\n \"action\": \"修订全厂设备点检SOP将所有自动化辅助系统纳入每日必检项并对维修人员进行全员培训\",\n \"owner\": \"工程与设备管理部\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.95,\n \"assumptions\": [\n \"润滑油的标号和品质符合设备要求,非油品质量问题\",\n \"设备并未处于超负荷设计极限运行状态\"\n ]\n }\n}"
},
{
"input": {
"problem": "高校图书馆自习室“占座”现象严重,引发大量投诉",
"context": "期末考试临近,学生对学习空间需求暴增。大量座位上放着书本但长时间无人使用,引发学生间的矛盾",
"goal": "提高图书馆座位的实际利用率,平息学生群体的不满情绪"
},
"output": "{\n \"problemStatement\": \"期末复习高峰期,图书馆自习室存在大量‘物品占座但无人使用’的现象,导致实际资源浪费并引发学生强烈不满。\",\n \"whys\": [\n {\n \"level\": 1,\n \"question\": \"为什么自习室‘占座’现象如此严重?\",\n \"answer\": \"因为很多学生为了确保自己随时有座位,习惯用书本占位后去上课或吃饭,且长时间不返回。\"\n },\n {\n \"level\": 2,\n \"question\": \"为什么学生能够肆无忌惮地长时间占座而不被制止?\",\n \"answer\": \"因为现场没有有效的制止机制,管理员巡查频率极低,且其他学生不敢私自清理他人物品。\"\n },\n {\n \"level\": 3,\n \"question\": \"为什么管理员巡查频率低且不主动清理?\",\n \"answer\": \"因为管理员人手不足,更重要的是,缺乏专门针对‘占座超时’的具体判定标准和管理授权。\"\n },\n {\n \"level\": 4,\n \"question\": \"为什么图书馆一直没有制定占座的判定标准和管理制度?\",\n \"answer\": \"因为图书馆管理层此前一直认为占座是‘学生道德和自觉性’问题,不愿采取强硬的管理手段引发可能的冲突。\"\n },\n {\n \"level\": 5,\n \"question\": \"为什么管理层会将管理责任推给‘自觉’,而不积极介入?\",\n \"answer\": \"因为图书馆缺乏有效的数据监控手段和学生意见反馈渠道,管理层与学生真实痛点脱节,未意识到资源错配的严重性。\"\n }\n ],\n \"rootCause\": \"图书馆管理层服务意识与数字化管理手段双重滞后,未建立科学的空间资源分配规则与违规惩处机制。\",\n \"actionableSolutions\": [\n {\n \"action\": \"紧急出台《自习室防占座管理规定》明确离座超过45分钟即视为放弃并安排专人定期清理滞留物品\",\n \"owner\": \"图书馆馆长办公室\"\n },\n {\n \"action\": \"加快引入基于微信小程序的‘座位预约与扫码签到系统’,用技术手段实现座位资源的动态分配与黑名单机制\",\n \"owner\": \"图书馆信息技术部\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.88,\n \"assumptions\": [\n \"图书馆整体座位数在绝对数量上确实无法满足期末全体学生的同时自习需求,属于存量博弈\",\n \"学生群体对引入公平的预约机制具有较高的接受度\"\n ]\n }\n}"
}
]
}

View File

@@ -0,0 +1,50 @@
{
"name": "pestle",
"description": "PESTLE 宏观环境分析:从政治(P)、经济(E)、社会(S)、技术(T)、法律(L)、环境(E)六个维度,全面结构化评估目标行业或领域的外部宏观环境。",
"persona": "macro_environment_analyst",
"parameters": [
{
"name": "domain",
"description": "需要分析的具体行业、市场或业务领域",
"required": true
},
{
"name": "region",
"description": "目标地域范围(如:全球、中国、北美、特定省市等,可选)",
"required": false
},
{
"name": "timeframe",
"description": "分析的时间跨度当前现状、未来3-5年等可选",
"required": false
}
],
"template": "请使用 PESTLE 框架分析以下行业/领域的宏观环境:\n目标行业/领域 (Domain){{domain}}\n地域范围 (Region){{region}}\n时间跨度 (Timeframe){{timeframe}}\n\n请依次从政治、经济、社会、技术、法律、环境六个维度展开深度评估并给出战略建议。\n\n输出格式要求请严格按以下 JSON 格式输出你的分析结果,不要包含任何 Markdown 代码块标记(如反引号包裹的 json直接输出纯 JSON 文本:\n{\n \"political\": {\n \"analysis\": \"政治因素分析,如政府政策、稳定性、贸易导向等\",\n \"impactLevel\": \"High|Medium|Low\",\n \"trend\": \"Positive|Negative|Neutral\"\n },\n \"economic\": {\n \"analysis\": \"经济因素分析,如经济增长、汇率、消费能力等\",\n \"impactLevel\": \"High|Medium|Low\",\n \"trend\": \"Positive|Negative|Neutral\"\n },\n \"social\": {\n \"analysis\": \"社会因素分析,如人口结构、文化趋势、生活方式等\",\n \"impactLevel\": \"High|Medium|Low\",\n \"trend\": \"Positive|Negative|Neutral\"\n },\n \"technological\": {\n \"analysis\": \"技术因素分析,如技术创新、研发周期、自动化替代等\",\n \"impactLevel\": \"High|Medium|Low\",\n \"trend\": \"Positive|Negative|Neutral\"\n },\n \"legal\": {\n \"analysis\": \"法律因素分析,如劳动法、数据安全、知识产权等\",\n \"impactLevel\": \"High|Medium|Low\",\n \"trend\": \"Positive|Negative|Neutral\"\n },\n \"environmental\": {\n \"analysis\": \"环境因素分析如气候变化、ESG标准、碳排放政策等\",\n \"impactLevel\": \"High|Medium|Low\",\n \"trend\": \"Positive|Negative|Neutral\"\n },\n \"strategicAdvice\": [\n {\n \"strategy\": \"基于上述宏观环境得出的具体战略建议\",\n \"focusArea\": \"研发|市场|合规|供应链等\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.85,\n \"assumptions\": [\"推导该分析所基于的关键宏观假设\"]\n }\n}",
"systemPrompt": "你是一位宏观环境分析专家,擅长运用 PESTLE 框架评估行业外部环境,能够结合具体数据和发展趋势进行深入洞察。你必须严格遵守 JSON 格式输出规范,确保数据可直接被下游程序结构化解析。",
"examples": [
{
"input": {
"domain": "人工智能芯片行业",
"region": "中国",
"timeframe": "未来3-5年"
},
"output": "{\n \"political\": {\n \"analysis\": \"中国政府将AI芯片列为国家战略新兴产业通过大基金等提供支持。同时外部大国对华芯片出口管制持续收紧倒逼国内加速自主可控。政府东数西算工程也为国产芯片创造了庞大的算力底座应用场景。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Positive\"\n },\n \"economic\": {\n \"analysis\": \"AI大模型带来的算力需求旺盛市场规模年增速预计超30%。然而,宏观经济弱复苏可能导致部分下游行业资本开支收缩,且先进制程的代工成本持续高企,对芯片设计企业的现金流带来挑战。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Neutral\"\n },\n \"social\": {\n \"analysis\": \"全社会数字化转型加速AI应用向制造、医疗等实体经济渗透。公众对数据安全和算法伦理关注度上升。此外高校相关专业扩招使得底层架构研发人才供给逐渐增加但高端领军人才依然稀缺。\",\n \"impactLevel\": \"Medium\",\n \"trend\": \"Positive\"\n },\n \"technological\": {\n \"analysis\": \"GPU仍占主导但ASIC、类脑芯片等新架构不断涌现。先进封装Chiplet成为绕开先进制程封锁、提升性能的关键途径。国内在推理芯片端已接近国际水平但在训练芯片端仍存代差。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Positive\"\n },\n \"legal\": {\n \"analysis\": \"《数据安全法》等法规对芯片底层的数据处理合规提出要求。知识产权保护力度加大专利战可能频发。海外实体清单限制了部分EDA工具和半导体IP的获取带来法律和合规断供风险。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Negative\"\n },\n \"environmental\": {\n \"analysis\": \"芯片制造是高耗能产业双碳目标下政府对晶圆厂能耗监管趋严。同时AI数据中心对算力芯片的能效比(PPA)要求极高低功耗设计成为核心竞争力符合ESG投资趋势。\",\n \"impactLevel\": \"Medium\",\n \"trend\": \"Positive\"\n },\n \"strategicAdvice\": [\n {\n \"strategy\": \"加大基于Chiplet架构和RISC-V开源指令集的研发投入对冲先进制程和商业IP断供的风险。\",\n \"focusArea\": \"研发\"\n },\n {\n \"strategy\": \"紧贴‘东数西算’等政府新基建项目,聚焦信创市场进行国产化替代销售。\",\n \"focusArea\": \"市场\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.88,\n \"assumptions\": [\n \"全球地缘政治博弈在未来3-5年内不会显著缓和\",\n \"国内算力需求将持续保持指数级增长\"\n ]\n }\n}"
},
{
"input": {
"domain": "新能源汽车电池回收行业",
"region": "欧洲",
"timeframe": "当前至2030年"
},
"output": "{\n \"political\": {\n \"analysis\": \"欧盟致力于实现‘欧洲绿色协议’目标,高度依赖海外关键矿物(锂、钴、镍)。通过立法推动本土电池供应链闭环,以减少对亚洲材料的依赖,政治支持力度空前。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Positive\"\n },\n \"economic\": {\n \"analysis\": \"由于原始矿产资源价格波动巨大回收材料的经济价值凸显。预计到2030年欧洲将有大量初代电动车电池退役催生百亿欧元级别的回收市场吸引大量风险投资和传统车企资本涌入。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Positive\"\n },\n \"social\": {\n \"analysis\": \"欧洲民众环保意识极强,对循环经济接受度高。消费者在购车时越来越关注产品的全生命周期碳足迹,社会舆论对不可回收的高污染电池持强烈抵制态度。\",\n \"impactLevel\": \"Medium\",\n \"trend\": \"Positive\"\n },\n \"technological\": {\n \"analysis\": \"湿法冶金和火法冶金技术正面临优化,以提高金属回收率并降低能耗。电池直接修复与梯次利用(如降级用于储能)的检测和筛选技术是当前的研发热点,自动化拆解技术急需突破。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Neutral\"\n },\n \"legal\": {\n \"analysis\": \"新《欧盟电池法》强制规定了新电池中必须包含最低比例的回收金属如16%的钴、6%的锂等),并要求实施‘电池护照’制度。法规极其严苛,对企业的合规溯源能力提出巨大挑战。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Positive\"\n },\n \"environmental\": {\n \"analysis\": \"电池回收本身旨在解决环境问题但回收工艺尤其是传统火法可能产生二次污染和高碳排放。行业必须采用清洁能源驱动的低碳回收工艺以符合欧盟严苛的ESG环保审核。\",\n \"impactLevel\": \"Medium\",\n \"trend\": \"Neutral\"\n },\n \"strategicAdvice\": [\n {\n \"strategy\": \"加快建立基于区块链的‘电池护照’数据平台,实现全链条数据追踪,满足欧盟合规要求。\",\n \"focusArea\": \"合规与数字化\"\n },\n {\n \"strategy\": \"与欧洲本土大型车企建立早期回收联盟锁定未来5年的退役电池废料来源。\",\n \"focusArea\": \"供应链\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.92,\n \"assumptions\": [\n \"新《欧盟电池法》将按预期时间表严格执行不倒退\",\n \"新能源车保有量达到预期规模,提供充足的退役电池\"\n ]\n }\n}"
},
{
"input": {
"domain": "在线职业教育",
"region": "全球(聚焦新兴市场)",
"timeframe": "当前现状"
},
"output": "{\n \"political\": {\n \"analysis\": \"多国政府为缓解结构性失业,出台政策鼓励成人技能重塑。部分新兴市场政府提供财政补贴支持数字化技能培训,但同时对跨境在线教育内容和数据出境的审查也在趋严。\",\n \"impactLevel\": \"Medium\",\n \"trend\": \"Neutral\"\n },\n \"economic\": {\n \"analysis\": \"全球经济增长放缓导致就业压力增大职场人逆周期自我提升投资意愿增强。然而新兴市场用户支付能力有限平台需要探索B2B2C通过企业采购或微支付等灵活的商业模式。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Positive\"\n },\n \"social\": {\n \"analysis\": \"终身学习理念普及斜杠青年和自由职业者群体扩大。年轻一代习惯碎片化、移动化学习。由于AI对基础文职工作的冲击社会对软技能和复合型技术技能的培训需求急剧上升。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Positive\"\n },\n \"technological\": {\n \"analysis\": \"AIGC技术正在重塑在线教育实现课程内容的自动化生成、个性化自适应学习路径以及7x24小时的AI虚拟导师辅导大幅降低了教研和辅导的人力成本。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Positive\"\n },\n \"legal\": {\n \"analysis\": \"面临不同国家的隐私保护法如GDPR合规挑战。AI生成内容的版权归属问题尚不明确。此外部分国家对颁发职业资格证书的线上机构设有严格的准入资质限制。\",\n \"impactLevel\": \"Medium\",\n \"trend\": \"Negative\"\n },\n \"environmental\": {\n \"analysis\": \"在线教育作为无纸化、零通勤的低碳行业契合ESG理念。云服务器带来的碳排放虽然存在但总体环境负面影响极小。\",\n \"impactLevel\": \"Low\",\n \"trend\": \"Neutral\"\n },\n \"strategicAdvice\": [\n {\n \"strategy\": \"全面集成AIGC能力推出AI私教功能以低成本在下沉市场和新兴市场提供个性化体验。\",\n \"focusArea\": \"研发与产品\"\n },\n {\n \"strategy\": \"加强与目标市场本地企业HR部门的合作从单纯卖课向培训+推荐就业’闭环服务转型。\",\n \"focusArea\": \"市场与销售\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.85,\n \"assumptions\": [\n \"AI技术能够有效替代部分基础人工辅导工作\",\n \"新兴市场互联网基础设施足以支持流畅的视频与AI互动\"\n ]\n }\n}"
}
]
}

View File

@@ -0,0 +1,50 @@
{
"name": "scqa",
"description": "SCQA 架构分析:基于情境(Situation)与冲突(Complication),提炼核心问题(Question)并输出结构化的战略级答案(Answer)与行动计划。",
"persona": "scqa_facilitator",
"parameters": [
{
"name": "situation",
"description": "当前的客观背景或情境描述",
"required": true
},
{
"name": "context",
"description": "补充的约束条件、行业背景或已有资源(可选)",
"required": false
},
{
"name": "objective",
"description": "期望达成的最终业务目标(可选)",
"required": false
}
],
"template": "请使用 SCQA 架构分析以下情境:\n情境 (Situation){{situation}}\n补充背景/约束 (Context){{context}}\n期望目标 (Objective){{objective}}\n\n请依次构建\n1. 复杂化 (Complication):指出情境中存在的矛盾或挑战。\n2. 问题 (Question):基于复杂化提炼出核心问题。\n3. 答案 (Answer):提出解决问题的初步方案或分析路径。\n\n输出格式要求请严格按以下 JSON 格式输出你的分析结果,不要包含任何 Markdown 代码块标记(如反引号包裹的 json直接输出纯 JSON 文本:\n{\n \"situation\": \"客观陈述背景,说明当前处于什么状态\",\n \"complication\": \"指出打破现状的冲突点、矛盾或面临的挑战\",\n \"question\": \"基于冲突提炼出一个或多个亟待解决的核心疑问\",\n \"answer\": \"提出解决问题的战略性回答或核心逻辑\",\n \"actionItems\": [\n {\n \"task\": \"具体的执行步骤描述\",\n \"owner\": \"建议的负责角色或部门\",\n \"priority\": \"High|Medium|Low\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.85,\n \"assumptions\": [\"推导该方案所基于的关键假设\"]\n }\n}",
"systemPrompt": "你是一位资深战略分析师,擅长运用 SCQA 框架进行逻辑严谨的业务拆解。你必须严格遵守 JSON 格式输出规范,确保数据可直接被程序解析。",
"examples": [
{
"input": {
"situation": "一家传统零售企业过去三年线上销售额年均增长仅 3%,而行业平均水平为 15%。",
"context": "公司拥有300家实体店网络和稳定供应链但IT部门仅能维护ERP缺乏数字营销经验。",
"objective": "在两年内将线上销售增速提升至行业平均水平,且不影响实体店利润。"
},
"output": "{\n \"situation\": \"企业拥有300家实体店和成熟供应链但在电商崛起背景下过去三年线上销售年均复合增长率仅为3%远落后于行业15%的均值。\",\n \"complication\": \"企业意识到需数字化转型并设定了高增长目标但线上线下渠道割裂且现有IT团队缺乏数字营销与用户运营的基因导致转型难以落地。\",\n \"question\": \"在缺乏原生数字团队和资源受限的情况下,如何快速弥补线上运营短板,实现线上线下融合(OMO)以拉动业绩增长?\",\n \"answer\": \"采取‘借力与重构’并行的 OMO 战略:短期内引入成熟的第三方电商中台与代运营团队快速起步,中期在内部成立独立核算的数字化事业部,打通底层会员与库存数据。\",\n \"actionItems\": [\n {\n \"task\": \"引入外部数字化中台SaaS服务实现库存、会员、营销三通\",\n \"owner\": \"CIO & 采购部\",\n \"priority\": \"High\"\n },\n {\n \"task\": \"成立独立数字化事业部按全域GMV而非单渠道进行绩效考核\",\n \"owner\": \"CEO & HR\",\n \"priority\": \"High\"\n },\n {\n \"task\": \"在一线城市挑选20家门店作为店仓一体化敏捷试点\",\n \"owner\": \"数字化事业部 & 运营部\",\n \"priority\": \"Medium\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.85,\n \"assumptions\": [\n \"现有实体店网络具备转化为前置仓的空间和物流基础\",\n \"管理层愿意为独立数字化事业部下放决策权\"\n ]\n }\n}"
},
{
"input": {
"situation": "某三甲医院近期患者满意度调查得分持续下降,尤其在挂号、候诊环节抱怨最多。",
"context": "医院两年前已推行微信预约挂号目前线上预约率达60%,但现场仍有大量不会使用智能手机的老年患者,与预约患者混流。",
"objective": "在不增加医生工作负荷的前提下将患者平均候诊时间缩短30%并提升整体满意度至90分以上。"
},
"output": "{\n \"situation\": \"医院日均门诊量巨大虽已推行线上预约且预约率达60%,但近期患者满意度调查中挂号和候诊环节得分大幅下降。\",\n \"complication\": \"线上预约未实现精准分时,且与大量依赖现场挂号的老年患者混流;同时信息系统割裂导致护士手动核对效率低,加剧了现场拥堵和医患沟通不足的矛盾。\",\n \"question\": \"在医疗资源和医生工作量固定的限制下,如何通过技术手段与流程重构,精准分流并缩短患者候诊时间,挽回患者满意度?\",\n \"answer\": \"推行‘精准预约+分时段报到’的精细化排班体系,同时针对老年群体保留专属服务通道,打通院内信息系统实现全流程自动化叫号。\",\n \"actionItems\": [\n {\n \"task\": \"升级预约系统将预约窗口缩短至15分钟实行提前扫码报到制\",\n \"owner\": \"信息科 & 门诊办\",\n \"priority\": \"High\"\n },\n {\n \"task\": \"设立2个老年人无手机通道窗口及志愿者协助机制\",\n \"owner\": \"客服中心\",\n \"priority\": \"High\"\n },\n {\n \"task\": \"对接HIS与叫号系统实现自动化排队和诊前科普内容推送\",\n \"owner\": \"信息科\",\n \"priority\": \"Medium\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.90,\n \"assumptions\": [\n \"各科室医生看诊速度可通过历史数据进行合理均值化预估\",\n \"绝大部分中青年患者能较快适应分时段报到制度\"\n ]\n }\n}"
},
{
"input": {
"situation": "某知识付费平台“课程完课率”不足 10%,用户购买课程后往往只学几节课就放弃。",
"context": "平台主打职场技能提升,课程质量高评分好,但学习模式以单向录播视频为主,缺乏社群互动和外部学习督促机制。",
"objective": "通过产品机制和轻度运营手段将课程完课率提升至30%以上,带动二次复购。"
},
"output": "{\n \"situation\": \"知识付费平台坐拥大量活跃用户和高分优质课程但核心指标完课率仅不足10%,用户极易流失。\",\n \"complication\": \"内容优质但缺乏互动与反馈机制,用户仅凭自律难以坚持;加之无社群督促,学习过程枯燥孤立,导致冲动购买后长期搁置。\",\n \"question\": \"如何在不大幅增加人工服务成本的前提下,利用产品机制激发用户内在学习动力,突破完课率瓶颈?\",\n \"answer\": \"引入‘游戏化学习路径’与‘社群轻运营’双螺旋模式:通过将课程拆解为微任务并匹配荣誉体系降低学习门槛,同时通过系统自动匹配学习小组营造同侪压力。\",\n \"actionItems\": [\n {\n \"task\": \"重构播放器体验课程按15分钟微任务拆包增加签到与勋章奖励\",\n \"owner\": \"产品部 & 研发部\",\n \"priority\": \"High\"\n },\n {\n \"task\": \"开发学伴匹配功能系统自动为同进度用户组建3-5人微信或站内互助小组\",\n \"owner\": \"产品部 & 运营部\",\n \"priority\": \"High\"\n },\n {\n \"task\": \"建立触发式自动化干预策略针对超过3天未学习的用户自动推送关怀提醒\",\n \"owner\": \"用户运营部\",\n \"priority\": \"Medium\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.88,\n \"assumptions\": [\n \"现有的录播课程内容结构上允许被切分为短小精悍的微任务模块\",\n \"用户对于虚拟成就(勋章/积分)和同伴压力有积极的响应反馈\"\n ]\n }\n}"
}
]
}

View File

@@ -0,0 +1,50 @@
{
"name": "swot",
"description": "SWOT 分析:从内部优势(S)、内部劣势(W)、外部机会(O)、外部威胁(T)四个维度全面评估分析对象并推导交叉战略方案如SO/ST/WO/WT。",
"persona": "strategy_advisor",
"parameters": [
{
"name": "entity",
"description": "分析对象(如企业、产品、项目、个人等)",
"required": true
},
{
"name": "context",
"description": "补充的行业背景、当前阶段或面临的核心挑战(可选)",
"required": false
},
{
"name": "competitors",
"description": "主要竞争对手或对标对象(可选)",
"required": false
}
],
"template": "请使用 SWOT 框架分析以下对象:\n分析对象 (Entity){{entity}}\n背景信息 (Context){{context}}\n主要竞争对手 (Competitors){{competitors}}\n\n请依次从内部优势、内部劣势、外部机会、外部威胁四个维度展开评估并结合 SWOT 交叉矩阵给出战略建议。\n\n输出格式要求请严格按以下 JSON 格式输出你的分析结果,不要包含任何 Markdown 代码块标记(如反引号包裹的 json直接输出纯 JSON 文本:\n{\n \"strengths\": [\n { \"factor\": \"优势点名称\", \"reasoning\": \"简要论证\", \"impactLevel\": \"High|Medium|Low\" }\n ],\n \"weaknesses\": [\n { \"factor\": \"劣势点名称\", \"reasoning\": \"简要论证\", \"impactLevel\": \"High|Medium|Low\" }\n ],\n \"opportunities\": [\n { \"factor\": \"机会点名称\", \"reasoning\": \"简要论证\", \"urgency\": \"High|Medium|Low\" }\n ],\n \"threats\": [\n { \"factor\": \"威胁点名称\", \"reasoning\": \"简要论证\", \"severity\": \"High|Medium|Low\" }\n ],\n \"actionableStrategies\": [\n {\n \"strategyType\": \"SO增长战略 | ST防御战略 | WO扭转战略 | WT生存战略\",\n \"description\": \"结合具体优劣势和外部环境得出的具体行动建议\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.85,\n \"assumptions\": [\"推导该分析所基于的关键假设\"]\n }\n}",
"systemPrompt": "你是一位资深的战略分析顾问,擅长使用 SWOT 框架进行竞争态势分析。你必须清晰区分内外部因素,并通过交叉分析推导可执行的战略。你必须严格遵守 JSON 格式输出规范,确保数据可直接被下游程序结构化解析。",
"examples": [
{
"input": {
"entity": "一家专注于新能源汽车电池研发的初创公司",
"context": "A轮融资仅够支撑18个月急需实现技术向商业的转化并打入头部整车厂供应链",
"competitors": "宁德时代、LG新能源等传统电池巨头"
},
"output": "{\n \"strengths\": [\n { \"factor\": \"核心技术领先\", \"reasoning\": \"创始团队拥有5项固态电池核心专利实验室能量密度比市面主流产品高30%。\", \"impactLevel\": \"High\" },\n { \"factor\": \"轻资产与高灵活性\", \"reasoning\": \"采用研发+技术授权模式,决策链路短,无需背负沉重的重资产建厂包袱。\", \"impactLevel\": \"Medium\" }\n ],\n \"weaknesses\": [\n { \"factor\": \"资金链脆弱\", \"reasoning\": \"A轮资金仅能维持18个月的研发抗风险能力弱难以支撑长周期的商业化试错。\", \"impactLevel\": \"High\" },\n { \"factor\": \"缺乏量产与品控经验\", \"reasoning\": \"团队主要为科研背景,中试到规模化量产的技术转化能力未经验证。\", \"impactLevel\": \"High\" }\n ],\n \"opportunities\": [\n { \"factor\": \"高端车型对高能量密度电池渴求\", \"reasoning\": \"整车厂为缓解里程焦虑,对下一代固态电池技术存在巨大的潜在订单需求。\", \"urgency\": \"High\" },\n { \"factor\": \"车企供应链‘去中心化’趋势\", \"reasoning\": \"整车厂不愿被单一巨头(如宁德时代)绑定,积极扶持‘二供’和‘三供’。\", \"urgency\": \"Medium\" }\n ],\n \"threats\": [\n { \"factor\": \"巨头挤压与技术路线封锁\", \"reasoning\": \"传统巨头已投入巨资研发固态电池,初创公司面临巨大的资本与专利壁垒。\", \"severity\": \"High\" },\n { \"factor\": \"原材料价格剧烈波动\", \"reasoning\": \"上游关键矿石材料价格波动易导致研发和中试成本失控。\", \"severity\": \"Medium\" }\n ],\n \"actionableStrategies\": [\n {\n \"strategyType\": \"ST防御战略\",\n \"description\": \"凭借核心专利S选择与头部整车厂建立独家联合研发实验室通过技术入股或预付款形式绑定客户抵御传统电池巨头的资本挤压T缓解资金压力。\"\n },\n {\n \"strategyType\": \"WO扭转战略\",\n \"description\": \"利用车企扶持二供的意愿O引入具备制造业经验的外部高管或与成熟代工厂合资弥补团队缺乏量产经验的短板W。\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.88,\n \"assumptions\": [\n \"固态电池技术在未来两年内能够达到量产前夕的中试标准\",\n \"资本市场对固态电池赛道依然保持较高估值期待\"\n ]\n }\n}"
},
{
"input": {
"entity": "个人职业转型:从技术开发岗转向产品经理岗",
"context": "30岁5年后端开发经验公司内部有产品线扩张的转岗机会",
"competitors": "外部经验丰富的产品经理候选人、内部更年轻的转岗者"
},
"output": "{\n \"strengths\": [\n { \"factor\": \"技术底层理解深\", \"reasoning\": \"5年开发经验使得对系统架构、边界条件及开发成本有精准预判不易被技术团队忽悠。\", \"impactLevel\": \"High\" },\n { \"factor\": \"业务熟悉度高\", \"reasoning\": \"作为内部员工,熟悉公司现有产品线历史债务及组织协作文化,上手成本极低。\", \"impactLevel\": \"High\" }\n ],\n \"weaknesses\": [\n { \"factor\": \"缺乏用户洞察经验\", \"reasoning\": \"长期面对机器和代码,缺乏系统的竞品分析、用户访谈及交互设计思维训练。\", \"impactLevel\": \"High\" },\n { \"factor\": \"沟通习惯偏向确定性\", \"reasoning\": \"习惯以确定性的技术视角看待问题,需适应产品早期阶段高度模糊和频繁变更的沟通场景。\", \"impactLevel\": \"Medium\" }\n ],\n \"opportunities\": [\n { \"factor\": \"公司内部业务扩张\", \"reasoning\": \"公司正进行中台化或技术驱动型产品线扩张,亟需懂业务逻辑的技术型产品经理。\", \"urgency\": \"High\" },\n { \"factor\": \"AI与大模型浪潮\", \"reasoning\": \"AI产品的设计越来越需要懂算法和接口边界的复合型人才这是纯业务型PM的软肋。\", \"urgency\": \"Medium\" }\n ],\n \"threats\": [\n { \"factor\": \"年龄与试错成本\", \"reasoning\": \"30岁转岗意味着放弃原有技术积累的溢价且一旦转型失败难以退回原岗。\", \"severity\": \"High\" },\n { \"factor\": \"外部熟练工竞争\", \"reasoning\": \"行业内涌现大量自带成熟产品方法论的外部候选人,竞争激烈。\", \"severity\": \"High\" }\n ],\n \"actionableStrategies\": [\n {\n \"strategyType\": \"SO增长战略\",\n \"description\": \"利用自身深厚的技术背景和对内部系统的熟悉S主动申请主导偏底层逻辑或技术中台类的产品线O形成差异化竞争。\"\n },\n {\n \"strategyType\": \"WO扭转战略\",\n \"description\": \"在内部寻找资深业务型PM作为导师并在日常工作中主动承担原型绘制和用户调研任务O快速补齐需求分析短板W。\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.85,\n \"assumptions\": [\n \"公司内部拥有较为宽容的转岗试用机制\",\n \"目标产品线确实属于技术逻辑较重的领域\"\n ]\n }\n}"
},
{
"input": {
"entity": "某地方性连锁超市",
"context": "在本市拥有40家门店主打生鲜当前正面临客流严重下滑的危机",
"competitors": "美团优选、多多买菜等全国性社区团购平台"
},
"output": "{\n \"strengths\": [\n { \"factor\": \"本地供应链深耕\", \"reasoning\": \"与本地农户长期合作,生鲜直采品质可控,生鲜品类的信任度高于纯线上平台。\", \"impactLevel\": \"High\" },\n { \"factor\": \"高密度的前置网点\", \"reasoning\": \"40家门店深入社区腹地具备开展最后一公里即时零售的天然地理优势。\", \"impactLevel\": \"Medium\" }\n ],\n \"weaknesses\": [\n { \"factor\": \"数字化运营能力弱\", \"reasoning\": \"缺乏精准的用户画像与私域流量池,营销手段依赖传统的纸质海报,触达率极低。\", \"impactLevel\": \"High\" },\n { \"factor\": \"规模采购成本劣势\", \"reasoning\": \"标品(日化、零食)的采购体量远不及全国性互联网巨头,价格竞争处于下风。\", \"impactLevel\": \"High\" }\n ],\n \"opportunities\": [\n { \"factor\": \"即时零售(即时配送)红利\", \"reasoning\": \"消费者对‘半小时达’的即时性需求增加,而社区团购通常是‘次日达’。\", \"urgency\": \"High\" },\n { \"factor\": \"适老化消费场景构建\", \"reasoning\": \"老龄化趋势下,老年人依然偏好实体店挑拣生鲜的体验和人际交流。\", \"urgency\": \"Medium\" }\n ],\n \"threats\": [\n { \"factor\": \"巨头低价倾销抢夺客流\", \"reasoning\": \"社区团购利用资本优势持续高额补贴,导致价格敏感型顾客大量流失。\", \"severity\": \"High\" },\n { \"factor\": \"运营成本刚性上升\", \"reasoning\": \"实体门店租金、人工成本逐年攀升,持续挤压本已微薄的零售利润。\", \"severity\": \"High\" }\n ],\n \"actionableStrategies\": [\n {\n \"strategyType\": \"ST防御战略\",\n \"description\": \"避开与互联网巨头在标品上的价格战T利用生鲜直采优势S主打高品质、即买即得的差异化生鲜体验巩固基本盘。\"\n },\n {\n \"strategyType\": \"WO扭转战略\",\n \"description\": \"借势即时零售平台(如入驻美团/饿了么O将实体店转化为前置仓弥补自身数字化与配送能力的短板W拓展年轻客群。\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.90,\n \"assumptions\": [\n \"超市有一定资金支撑初期接入第三方即时配送平台的改造成本\",\n \"本地核心消费群体仍愿为高品质生鲜支付一定的溢价\"\n ]\n }\n}"
}
]
}

View File

@@ -0,0 +1,28 @@
"use strict";
/**
* Project Caffeine v0.0.3
* Copyright (c) 2025-2026 Gitconomy Research
*
* SPDX-License-Identifier: MIT
*
* Contributors:
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.literatureSchema = void 0;
const zod_1 = require("zod");
/**
* 文献标准化 JSON 的 Zod 模型
*/
exports.literatureSchema = zod_1.z.object({
title: zod_1.z.string().min(1, '标题不能为空'),
authors: zod_1.z.array(zod_1.z.string()).min(1, '作者列表不能为空'),
abstract: zod_1.z.string().optional(),
link: zod_1.z.string().url('链接格式不正确').optional(),
publication_year: zod_1.z.number().int().min(1900).optional(),
citation_count: zod_1.z.number().int().min(0).optional(),
source: zod_1.z.string().min(1, '来源不能为空'),
doi: zod_1.z.string().optional(),
keywords: zod_1.z.array(zod_1.z.string()).optional(),
});
//# sourceMappingURL=literatureSchema.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"literatureSchema.js","sourceRoot":"","sources":["../../src/models/literatureSchema.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAEH,6BAAwB;AAExB;;GAEG;AACU,QAAA,gBAAgB,GAAG,OAAC,CAAC,MAAM,CAAC;IACvC,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC;IAClC,OAAO,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC;IAC/C,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE;IAC1C,gBAAgB,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;IACvD,cAAc,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IAClD,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC;IACnC,GAAG,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC1B,QAAQ,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;CACzC,CAAC,CAAC"}

View File

@@ -0,0 +1,32 @@
[
{
"id": "strategy_advisor",
"name": "战略分析顾问",
"description": "擅长竞争态势分析,能够清晰区分内外部因素并提出客观见解。",
"systemPrompt": "你是一位资深的战略分析顾问,擅长使用 SWOT 框架进行竞争态势分析,能够清晰区分内外部因素并提出客观见解。"
},
{
"id": "root_cause_analyst",
"name": "根因分析专家",
"description": "擅长通过系统性追问深入挖掘问题的根本原因。",
"systemPrompt": "你是一位擅长根因分析的问题解决专家,能够通过系统性追问深入挖掘问题的根本原因。"
},
{
"id": "macro_environment_analyst",
"name": "宏观环境分析专家",
"description": "擅长运用 PESTLE 框架评估行业外部环境。",
"systemPrompt": "你是一位宏观环境分析专家,擅长运用 PESTLE 框架评估行业外部环境,能够结合具体数据和发展趋势进行深入洞察。"
},
{
"id": "structured_thinker",
"name": "结构化思维分析师",
"description": "擅长运用 5W3H 框架进行系统性拆解。",
"systemPrompt": "你是一位擅长运用 5W3H 框架进行结构化思考的分析师,能够全面覆盖问题的各个维度,确保分析的系统性和深度。"
},
{
"id": "scqa_facilitator",
"name": "SCQA 引导师",
"description": "擅长运用 SCQA 框架构建逻辑严谨的问题分析报告。",
"systemPrompt": "你是一位资深战略分析师,擅长运用 SCQA 框架构建逻辑严谨的问题分析报告。"
}
]

View File

@@ -0,0 +1,48 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.searchArxivSchema = exports.saveNoteSchema = exports.readNoteSchema = exports.generateSearchQueriesSchema = void 0;
/**
* Project Caffeine v0.0.3
* Copyright (c) 2025-2026 Gitconomy Research
*
* SPDX-License-Identifier: MIT
*
* Contributors:
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
*/
const zod_1 = require("zod");
/**
* 检索词生成工具的校验模式。
* 使用 .min(1) 确保查询语句不为空。
*/
exports.generateSearchQueriesSchema = zod_1.z.object({
query: zod_1.z.string().min(1, '查询语句不能为空'),
});
/**
* 读取笔记工具的校验模式。
* 对应 toolsController 中的 handleReadLocalNote。
*/
exports.readNoteSchema = zod_1.z.object({
filename: zod_1.z.string()
.min(1, '文件名不能为空')
.refine((name) => name.toLowerCase().endsWith('.md'), { message: '文件名必须以 .md 结尾' }),
});
/**
* 保存笔记工具的校验模式。
* 对应 toolsController 中的 handleSaveNote。
*/
exports.saveNoteSchema = zod_1.z.object({
filename: zod_1.z.string()
.min(1, '文件名不能为空')
.refine((name) => name.toLowerCase().endsWith('.md'), { message: '文件名必须以 .md 结尾' }),
content: zod_1.z.string().min(1, '内容不能为空'),
});
/**
* 文献检索工具的校验模式。
* 对应 toolsController 中的 handleSearchArxiv。
*/
exports.searchArxivSchema = zod_1.z.object({
query: zod_1.z.string().min(1, '检索词不能为空'),
maxResults: zod_1.z.number().optional().default(5).describe('最大返回数量'),
});
//# sourceMappingURL=schemas.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../src/models/schemas.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;GAQG;AACH,6BAAwB;AAExB;;;GAGG;AACU,QAAA,2BAA2B,GAAG,OAAC,CAAC,MAAM,CAAC;IAClD,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC;CACrC,CAAC,CAAC;AAEH;;;GAGG;AACU,QAAA,cAAc,GAAG,OAAC,CAAC,MAAM,CAAC;IACrC,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE;SACjB,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC;SACjB,MAAM,CACL,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,EAC5C,EAAE,OAAO,EAAE,eAAe,EAAE,CAC7B;CACJ,CAAC,CAAC;AAEH;;;GAGG;AACU,QAAA,cAAc,GAAG,OAAC,CAAC,MAAM,CAAC;IACrC,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE;SACjB,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC;SACjB,MAAM,CACL,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,EAC5C,EAAE,OAAO,EAAE,eAAe,EAAE,CAC7B;IACH,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC;CACrC,CAAC,CAAC;AAEH;;;GAGG;AACU,QAAA,iBAAiB,GAAG,OAAC,CAAC,MAAM,CAAC;IACxC,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC;IACnC,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;CAChE,CAAC,CAAC"}

View File

@@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const arxivService_1 = require("../arxivService");
// Mock 全局的 fetch 函数以防真实发起网络请求
global.fetch = jest.fn();
describe('Arxiv Service', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('应该成功解析 Arxiv 的 XML 并返回文献数组', async () => {
const mockXml = `
<feed>
<entry>
<id>http://arxiv.org/abs/1234.5678</id>
<title>Quantum Machine Learning</title>
<summary>This is a summary of QML.</summary>
</entry>
</feed>
`;
global.fetch.mockResolvedValue({
ok: true,
text: jest.fn().mockResolvedValue(mockXml)
});
const results = await (0, arxivService_1.searchArxiv)('Quantum', 1);
expect(results).toHaveLength(1);
expect(results[0].title).toBe('Quantum Machine Learning');
expect(results[0].id).toBe('http://arxiv.org/abs/1234.5678');
});
it('当网络请求失败时应该抛出错误', async () => {
global.fetch.mockResolvedValue({
ok: false,
status: 500
});
await expect((0, arxivService_1.searchArxiv)('Error', 1)).rejects.toThrow('Arxiv API 响应错误: 500');
});
});
//# sourceMappingURL=arxivService.test.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"arxivService.test.js","sourceRoot":"","sources":["../../../src/services/__test__/arxivService.test.ts"],"names":[],"mappings":";;AAAA,kDAA8C;AAE9C,8BAA8B;AAC9B,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC;AAEzB,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,UAAU,CAAC,GAAG,EAAE;QACd,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;QAC1C,MAAM,OAAO,GAAG;;;;;;;;KAQf,CAAC;QACD,MAAM,CAAC,KAAmB,CAAC,iBAAiB,CAAC;YAC5C,EAAE,EAAE,IAAI;YACR,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,OAAO,CAAC;SAC3C,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,MAAM,IAAA,0BAAW,EAAC,SAAS,EAAE,CAAC,CAAC,CAAC;QAChD,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QAC1D,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gBAAgB,EAAE,KAAK,IAAI,EAAE;QAC7B,MAAM,CAAC,KAAmB,CAAC,iBAAiB,CAAC;YAC5C,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG;SACZ,CAAC,CAAC;QAEH,MAAM,MAAM,CAAC,IAAA,0BAAW,EAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}

View File

@@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const arxivService_1 = require("../arxivService");
// Mock 全局的 fetch 函数以防真实发起网络请求
global.fetch = jest.fn();
describe('Arxiv Service', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('应该成功解析 Arxiv 的 XML 并返回文献数组', async () => {
const mockXml = `
<feed>
<entry>
<id>http://arxiv.org/abs/1234.5678</id>
<title>Quantum Machine Learning</title>
<summary>This is a summary of QML.</summary>
</entry>
</feed>
`;
global.fetch.mockResolvedValue({
ok: true,
text: jest.fn().mockResolvedValue(mockXml)
});
const results = await (0, arxivService_1.searchArxiv)('Quantum', 1);
expect(results).toHaveLength(1);
expect(results[0].title).toBe('Quantum Machine Learning');
expect(results[0].id).toBe('http://arxiv.org/abs/1234.5678');
});
it('当网络请求失败时应该抛出错误', async () => {
global.fetch.mockResolvedValue({
ok: false,
status: 500
});
await expect((0, arxivService_1.searchArxiv)('Error', 1)).rejects.toThrow('Arxiv API 响应错误: 500');
});
});
//# sourceMappingURL=arxivService.test.ts.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"arxivService.test.ts.js","sourceRoot":"","sources":["../../../src/services/__test__/arxivService.test.ts.ts"],"names":[],"mappings":";;AAAA,kDAA8C;AAE9C,8BAA8B;AAC9B,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC;AAEzB,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,UAAU,CAAC,GAAG,EAAE;QACd,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;QAC1C,MAAM,OAAO,GAAG;;;;;;;;KAQf,CAAC;QACD,MAAM,CAAC,KAAmB,CAAC,iBAAiB,CAAC;YAC5C,EAAE,EAAE,IAAI;YACR,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,OAAO,CAAC;SAC3C,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,MAAM,IAAA,0BAAW,EAAC,SAAS,EAAE,CAAC,CAAC,CAAC;QAChD,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QAC1D,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gBAAgB,EAAE,KAAK,IAAI,EAAE;QAC7B,MAAM,CAAC,KAAmB,CAAC,iBAAiB,CAAC;YAC5C,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG;SACZ,CAAC,CAAC;QAEH,MAAM,MAAM,CAAC,IAAA,0BAAW,EAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}

View File

@@ -0,0 +1,58 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Project Caffeine
* 单元测试: intentService.ts
*/
const intentService_1 = require("../intentService");
describe('intentService -> generateSearchQueries', () => {
it('异常流:当输入为空字符串或全空格时,应当返回默认的后备检索词', () => {
// 验证空字符串
expect((0, intentService_1.generateSearchQueries)('')).toEqual(['通用研究主题']);
// 验证全空格
expect((0, intentService_1.generateSearchQueries)(' ')).toEqual(['通用研究主题']);
// 验证 null/undefined (通过 any 强制绕过 ts 检查,确保防御性编程生效)
expect((0, intentService_1.generateSearchQueries)(null)).toEqual(['通用研究主题']);
});
it('正常流:当输入包含多个有效关键词时,应当正确分词、去重并返回', () => {
// 包含重复的 "人工智能"
const query = '人工智能 机器学习 深度学习 人工智能';
const result = (0, intentService_1.generateSearchQueries)(query);
// 验证去重逻辑
expect(result).toEqual(['人工智能', '机器学习', '深度学习']);
expect(result).toHaveLength(3);
});
it('边界测试:当输入包含各种中英文标点符号时,应当正确替换为空格并分词', () => {
const query = 'AI芯片市场趋势2026;未来发展、产业格局';
const result = (0, intentService_1.generateSearchQueries)(query);
// 验证所有的标点都被视为了分隔符
expect(result).toEqual(['AI芯片', '市场趋势', '2026', '未来发展', '产业格局']);
});
it('边界测试:当有效检索词少于 3 个时,应当触发补全机制', () => {
const query = '量子计算';
const result = (0, intentService_1.generateSearchQueries)(query);
// 验证数量是否被补全到了 3 个
expect(result).toHaveLength(3);
// 验证第一项是有效词本身
expect(result[0]).toBe('量子计算');
// 验证后两项是被 "${query} 相关研究" 占位补全的
expect(result[1]).toBe('量子计算 相关研究');
expect(result[2]).toBe('量子计算 相关研究');
});
it('边界测试:当有效检索词超过 5 个时,应当执行截取操作', () => {
const query = '苹果 香蕉 橘子 葡萄 西瓜 芒果 樱桃';
const result = (0, intentService_1.generateSearchQueries)(query);
// 验证长度严格限制在 5
expect(result).toHaveLength(5);
// 验证截取的是前 5 个有效词
expect(result).toEqual(['苹果', '香蕉', '橘子', '葡萄', '西瓜']);
});
it('边界测试:应当自动过滤掉长度小于 2 的无意义单字(如停用词)', () => {
// "论"、"的"、"与" 长度均为 1应该被抛弃
const query = '论 AI 的 发展 与 IT 行业';
const result = (0, intentService_1.generateSearchQueries)(query);
// 验证单字被正确过滤
expect(result).toEqual(['AI', '发展', 'IT', '行业']);
});
});
//# sourceMappingURL=intentService.test.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"intentService.test.js","sourceRoot":"","sources":["../../../src/services/__test__/intentService.test.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,2 @@
"use strict";
//# sourceMappingURL=literatureService.test.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"literatureService.test.js","sourceRoot":"","sources":["../../../src/services/__test__/literatureService.test.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,144 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
// 1. 全局 Mock fs/promises 模块
jest.mock('fs/promises');
// 2. 全局 Mock personas.json提供测试专用的角色系统提示词
jest.mock('../../models/personas/personas.json', () => [
{ id: 'test_advisor', name: '测试顾问', systemPrompt: '这是一条测试专用的系统提示词' }
], { virtual: true });
describe('promptService', () => {
// 使用 any 替代 typeof import避免部分环境中未深度配置 ts-jest 导致的 Babel 解析报错
let promptService;
// 用于承载每次重置后的全新 fs mock 实例
let fsMock;
beforeEach(async () => {
jest.clearAllMocks(); // 清理 mock 的调用统计
jest.resetModules(); // 核心:重置模块注册表,清空 promptService 内部的 frameworksCache 状态
// 【关键修复】在 resetModules 之后,必须重新 require mock 的 fs 模块!
// 否则 promptService 内部引用的 fs 和这里外部的 fs 不是同一个实例,导致 mock 返回值失效并引发 TypeError。
fsMock = require('fs/promises');
promptService = await Promise.resolve().then(() => __importStar(require('../promptService')));
});
// 用于测试的伪造思维框架 JSON 数据
const mockFrameworkJson = {
name: "test_framework",
description: "用于测试的伪造框架",
parameters: [{ name: "topic", description: "测试主题", required: true }],
template: "请分析这个主题:{{topic}}",
persona: "test_advisor",
examples: [
{
input: { topic: "人工智能" },
output: "人工智能的分析结果"
}
]
};
describe('listFrameworks', () => {
it('应当成功读取并返回框架的元数据(且必须剔除 template 等内部字段)', async () => {
// Given: 使用重新获取的 fsMock 模拟文件系统的返回
fsMock.readdir.mockResolvedValueOnce(['test_framework.json']);
fsMock.readFile.mockResolvedValueOnce(JSON.stringify(mockFrameworkJson));
// When: 调用服务
const result = await promptService.listFrameworks();
// Then: 断言文件系统调用次数及返回值格式
expect(fsMock.readdir).toHaveBeenCalledTimes(1);
expect(fsMock.readFile).toHaveBeenCalledTimes(1);
expect(result).toHaveLength(1);
// 验证返回的元数据对象是否符合预期
expect(result[0]).toEqual({
name: "test_framework",
description: "用于测试的伪造框架",
parameters: [{ name: "topic", description: "测试主题", required: true }]
});
// 断言安全红线:不应该将模板内容暴露在 list 接口中
expect(result[0].template).toBeUndefined();
});
it('异常流:当框架目录读取失败时,应当安全捕获错误并返回空数组', async () => {
// 【新增优化】拦截并静音 console.error避免预期的错误日志污染终端测试面板
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
// 模拟磁盘异常
fsMock.readdir.mockRejectedValueOnce(new Error('目录不存在或无权限'));
const result = await promptService.listFrameworks();
// 验证业务逻辑:服务不会崩溃,而是返回容错的空数组
expect(result).toEqual([]);
// 验证系统确实捕获并打印了预期的错误
expect(consoleSpy).toHaveBeenCalledWith('[PromptService] 加载框架失败:', expect.any(Error));
// 测试完毕后恢复 console.error 的正常行为
consoleSpy.mockRestore();
});
});
describe('getFramework', () => {
beforeEach(() => {
// 为 getFramework 的测试统一挂载成功的 Mock 返回
fsMock.readdir.mockResolvedValue(['test_framework.json']);
fsMock.readFile.mockResolvedValue(JSON.stringify(mockFrameworkJson));
});
it('异常流:当请求不存在的框架名称时,应当抛出明确的错误', async () => {
await expect(promptService.getFramework('unknown_framework', {}))
.rejects.toThrow('框架 "unknown_framework" 不存在');
});
it('核心逻辑:应当正确解析角色,并组装 System、Few-Shot 及 Current User 消息序列', async () => {
// When: 请求框架,并传入动态参数
const result = await promptService.getFramework('test_framework', { topic: '云计算' });
const messages = result.messages;
// Then: 应当精确包含 4 条消息
// (1x System, 1x Example User, 1x Example Assistant, 1x Current User)
expect(messages).toHaveLength(4);
// 1. 验证 System 提示词 (是否成功匹配并读取到了 Mock 的 persona 数据)
expect(messages[0].role).toBe('system');
expect(messages[0].content.text).toBe('这是一条测试专用的系统提示词');
// 2. 验证 Few-Shot 示例中的 User 消息 (模板占位符是否被示例 input 正确替换)
expect(messages[1].role).toBe('user');
expect(messages[1].content.text).toBe('请分析这个主题:人工智能');
// 3. 验证 Few-Shot 示例中的 Assistant 消息
expect(messages[2].role).toBe('assistant');
expect(messages[2].content.text).toBe('人工智能的分析结果');
// 4. 验证当前用户请求 ({{topic}} 占位符是否被传入的 '云计算' 正确替换)
expect(messages[3].role).toBe('user');
expect(messages[3].content.text).toBe('请分析这个主题:云计算');
});
it('边界情况:如果多次调用 getFramework验证内部框架缓存 (frameworksCache) 是否生效', async () => {
// 第一次调用,会触发读盘
await promptService.getFramework('test_framework', { topic: '测试1' });
// 第二次调用,应该直接命中内部的 frameworksCache 缓存
await promptService.getFramework('test_framework', { topic: '测试2' });
// 断言:整个生命周期中,读盘操作应只有 1 次
expect(fsMock.readdir).toHaveBeenCalledTimes(1);
expect(fsMock.readFile).toHaveBeenCalledTimes(1);
});
});
});
//# sourceMappingURL=promptService.test.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"promptService.test.js","sourceRoot":"","sources":["../../../src/services/__test__/promptService.test.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,107 @@
"use strict";
/**
* Project Caffeine v0.1.1
* Copyright (c) 2025-2026 Gitconomy Research
*
* SPDX-License-Identifier: MIT
*
* Contributors:
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.listObsidianNotes = listObsidianNotes;
exports.readObsidianNote = readObsidianNote;
exports.saveNote = saveNote;
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
/**
* 本地知识库的根目录路径。
*
* 该目录存放所有 Markdown 笔记文件,所有文件操作均限定在此目录内,
* 以防止路径遍历攻击。
*
* @constant {string}
*/
const OBSIDIAN_VAULT_PATH = '/home/wguo/Downloads/MyVault'; // 【⚠️ 重要配置】请修改为你电脑上真实的 Markdown 笔记文件夹绝对路径!
/**
* 列出知识库中所有 Markdown 笔记的文件名。
*
* 该函数读取 OBSIDIAN_VAULT_PATH 目录下的所有文件,过滤出以 .md 结尾
* (不区分大小写)的文件,并返回文件名列表。若目录不存在或无权限访问,
* 则返回空数组并打印错误日志。
*
* @returns {Promise<string[]>} 包含所有笔记文件名的数组,若失败则返回空数组。
*/
async function listObsidianNotes() {
try {
const files = await promises_1.default.readdir(OBSIDIAN_VAULT_PATH);
return files.filter(file => file.toLowerCase().endsWith('.md'));
}
catch (error) {
console.error(`[Project Caffeine] 无法读取知识库目录: ${error.message}`);
return [];
}
}
/**
* 读取指定笔记文件的完整内容。
*
* 该函数首先对文件名进行安全校验,确保文件位于知识库目录内,
* 防止路径遍历攻击。校验通过后,读取文件内容并返回。
*
* @param {string} filename - 要读取的笔记文件名(必须包含 .md 后缀)
* @returns {Promise<string>} 笔记文件的文本内容
* @throws {Error} 当文件名导致路径越界时抛出安全警告
* @throws {Error} 当文件不存在或无权限读取时抛出错误
*/
async function readObsidianNote(filename) {
const targetPath = path_1.default.resolve(OBSIDIAN_VAULT_PATH, filename);
const safeVaultPath = path_1.default.resolve(OBSIDIAN_VAULT_PATH);
// 核心防御:防止大模型通过传入 "../../" 读取系统敏感文件
if (!targetPath.startsWith(safeVaultPath)) {
throw new Error(`安全警告:越权访问拦截!禁止读取目录外的文件: ${filename}`);
}
try {
const content = await promises_1.default.readFile(targetPath, 'utf-8');
return content;
}
catch (error) {
throw new Error(`无法读取笔记 [${filename}]: 文件可能不存在或无权限。`);
}
}
/**
* 保存笔记到本地知识库。
*
* 该函数将内容写入指定文件,执行以下校验和操作:
* 1. 验证文件名是否以 .md 结尾。
* 2. 验证文件路径是否在知识库目录内,防止路径遍历攻击。
* 3. 确保知识库目录存在(若不存在则自动创建)。
* 4. 将内容写入文件。
*
* @param {string} filename - 笔记文件名(必须以 .md 结尾)
* @param {string} content - 笔记内容Markdown 格式)
* @returns {Promise<string>} 保存成功的提示信息,包含文件绝对路径
* @throws {Error} 当文件名不以 .md 结尾时抛出错误
* @throws {Error} 当文件名导致路径越界时抛出错误
* @throws {Error} 当目录创建失败或文件写入失败时抛出错误
*/
async function saveNote(filename, content) {
// 1. 验证文件名是否以 .md 结尾
if (!filename.endsWith('.md')) {
throw new Error('文件名必须以 .md 结尾');
}
// 2. 防止路径遍历攻击:解析绝对路径,并检查是否在 NOTES_DIR 下
const fullPath = path_1.default.resolve(OBSIDIAN_VAULT_PATH, filename);
const relative = path_1.default.relative(OBSIDIAN_VAULT_PATH, fullPath);
if (relative.startsWith('..') || path_1.default.isAbsolute(relative)) {
throw new Error('无效的文件名,不允许访问上层目录');
}
// 3. 确保目标目录存在(可选,如果 NOTES_DIR 必须存在则可跳过)
await promises_1.default.mkdir(OBSIDIAN_VAULT_PATH, { recursive: true });
// 4. 写入文件
await promises_1.default.writeFile(fullPath, content, 'utf-8');
return `笔记已保存至: ${fullPath}`;
}
//# sourceMappingURL=resourceService.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"resourceService.js","sourceRoot":"","sources":["../../../src/services/__test__/resourceService.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;;;AA2BH,8CAQC;AAcD,4CAeC;AAmBD,4BAmBC;AApGD,2DAA6B;AAC7B,gDAAwB;AAExB;;;;;;;GAOG;AAGH,MAAM,mBAAmB,GAAG,8BAA8B,CAAC,CAAC,2CAA2C;AAEvG;;;;;;;;GAQG;AAEI,KAAK,UAAU,iBAAiB;IACnC,IAAI,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,kBAAE,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;QACpD,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IACpE,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,iCAAiC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAChE,OAAO,EAAE,CAAC;IACd,CAAC;AACL,CAAC;AAED;;;;;;;;;;GAUG;AAEI,KAAK,UAAU,gBAAgB,CAAC,QAAgB;IACnD,MAAM,UAAU,GAAG,cAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAC/D,MAAM,aAAa,GAAG,cAAI,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAExD,mCAAmC;IACnC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,kBAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACvD,OAAO,OAAO,CAAC;IACnB,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,WAAW,QAAQ,iBAAiB,CAAC,CAAC;IAC1D,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AAEI,KAAK,UAAU,QAAQ,CAAC,QAAgB,EAAE,OAAe;IAC9D,qBAAqB;IACrB,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;IACnC,CAAC;IAED,wCAAwC;IACxC,MAAM,QAAQ,GAAG,cAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAC7D,MAAM,QAAQ,GAAG,cAAI,CAAC,QAAQ,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAC9D,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,cAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACtC,CAAC;IAED,wCAAwC;IACxC,MAAM,kBAAE,CAAC,KAAK,CAAC,mBAAmB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzD,UAAU;IACV,MAAM,kBAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC/C,OAAO,WAAW,QAAQ,EAAE,CAAC;AAC/B,CAAC"}

View File

@@ -0,0 +1,153 @@
"use strict";
/**
* Project Caffeine - Sprint 3
* 测试意图识别模式下的核心工具分发器 (Tools Controller)
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const toolsController_1 = require("../../controllers/toolsController");
const resourceService = __importStar(require("../../services/resourceService"));
const arxivService = __importStar(require("../../services/arxivService"));
const fs = __importStar(require("fs"));
// ====================================================
// 1. 全局 Mock 外部依赖 (防止测试时发生真实读写和网络请求)
// ====================================================
jest.mock('fs');
jest.mock('../../services/resourceService', () => ({
saveNote: jest.fn().mockResolvedValue('保存成功'),
listObsidianNotes: jest.fn().mockResolvedValue(['test-note.md', 'ai-trend.md']),
readObsidianNote: jest.fn().mockResolvedValue('这是模拟的本地笔记内容')
}));
jest.mock('../../services/arxivService', () => ({
searchArxiv: jest.fn().mockResolvedValue([
{ id: 'http://arxiv.org/abs/1234', title: 'Test Paper', summary: 'Mock Summary' }
])
}));
describe('Tools Controller - Sprint 3 (意图驱动与容错机制)', () => {
beforeEach(() => {
// 每次测试前清理 Mock 调用记录
jest.clearAllMocks();
});
// ====================================================
// 测试: 未知工具防呆
// ====================================================
it('当调用未知工具时,应返回 isError 为 true 的友好提示', async () => {
const result = await (0, toolsController_1.handleToolCall)('unknown_tool', {});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('未知工具: unknown_tool');
});
// ====================================================
// 测试: 意图 3 - 保存笔记工具 (重点测试大模型容错能力)
// ====================================================
describe('Tool: save_note (容错版保存工具)', () => {
it('当大模型老老实实传入【纯文本字符串】时,应直接正常保存', async () => {
const result = await (0, toolsController_1.handleToolCall)('save_note', { filename: 'output.md', content: '这是一段完美的Markdown文本' });
expect(result.isError).toBeUndefined();
expect(result.content[0].text).toBe('保存成功');
expect(resourceService.saveNote).toHaveBeenCalledWith('output.md', '这是一段完美的Markdown文本');
});
it('🚨 容错测试:当大模型错误地传入【深度嵌套的 JSON 对象】时,应自动将其序列化,而不是崩溃', async () => {
// 模拟大模型发生幻觉,把整个分析对象当成了参数传进来
const mockJsonObject = {
title: "行业分析报告",
data: { trend: "上升", keywords: ["AI", "Quantum"] }
};
const result = await (0, toolsController_1.handleToolCall)('save_note', { filename: 'report.md', content: mockJsonObject });
expect(result.isError).toBeUndefined();
expect(result.content[0].text).toBe('保存成功');
// 期望底层调用保存时Controller 已经非常聪明地把 JSON 对象转换为了带缩进的字符串
const expectedString = JSON.stringify(mockJsonObject, null, 2);
expect(resourceService.saveNote).toHaveBeenCalledWith('report.md', expectedString);
});
it('当缺少必要参数时,应优雅地返回错误信息', async () => {
const result = await (0, toolsController_1.handleToolCall)('save_note', { filename: 'error.md' }); // 故意漏掉 content
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('缺少 filename 或 content 参数');
});
});
// ====================================================
// 测试: 意图 2 - 获取思维框架模板
// ====================================================
describe('Tool: fetch_framework_template (抗死循环剥离器)', () => {
it('应该正确读取 JSON 文件,剥离外壳,并【强制附加刹车指令】防止大模型死循环', async () => {
// 伪造一个框架 JSON 文件内容
const mockFrameworkJson = JSON.stringify({
messages: [
{ role: 'system', content: { text: '我是系统架构师' } },
{ role: 'user', content: { text: '请按 SWOT 分析化验' } }
]
});
fs.readFileSync.mockReturnValue(mockFrameworkJson);
const result = await (0, toolsController_1.handleToolCall)('fetch_framework_template', { framework_name: 'swot' });
expect(result.isError).toBeUndefined();
const outputText = result.content[0].text;
// 断言:必须包含这句护身符,阻止大模型不断重复调工具
expect(outputText).toContain('立即停止调用任何工具');
// 断言JSON 外壳已经被剥离,直接透出了内部指导文字
expect(outputText).toContain('我是系统架构师');
expect(outputText).toContain('请按 SWOT 分析化验');
});
});
// ====================================================
// 测试: 意图 1 - 文献检索
// ====================================================
describe('Tool: search_arxiv', () => {
it('应该正确调用服务并格式化返回带 Markdown 语法的文献列表', async () => {
const result = await (0, toolsController_1.handleToolCall)('search_arxiv', { query: 'AI' });
expect(result.isError).toBeUndefined();
expect(arxivService.searchArxiv).toHaveBeenCalledWith('AI', 5);
const outputText = result.content[0].text;
expect(outputText).toContain('找到了关于 "AI" 的相关文献');
expect(outputText).toContain('Test Paper');
expect(outputText).toContain('http://arxiv.org/abs/1234');
});
});
// ====================================================
// 测试: 意图 4 - 本地笔记相关
// ====================================================
describe('Local Notes Tools', () => {
it('list_local_notes 应该正确返回笔记列表', async () => {
const result = await (0, toolsController_1.handleToolCall)('list_local_notes', {});
expect(result.content[0].text).toContain('test-note.md');
expect(result.content[0].text).toContain('ai-trend.md');
});
it('read_local_note 应该正确读取具体笔记内容', async () => {
const result = await (0, toolsController_1.handleToolCall)('read_local_note', { filename: 'test-note.md' });
expect(resourceService.readObsidianNote).toHaveBeenCalledWith('test-note.md');
expect(result.content[0].text).toContain('这是模拟的本地笔记内容');
});
});
});
//# sourceMappingURL=resourceService.test.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"resourceService.test.js","sourceRoot":"","sources":["../../../src/services/__test__/resourceService.test.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,45 @@
"use strict";
/**
* Project Caffeine v0.0.3
* Copyright (c) 2025-2026 Gitconomy Research
*
* SPDX-License-Identifier: MIT
*
* Contributors:
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.searchArxiv = searchArxiv;
/**
* 连接 Arxiv API 进行文献检索。
* * @param {string} query - 检索关键字
* @param {number} [maxResults=5] - 最大返回数量,默认为 5
* @returns {Promise<Array<{ id: string, title: string, summary: string }>>} 包含文献 ID、标题和摘要的数组
* @throws {Error} 若网络请求或 XML 解析失败则抛出错误
*/
async function searchArxiv(query, maxResults = 5) {
const url = `http://export.arxiv.org/api/query?search_query=all:${encodeURIComponent(query)}&start=0&max_results=${maxResults}`;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Arxiv API 响应错误: ${response.status}`);
}
const xmlData = await response.text();
// 使用正则提取 entry 节点(轻量级解析,避免引入繁重的 XML 解析库)
const entries = xmlData.split('<entry>').slice(1);
return entries.map(entry => {
const titleMatch = entry.match(/<title>([\s\S]*?)<\/title>/);
const summaryMatch = entry.match(/<summary>([\s\S]*?)<\/summary>/);
const idMatch = entry.match(/<id>([\s\S]*?)<\/id>/);
return {
id: idMatch ? idMatch[1].trim() : '未知链接',
title: titleMatch ? titleMatch[1].replace(/\s+/g, ' ').trim() : '无标题',
summary: summaryMatch ? summaryMatch[1].replace(/\s+/g, ' ').trim() : '无摘要'
};
});
}
catch (error) {
throw new Error(`获取文献失败: ${error.message}`);
}
}
//# sourceMappingURL=arxivService.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"arxivService.js","sourceRoot":"","sources":["../../src/services/arxivService.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AASH,kCA2BC;AAlCD;;;;;;GAMG;AACI,KAAK,UAAU,WAAW,CAAC,KAAa,EAAE,aAAqB,CAAC;IACrE,MAAM,GAAG,GAAG,sDAAsD,kBAAkB,CAAC,KAAK,CAAC,wBAAwB,UAAU,EAAE,CAAC;IAEhI,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,mBAAmB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QACxD,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACtC,yCAAyC;QACzC,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAElD,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;YACzB,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;YAC7D,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAC;YACnE,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;YAEpD,OAAO;gBACL,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM;gBACxC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK;gBACrE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK;aAC5E,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,WAAW,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9C,CAAC;AACH,CAAC"}

View File

@@ -0,0 +1,46 @@
"use strict";
/**
* 意图分析服务:根据用户查询生成交互选项(硬编码)
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.analyzeIntent = analyzeIntent;
function analyzeIntent(query) {
const lowerQuery = query.toLowerCase();
// 检测是否包含具体框架名称(支持常见写法)
const frameworkMatch = lowerQuery.match(/\b(scqa|5whys|5w3h|swot|pestle)\b/);
const hasFramework = !!frameworkMatch;
// 检测是否包含明确的检索意图
const hasSearch = /论文|文献|检索|查找|找|paper|article|survey|literature|search/.test(lowerQuery);
let options = [];
let message = '';
if (hasFramework) {
// 用户指定了框架,直接提供分析选项
const framework = frameworkMatch[0].toLowerCase(); // 转为小写以匹配 prompt 名称
options = [{
id: 'A',
action: 'use_prompt', // 特殊标记,指示模型调用 prompt
params: { query, framework }
}];
message = `检测到您希望使用 ${framework} 框架进行分析。我将为您调用对应的 prompt 工具。请回复 A 确认开始分析。`;
}
else if (hasSearch) {
// 用户明确想检索
options = [{
id: 'B',
action: 'search_academic_literature',
params: { query, limit: 5 }
}];
message = `检测到您希望检索文献。请回复 B 确认检索。`;
}
else {
// 模糊查询,提供三个标准选项
options = [
{ id: 'A', action: 'use_prompt', params: { query } },
{ id: 'B', action: 'search_academic_literature', params: { query, limit: 5 } },
{ id: 'C', action: 'generate_search_queries', params: { query } }
];
message = `您希望我如何帮助您?请选择:\nA. 使用思维框架进行分析(可指定框架,如 SWOT、5Whys 等)\nB. 直接检索相关学术文献\nC. 先生成英文搜索关键词,再决定如何检索\n请回复 A、B 或 C。`;
}
return { message, options };
}
//# sourceMappingURL=intentAnalysisService.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"intentAnalysisService.js","sourceRoot":"","sources":["../../src/services/intentAnalysisService.ts"],"names":[],"mappings":";AAAA;;GAEG;;AAEH,sCAyCC;AAzCD,SAAgB,aAAa,CAAC,KAAa;IACzC,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IAEvC,uBAAuB;IACvB,MAAM,cAAc,GAAG,UAAU,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;IAC7E,MAAM,YAAY,GAAG,CAAC,CAAC,cAAc,CAAC;IAEtC,gBAAgB;IAChB,MAAM,SAAS,GAAG,sDAAsD,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAE1F,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,OAAO,GAAG,EAAE,CAAC;IAEjB,IAAI,YAAY,EAAE,CAAC;QACjB,mBAAmB;QACnB,MAAM,SAAS,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,oBAAoB;QACvE,OAAO,GAAG,CAAC;gBACT,EAAE,EAAE,GAAG;gBACP,MAAM,EAAE,YAAY,EAAE,qBAAqB;gBAC3C,MAAM,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE;aAC7B,CAAC,CAAC;QACH,OAAO,GAAG,YAAY,SAAS,2CAA2C,CAAC;IAC7E,CAAC;SAAM,IAAI,SAAS,EAAE,CAAC;QACrB,UAAU;QACV,OAAO,GAAG,CAAC;gBACT,EAAE,EAAE,GAAG;gBACP,MAAM,EAAE,4BAA4B;gBACpC,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE;aAC5B,CAAC,CAAC;QACH,OAAO,GAAG,wBAAwB,CAAC;IACrC,CAAC;SAAM,CAAC;QACN,gBAAgB;QAChB,OAAO,GAAG;YACR,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE;YACpD,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,4BAA4B,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC9E,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,yBAAyB,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE;SAClE,CAAC;QACF,OAAO,GAAG,yGAAyG,CAAC;IACtH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAC9B,CAAC"}

View File

@@ -0,0 +1,36 @@
"use strict";
/**
* Project Caffeine v0.0.3
* Copyright (c) 2025-2026 Gitconomy Research
*
* SPDX-License-Identifier: MIT
*
* Contributors:
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateSearchQueries = generateSearchQueries;
/**
* 将用户的自然语言查询拆解为专业检索词列表。
*
* @param {string} query - 用户原始查询字符串
* @returns {string[]} 去重后的检索词数组3~5 个)
*/
function generateSearchQueries(query) {
if (!query || query.trim().length === 0) {
return ['通用研究主题'];
}
// 1. 去除常见标点符号,替换为空格
const cleaned = query.replace(/[,。??、;;]/g, ' ');
// 2. 按空白字符分割,过滤掉长度小于 2 的词(避免单字噪音)
const words = cleaned.split(/\s+/).filter(word => word.length >= 2);
// 3. 去重
const uniqueWords = [...new Set(words)];
// 4. 若不足 3 个,补充基于原查询的扩展词
while (uniqueWords.length < 3) {
uniqueWords.push(`${query} 相关研究`);
}
// 5. 截取前 5 个返回
return uniqueWords.slice(0, 5);
}
//# sourceMappingURL=intentService.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"intentService.js","sourceRoot":"","sources":["../../src/services/intentService.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAQH,sDAqBC;AA3BD;;;;;GAKG;AACH,SAAgB,qBAAqB,CAAC,KAAa;IACjD,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACpB,CAAC;IAED,oBAAoB;IACpB,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;IAElD,kCAAkC;IAClC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IAEpE,QAAQ;IACR,MAAM,WAAW,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IAExC,yBAAyB;IACzB,OAAO,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,WAAW,CAAC,IAAI,CAAC,GAAG,KAAK,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,eAAe;IACf,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACjC,CAAC"}

View File

@@ -0,0 +1,17 @@
"use strict";
/**
* Project Caffeine v0.1.1
* 文献检索服务(仅 arXiv
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.saveLiteratureToVault = void 0;
exports.searchAcademicLiterature = searchAcademicLiterature;
const apiClients_1 = require("../utils/apiClients");
const storageService_1 = require("../services/storageService");
Object.defineProperty(exports, "saveLiteratureToVault", { enumerable: true, get: function () { return storageService_1.saveLiteratureAsMarkdown; } });
async function searchAcademicLiterature(query, limit = 10, yearFrom, yearTo) {
const arxivResults = await (0, apiClients_1.fetchArXivPapers)(query, limit, yearFrom, yearTo);
// 如果后续需要添加其他源,可以在这里扩展,目前直接返回 arXiv 结果
return arxivResults.slice(0, limit);
}
//# sourceMappingURL=literatureService.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"literatureService.js","sourceRoot":"","sources":["../../src/services/literatureService.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAMH,4DASC;AAbD,oDAAuD;AAEvD,+DAAsE;AAcjC,sGAd5B,yCAAwB,OAcyB;AAZnD,KAAK,UAAU,wBAAwB,CAC5C,KAAa,EACb,QAAgB,EAAE,EAClB,QAAiB,EACjB,MAAe;IAEf,MAAM,YAAY,GAAG,MAAM,IAAA,6BAAgB,EAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC5E,sCAAsC;IACtC,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AACtC,CAAC"}

View File

@@ -0,0 +1,105 @@
"use strict";
/**
* Project Caffeine v0.0.3
* 意图规划服务:分析用户查询,生成研究计划
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.detectFramework = detectFramework;
exports.extractSearchQuery = extractSearchQuery;
exports.planResearchApproach = planResearchApproach;
// 框架关键词映射表(仅保留专有标识)
const FRAMEWORK_KEYWORDS = {
scqa: ['scqa', 'SCQA'],
'5whys': ['5whys', '5 why', '5why', '五问法', '五问', '根因分析', 'root cause'],
'5w3h': ['5w3h', '5W3H', '五W三H'],
swot: ['swot', 'SWOT', '优势劣势', '机会威胁'],
pestle: ['pestle', 'PESTLE', '宏观环境', '政治经济']
};
/**
* 检测用户查询中是否包含特定框架的关键词,返回匹配的框架名称(第一个匹配的)
*/
function detectFramework(query) {
const lowerQuery = query.toLowerCase();
for (const [framework, keywords] of Object.entries(FRAMEWORK_KEYWORDS)) {
if (keywords.some(keyword => lowerQuery.includes(keyword.toLowerCase()))) {
return framework;
}
}
return null;
}
/**
* 提取可能的检索关键词简单策略原样返回后续可由LLM优化
*/
function extractSearchQuery(query) {
return query;
}
/**
* 分析用户查询,生成研究计划
*/
function planResearchApproach(query) {
const lowerQuery = query.toLowerCase();
const analysisKeywords = ['分析', '评估', '评价', '剖析', '诊断', '拆解', '研究', '探讨'];
const containsAnalysis = analysisKeywords.some(keyword => lowerQuery.includes(keyword));
const searchKeywords = ['论文', '文献', '研究', '最新进展', '综述', 'survey', 'paper', 'article', 'publication', '文献检索', '查找'];
const containsSearch = searchKeywords.some(keyword => lowerQuery.includes(keyword));
const detectedFramework = detectFramework(query);
const steps = [];
if (containsAnalysis || detectedFramework) {
let framework = detectedFramework || 'scqa';
let frameworkDescription = '';
const frameworkDescriptions = {
scqa: '使用 SCQA 框架对问题进行结构化分析',
'5whys': '使用 5 Whys 框架进行根因分析',
'5w3h': '使用 5W3H 框架全面拆解问题',
swot: '使用 SWOT 框架分析优势、劣势、机会、威胁',
pestle: '使用 PESTLE 框架分析宏观环境'
};
frameworkDescription = frameworkDescriptions[framework] || '使用思维框架分析问题';
const params = {};
if (framework === 'scqa') {
params.situation = query;
}
else if (framework === '5whys') {
params.problem = query;
}
else if (framework === '5w3h') {
params.topic = query;
}
else if (framework === 'swot') {
params.entity = query;
}
else if (framework === 'pestle') {
params.domain = query;
}
steps.push({
type: 'prompt',
tool: framework,
params,
description: frameworkDescription
});
}
if (containsSearch || (!containsAnalysis && !detectedFramework)) {
steps.push({
type: 'search',
tool: 'search_academic_literature',
params: {
query: extractSearchQuery(query),
limit: 5
},
description: '检索相关学术文献'
});
}
if (steps.length === 0) {
steps.push({
type: 'search',
tool: 'search_academic_literature',
params: {
query: extractSearchQuery(query),
limit: 5
},
description: '检索相关学术文献'
});
}
return { steps };
}
//# sourceMappingURL=planningService.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"planningService.js","sourceRoot":"","sources":["../../src/services/planningService.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,154 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.listFrameworks = listFrameworks;
exports.getFramework = getFramework;
exports.getFrameworkTemplate = getFrameworkTemplate;
/**
* Project Caffeine v0.0.4
* Copyright (c) 2025-2026 Gitconomy Research
*
* SPDX-License-Identifier: MIT
*
* Contributors:
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
*/
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
/** 框架定义文件存放的目录路径 */
const FRAMEWORKS_DIR = path_1.default.join(__dirname, '../models/frameworks');
/** 角色矩阵文件路径 */
const PERSONAS_PATH = path_1.default.join(__dirname, '../models/personas/personas.json');
/** 框架缓存,避免重复读取文件系统 */
let frameworksCache = null;
/** 角色缓存 */
let personasCache = null;
/**
* 加载角色矩阵personas.json
*
* @returns {Promise<any[]>} 角色数组,若加载失败则返回空数组
*/
async function loadPersonas() {
if (personasCache)
return personasCache;
try {
const content = await promises_1.default.readFile(PERSONAS_PATH, 'utf-8');
personasCache = JSON.parse(content);
return personasCache;
}
catch (error) {
console.error('[PromptService] 加载角色矩阵失败:', error);
return [];
}
}
/**
* 从文件系统加载所有框架 JSON 文件。
*
* @returns {Promise<Framework[]>} 框架对象数组,若加载失败则返回空数组
*/
async function loadFrameworks() {
if (frameworksCache)
return frameworksCache;
try {
const files = await promises_1.default.readdir(FRAMEWORKS_DIR);
const jsonFiles = files.filter(f => f.endsWith('.json'));
const frameworks = await Promise.all(jsonFiles.map(async (file) => {
const content = await promises_1.default.readFile(path_1.default.join(FRAMEWORKS_DIR, file), 'utf-8');
return JSON.parse(content);
}));
frameworksCache = frameworks;
return frameworks;
}
catch (error) {
console.error('[PromptService] 加载框架失败:', error);
return [];
}
}
/**
* 列出所有可用框架的元信息(不含模板、系统提示词和示例)。
*
* @returns {Promise<Array<Omit<Framework, 'template' | 'systemPrompt' | 'examples'>>>}
* 框架元信息列表,每个框架包含名称、描述和参数列表
*/
async function listFrameworks() {
const frameworks = await loadFrameworks();
return frameworks.map(({ name, description, parameters }) => ({
name,
description,
parameters
}));
}
/**
* 获取指定框架的完整提示词消息序列。
*
* @param {string} name - 框架名称
* @param {Record<string, string>} args - 用户传入的参数键值对
* @returns {Promise<PromptResult>} 符合 MCP 规范的消息序列对象
* @throws {Error} 当指定名称的框架不存在时抛出错误
*/
async function getFramework(name, args) {
const frameworks = await loadFrameworks();
const framework = frameworks.find(f => f.name === name);
if (!framework) {
throw new Error(`框架 "${name}" 不存在`);
}
// 确定系统提示词(优先使用角色矩阵)
let systemPrompt = framework.systemPrompt || '';
if (framework.persona) {
const personas = await loadPersonas();
const persona = personas.find(p => p.id === framework.persona);
if (persona) {
systemPrompt = persona.systemPrompt;
}
}
const messages = [];
if (systemPrompt) {
messages.push({
role: 'system',
content: { type: 'text', text: systemPrompt }
});
}
if (framework.examples && Array.isArray(framework.examples)) {
for (const example of framework.examples) {
let exampleUserContent = framework.template;
for (const [key, value] of Object.entries(example.input)) {
exampleUserContent = exampleUserContent.replace(new RegExp(`{{${key}}}`, 'g'), value);
}
messages.push({
role: 'user',
content: { type: 'text', text: exampleUserContent }
});
messages.push({
role: 'assistant',
content: { type: 'text', text: example.output }
});
}
}
let currentUserContent = framework.template;
for (const [key, value] of Object.entries(args)) {
currentUserContent = currentUserContent.replace(new RegExp(`{{${key}}}`, 'g'), value);
}
messages.push({
role: 'user',
content: { type: 'text', text: currentUserContent }
});
return { messages };
}
/**
* 获取指定框架的完整定义(包括模板、示例、系统提示等)。
*
* @param {string} name - 框架名称
* @returns {Promise<Framework>} 完整的 Framework 对象
* @throws {Error} 如果框架不存在则抛出错误
*/
async function getFrameworkTemplate(name) {
const frameworks = await loadFrameworks();
const framework = frameworks.find(f => f.name === name);
if (!framework) {
throw new Error(`框架 "${name}" 不存在`);
}
return framework;
}
//# sourceMappingURL=promptService.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"promptService.js","sourceRoot":"","sources":["../../src/services/promptService.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,64 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.researchAssistant = researchAssistant;
const literatureService_1 = require("./literatureService");
const promptService_1 = require("./promptService");
const planningService_1 = require("./planningService"); // 复用之前的逻辑
async function researchAssistant(query, maxPapers) {
const lowerQuery = query.toLowerCase();
let resultParts = [];
// 检测是否包含分析意图
const analysisKeywords = ['分析', '评估', 'swot', '5whys', 'scqa', 'pestle', '5w3h', '根因', '优势劣势'];
const hasAnalysis = analysisKeywords.some(kw => lowerQuery.includes(kw));
// 检测是否包含检索意图
const searchKeywords = ['论文', '文献', '检索', '查找', '找', 'paper', 'article', 'survey'];
const hasSearch = searchKeywords.some(kw => lowerQuery.includes(kw));
// 检测具体框架
const framework = (0, planningService_1.detectFramework)(query);
// 1. 如果需要分析,调用对应框架
if (hasAnalysis || framework) {
try {
const frameworkName = framework || 'scqa'; // 默认SCQA
const args = {};
// 根据框架填充参数
if (frameworkName === 'scqa')
args.situation = query;
else if (frameworkName === '5whys')
args.problem = query;
else if (frameworkName === '5w3h')
args.topic = query;
else if (frameworkName === 'swot')
args.entity = query;
else if (frameworkName === 'pestle')
args.domain = query;
const promptResult = await (0, promptService_1.getFramework)(frameworkName, args);
const analysisText = promptResult.messages
.filter(m => m.role === 'assistant')
.map(m => m.content.text)
.join('\n\n');
resultParts.push(`## 分析结果 (${frameworkName})\n\n${analysisText}`);
}
catch (error) {
resultParts.push(`## 分析失败\n\n${error}`);
}
}
// 2. 如果需要检索,调用文献检索
if (hasSearch || !hasAnalysis) { // 若没有分析则默认检索
try {
const searchQuery = (0, planningService_1.extractSearchQuery)(query); // 可复用之前的关键词提取逻辑
const papers = await (0, literatureService_1.searchAcademicLiterature)(searchQuery, maxPapers);
if (papers.length === 0) {
resultParts.push(`## 文献检索\n\n未找到相关文献。`);
}
else {
const papersList = papers.map(p => `- **${p.title}** (${p.publication_year})\n ${p.authors.join(', ')}\n [链接](${p.link})`).join('\n\n');
resultParts.push(`## 相关文献\n\n${papersList}`);
}
}
catch (error) {
resultParts.push(`## 文献检索失败\n\n${error}`);
}
}
return resultParts.join('\n\n---\n\n');
}
//# sourceMappingURL=researchAssistantService.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"researchAssistantService.js","sourceRoot":"","sources":["../../src/services/researchAssistantService.ts"],"names":[],"mappings":";;AAIA,8CAyDC;AA7DD,2DAA+D;AAC/D,mDAA+C;AAC/C,uDAAwE,CAAC,UAAU;AAE5E,KAAK,UAAU,iBAAiB,CAAC,KAAa,EAAE,SAAiB;IACtE,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IACvC,IAAI,WAAW,GAAa,EAAE,CAAC;IAE/B,aAAa;IACb,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAC/F,MAAM,WAAW,GAAG,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;IAEzE,aAAa;IACb,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IACnF,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;IAErE,SAAS;IACT,MAAM,SAAS,GAAG,IAAA,iCAAe,EAAC,KAAK,CAAC,CAAC;IAEzC,mBAAmB;IACnB,IAAI,WAAW,IAAI,SAAS,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,aAAa,GAAG,SAAS,IAAI,MAAM,CAAC,CAAC,SAAS;YACpD,MAAM,IAAI,GAA2B,EAAE,CAAC;YACxC,WAAW;YACX,IAAI,aAAa,KAAK,MAAM;gBAAE,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;iBAChD,IAAI,aAAa,KAAK,OAAO;gBAAE,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;iBACpD,IAAI,aAAa,KAAK,MAAM;gBAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;iBACjD,IAAI,aAAa,KAAK,MAAM;gBAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;iBAClD,IAAI,aAAa,KAAK,QAAQ;gBAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;YAEzD,MAAM,YAAY,GAAG,MAAM,IAAA,4BAAY,EAAC,aAAa,EAAE,IAAI,CAAC,CAAC;YAC7D,MAAM,YAAY,GAAG,YAAY,CAAC,QAAQ;iBACvC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC;iBACnC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;iBACxB,IAAI,CAAC,MAAM,CAAC,CAAC;YAChB,WAAW,CAAC,IAAI,CAAC,YAAY,aAAa,QAAQ,YAAY,EAAE,CAAC,CAAC;QACpE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,WAAW,CAAC,IAAI,CAAC,cAAc,KAAK,EAAE,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IAED,mBAAmB;IACnB,IAAI,SAAS,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,aAAa;QAC5C,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,IAAA,oCAAkB,EAAC,KAAK,CAAC,CAAC,CAAC,gBAAgB;YAC/D,MAAM,MAAM,GAAG,MAAM,IAAA,4CAAwB,EAAC,WAAW,EAAE,SAAS,CAAC,CAAC;YACtE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxB,WAAW,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;YAC1C,CAAC;iBAAM,CAAC;gBACN,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAChC,OAAO,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,gBAAgB,QAAQ,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,GAAG,CACzF,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBACf,WAAW,CAAC,IAAI,CAAC,cAAc,UAAU,EAAE,CAAC,CAAC;YAC/C,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,WAAW,CAAC,IAAI,CAAC,gBAAgB,KAAK,EAAE,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,OAAO,WAAW,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;AACzC,CAAC"}

View File

@@ -0,0 +1,124 @@
"use strict";
/**
* Project Caffeine v0.0.3
* Copyright (c) 2025-2026 Gitconomy Research
*
* SPDX-License-Identifier: MIT
*
* Contributors:
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.listObsidianNotes = listObsidianNotes;
exports.readObsidianNote = readObsidianNote;
exports.saveNote = saveNote;
exports.searchNotes = searchNotes;
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
/**
* 本地知识库的根目录路径。
* @constant {string}
*/
const OBSIDIAN_VAULT_PATH = '/home/wguo/Downloads/MyVault'; // 【⚠️ 重要配置】请修改为你电脑上真实的 Markdown 笔记文件夹绝对路径!
/**
* 列出知识库中所有 Markdown 笔记的文件名。
*
* @returns {Promise<string[]>} 包含所有笔记文件名的数组,若失败则返回空数组
*/
async function listObsidianNotes() {
try {
const files = await promises_1.default.readdir(OBSIDIAN_VAULT_PATH);
return files.filter(file => file.toLowerCase().endsWith('.md'));
}
catch (error) {
console.error(`[Project Caffeine] 无法读取知识库目录: ${error.message}`);
return [];
}
}
/**
* 读取指定笔记文件的完整内容。
*
* @param {string} filename - 要读取的笔记文件名(必须包含 .md 后缀)
* @returns {Promise<string>} 笔记文件的文本内容
* @throws {Error} 当文件名导致路径越界时抛出安全警告
* @throws {Error} 当文件不存在或无权限读取时抛出错误
*/
async function readObsidianNote(filename) {
const targetPath = path_1.default.resolve(OBSIDIAN_VAULT_PATH, filename);
const safeVaultPath = path_1.default.resolve(OBSIDIAN_VAULT_PATH);
// 核心防御:防止大模型通过传入 "../../" 读取系统敏感文件
if (!targetPath.startsWith(safeVaultPath)) {
throw new Error(`安全警告:越权访问拦截!禁止读取目录外的文件: ${filename}`);
}
try {
return await promises_1.default.readFile(targetPath, 'utf-8');
}
catch (error) {
throw new Error(`无法读取笔记 [${filename}]: 文件可能不存在或无权限。`);
}
}
/**
* 保存笔记到本地知识库。
*
* @param {string} filename - 笔记文件名(必须以 .md 结尾)
* @param {string} content - 笔记内容Markdown 格式)
* @returns {Promise<string>} 保存成功的提示信息,包含文件绝对路径
* @throws {Error} 当文件名不以 .md 结尾时抛出错误
* @throws {Error} 当文件名导致路径越界时抛出错误
* @throws {Error} 当目录创建失败或文件写入失败时抛出错误
*/
async function saveNote(filename, content) {
if (!filename.endsWith('.md')) {
throw new Error('文件名必须以 .md 结尾');
}
const fullPath = path_1.default.resolve(OBSIDIAN_VAULT_PATH, filename);
const relative = path_1.default.relative(OBSIDIAN_VAULT_PATH, fullPath);
if (relative.startsWith('..') || path_1.default.isAbsolute(relative)) {
throw new Error('无效的文件名,不允许访问上层目录');
}
await promises_1.default.mkdir(OBSIDIAN_VAULT_PATH, { recursive: true });
await promises_1.default.writeFile(fullPath, content, 'utf-8');
return `笔记已保存至: ${fullPath}`;
}
/**
* 在笔记内容中搜索关键词(忽略大小写)。
*
* @param {string[]} keywords - 关键词数组
* @returns {Promise<Array<{ filename: string; snippets: string[] }>>} 匹配的笔记列表,
* 每个包含文件名和上下文片段最多5条
*/
async function searchNotes(keywords) {
const files = await listObsidianNotes();
const results = [];
for (const file of files) {
try {
const content = await readObsidianNote(file);
const lowerContent = content.toLowerCase();
if (keywords.every(kw => lowerContent.includes(kw.toLowerCase()))) {
const snippets = [];
for (const kw of keywords) {
const index = lowerContent.indexOf(kw.toLowerCase());
if (index !== -1) {
const start = Math.max(0, index - 30);
const end = Math.min(content.length, index + kw.length + 30);
let snippet = content.substring(start, end);
if (start > 0)
snippet = '...' + snippet;
if (end < content.length)
snippet = snippet + '...';
snippets.push(snippet);
}
}
results.push({ filename: file, snippets });
}
}
catch (e) {
// 忽略读取失败的文件(如权限问题)
}
}
return results.slice(0, 5);
}
//# sourceMappingURL=resourceService.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"resourceService.js","sourceRoot":"","sources":["../../src/services/resourceService.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,2 @@
"use strict";
//# sourceMappingURL=sessionMagement.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"sessionMagement.js","sourceRoot":"","sources":["../../src/services/sessionMagement.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,158 @@
"use strict";
/**
* Project Caffeine v0.0.3
* Copyright (c) 2025-2026 Gitconomy Research
*
* SPDX-License-Identifier: MIT
*
* Contributors:
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
*
* 会话管理器:维护每个用户会话的状态,支持多轮交互流程控制。
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.sessionManager = exports.SessionManager = exports.SessionState = void 0;
/**
* 会话状态枚举
*/
var SessionState;
(function (SessionState) {
/** 等待用户选择意图 */
SessionState["AWAITING_INTENT"] = "AWAITING_INTENT";
/** 等待用户选择具体框架 */
SessionState["AWAITING_FRAMEWORK_SELECTION"] = "AWAITING_FRAMEWORK_SELECTION";
/** 等待用户提供框架参数 */
SessionState["AWAITING_FRAMEWORK_PARAMETERS"] = "AWAITING_FRAMEWORK_PARAMETERS";
/** 等待用户确认关键词 */
SessionState["AWAITING_KEYWORD_CONFIRMATION"] = "AWAITING_KEYWORD_CONFIRMATION";
/** 等待用户选择是否基于笔记分析 */
SessionState["AWAITING_NOTE_ANALYSIS_CONFIRMATION"] = "AWAITING_NOTE_ANALYSIS_CONFIRMATION";
/** 等待用户指定要读取的笔记 */
SessionState["AWAITING_NOTE_SELECTION"] = "AWAITING_NOTE_SELECTION";
/** 等待用户确认保存 */
SessionState["AWAITING_SAVE_CONFIRMATION"] = "AWAITING_SAVE_CONFIRMATION";
/** 空闲状态 */
SessionState["IDLE"] = "IDLE";
})(SessionState || (exports.SessionState = SessionState = {}));
/**
* 会话管理器类
*/
class SessionManager {
/**
* 创建会话管理器实例
* @param {number} timeout - 会话超时时间(毫秒),默认 1800000 (30分钟)
*/
constructor(timeout = 30 * 60 * 1000) {
this.sessions = new Map();
this.timeout = timeout;
}
/**
* 创建新会话
* @param {string} sessionId - 会话ID
* @returns {Session} 创建的会话对象
*/
createSession(sessionId) {
const session = {
id: sessionId,
state: SessionState.AWAITING_INTENT,
data: {},
lastUpdated: Date.now(),
};
this.sessions.set(sessionId, session);
return session;
}
/**
* 获取会话,如果不存在则创建
* @param {string} sessionId - 会话ID
* @returns {Session} 会话对象
*/
getOrCreateSession(sessionId) {
this.cleanupExpiredSessions();
let session = this.sessions.get(sessionId);
if (!session) {
session = this.createSession(sessionId);
}
else {
session.lastUpdated = Date.now();
}
return session;
}
/**
* 获取会话状态
* @param {string} sessionId - 会话ID
* @returns {SessionState | undefined} 会话状态,如果会话不存在则返回 undefined
*/
getSessionState(sessionId) {
return this.sessions.get(sessionId)?.state;
}
/**
* 更新会话状态
* @param {string} sessionId - 会话ID
* @param {SessionState} newState - 新状态
* @param {Partial<SessionData>} data - 要更新的数据(可选)
* @returns {boolean} 是否更新成功
*/
updateSession(sessionId, newState, data) {
const session = this.sessions.get(sessionId);
if (!session)
return false;
session.state = newState;
if (data) {
session.data = { ...session.data, ...data };
}
session.lastUpdated = Date.now();
return true;
}
/**
* 获取会话数据
* @param {string} sessionId - 会话ID
* @returns {SessionData | undefined} 会话数据
*/
getSessionData(sessionId) {
return this.sessions.get(sessionId)?.data;
}
/**
* 更新会话数据
* @param {string} sessionId - 会话ID
* @param {Partial<SessionData>} data - 要更新的数据
* @returns {boolean} 是否更新成功
*/
updateSessionData(sessionId, data) {
const session = this.sessions.get(sessionId);
if (!session)
return false;
session.data = { ...session.data, ...data };
session.lastUpdated = Date.now();
return true;
}
/**
* 删除会话
* @param {string} sessionId - 会话ID
* @returns {boolean} 是否删除成功
*/
deleteSession(sessionId) {
return this.sessions.delete(sessionId);
}
/**
* 清理过期会话
*/
cleanupExpiredSessions() {
const now = Date.now();
for (const [id, session] of this.sessions.entries()) {
if (now - session.lastUpdated > this.timeout) {
this.sessions.delete(id);
}
}
}
/**
* 获取所有会话数量(用于监控)
* @returns {number} 会话数量
*/
getSessionCount() {
return this.sessions.size;
}
}
exports.SessionManager = SessionManager;
// 导出默认实例(单例)
exports.sessionManager = new SessionManager();
//# sourceMappingURL=sessionManager.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"sessionManager.js","sourceRoot":"","sources":["../../src/services/sessionManager.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,57 @@
"use strict";
/**
* Project Caffeine v0.0.3
* Copyright (c) 2025-2026 Gitconomy Research
*
* SPDX-License-Identifier: MIT
*
* Contributors:
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.saveLiteratureAsMarkdown = saveLiteratureAsMarkdown;
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
const yamlHelper_1 = require("../utils/yamlHelper");
const config_1 = __importDefault(require("../config/config"));
/**
* 将文献对象保存为 Markdown 文件(含 YAML Frontmatter
* @param literature 符合 Literature 接口的文献对象
* @param customFilename 可选的自定义文件名(不含扩展名)
* @param additionalTags 可选附加标签数组
* @returns 保存的文件的绝对路径
* @throws 当文件名非法或路径越界时抛出错误
*/
async function saveLiteratureAsMarkdown(literature, customFilename, additionalTags = []) {
const storageRoot = config_1.default.literatureStoragePath;
await promises_1.default.mkdir(storageRoot, { recursive: true });
let filename;
if (customFilename) {
if (!/^[a-zA-Z0-9_-]+$/.test(customFilename)) {
throw new Error('自定义文件名只能包含字母、数字、连字符和下划线');
}
filename = `${customFilename}.md`;
}
else {
const base = literature.title
.toLowerCase()
.replace(/[^\w\s]/g, '')
.replace(/\s+/g, '-')
.substring(0, 80);
filename = `${base}-${literature.publication_year || 'nodate'}.md`;
}
const fullPath = path_1.default.resolve(storageRoot, filename);
const relative = path_1.default.relative(storageRoot, fullPath);
if (relative.startsWith('..') || path_1.default.isAbsolute(relative)) {
throw new Error('无效的文件名,不允许访问上层目录');
}
const yaml = (0, yamlHelper_1.generateYamlFrontmatter)(literature, additionalTags);
const markdownBody = literature.abstract || '暂无摘要。';
const content = `${yaml}\n\n${markdownBody}`;
await promises_1.default.writeFile(fullPath, content, 'utf-8');
return fullPath;
}
//# sourceMappingURL=storageService.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"storageService.js","sourceRoot":"","sources":["../../src/services/storageService.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;;;AAgBH,4DAmCC;AAjDD,2DAA6B;AAC7B,gDAAwB;AACxB,oDAA8D;AAE9D,8DAAsC;AAEtC;;;;;;;GAOG;AACI,KAAK,UAAU,wBAAwB,CAC5C,UAAsB,EACtB,cAAuB,EACvB,iBAA2B,EAAE;IAE7B,MAAM,WAAW,GAAG,gBAAM,CAAC,qBAAqB,CAAC;IACjD,MAAM,kBAAE,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEjD,IAAI,QAAgB,CAAC;IACrB,IAAI,cAAc,EAAE,CAAC;QACnB,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;YAC7C,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC7C,CAAC;QACD,QAAQ,GAAG,GAAG,cAAc,KAAK,CAAC;IACpC,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK;aAC1B,WAAW,EAAE;aACb,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;aACvB,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;aACpB,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACpB,QAAQ,GAAG,GAAG,IAAI,IAAI,UAAU,CAAC,gBAAgB,IAAI,QAAQ,KAAK,CAAC;IACrE,CAAC;IAED,MAAM,QAAQ,GAAG,cAAI,CAAC,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IACrD,MAAM,QAAQ,GAAG,cAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IACtD,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,cAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,IAAI,GAAG,IAAA,oCAAuB,EAAC,UAAU,EAAE,cAAc,CAAC,CAAC;IACjE,MAAM,YAAY,GAAG,UAAU,CAAC,QAAQ,IAAI,OAAO,CAAC;IACpD,MAAM,OAAO,GAAG,GAAG,IAAI,OAAO,YAAY,EAAE,CAAC;IAC7C,MAAM,kBAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAE/C,OAAO,QAAQ,CAAC;AAClB,CAAC"}

View File

@@ -0,0 +1,93 @@
"use strict";
// src/utils/apiClients.ts
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.fetchArXivPapers = fetchArXivPapers;
const axios_1 = __importDefault(require("axios"));
function createClient(baseURL, rateLimitMs = 1000) {
const client = axios_1.default.create({ baseURL, timeout: 10000 });
let lastRequestTime = 0;
client.interceptors.request.use(async (config) => {
const now = Date.now();
const timeSinceLast = now - lastRequestTime;
if (timeSinceLast < rateLimitMs) {
await new Promise(resolve => setTimeout(resolve, rateLimitMs - timeSinceLast));
}
lastRequestTime = Date.now();
return config;
});
return client;
}
const arxivClient = createClient('http://export.arxiv.org/api/', 1000);
/**
* 构建 arXiv 搜索查询字符串
* - 如果查询包含布尔运算符 (AND, OR, NOT) 或引号,则原样传递(支持高级语法)
* - 否则,将空格分隔的词用 OR 连接,以扩大匹配范围
*/
function buildArxivQuery(rawQuery) {
// 去除首尾空格
const trimmed = rawQuery.trim();
if (!trimmed)
return '';
// 检测是否包含高级语法AND/OR/NOT 或双引号
const hasAdvanced = /\b(AND|OR|NOT)\b|"/.test(trimmed);
if (hasAdvanced) {
return trimmed; // 原样返回,交给 arXiv 解析
}
// 普通查询:将空格拆分为词,用 OR 连接
const words = trimmed.split(/\s+/).filter(w => w.length > 0);
if (words.length === 1) {
return `all:${words[0]}`;
}
return words.map(w => `all:${w}`).join(' OR ');
}
async function fetchArXivPapers(query, limit, yearFrom, yearTo) {
try {
const arxivQuery = buildArxivQuery(query);
if (!arxivQuery)
return [];
// 多取一些结果,因为 OR 可能返回较多论文,确保年份过滤后仍有足够数量
const params = {
search_query: arxivQuery,
start: 0,
max_results: limit * 2,
sortBy: 'lastUpdatedDate',
sortOrder: 'descending',
};
const response = await arxivClient.get('query', { params });
const xml = response.data;
const entries = xml.split('<entry>').slice(1);
const papers = [];
for (const entry of entries) {
const titleMatch = entry.match(/<title>(.*?)<\/title>/);
const authorMatches = [...entry.matchAll(/<author><name>(.*?)<\/name><\/author>/g)];
const summaryMatch = entry.match(/<summary>(.*?)<\/summary>/s);
const idMatch = entry.match(/<id>(.*?)<\/id>/);
const publishedMatch = entry.match(/<published>(.*?)<\/published>/);
const year = publishedMatch ? new Date(publishedMatch[1]).getFullYear() : undefined;
if (yearFrom && year && year < yearFrom)
continue;
if (yearTo && year && year > yearTo)
continue;
papers.push({
title: titleMatch ? titleMatch[1].trim() : '无标题',
authors: authorMatches.map(m => m[1].trim()),
abstract: summaryMatch ? summaryMatch[1].trim() : undefined,
link: idMatch ? idMatch[1].trim() : undefined,
publication_year: year,
citation_count: 0,
source: 'arXiv',
doi: undefined,
});
}
// 返回前 limit 条
return papers.slice(0, limit);
}
catch (error) {
console.error('[apiClients] arXiv 请求失败:', error);
return [];
}
}
//# sourceMappingURL=apiClients.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"apiClients.js","sourceRoot":"","sources":["../../src/utils/apiClients.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,42 @@
"use strict";
/**
* Project Caffeine v0.1.1
* Copyright (c) 2025-2026 Gitconomy Research
*
* SPDX-License-Identifier: MIT
*
* Contributors:
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateYamlFrontmatter = generateYamlFrontmatter;
const yaml_1 = __importDefault(require("yaml"));
/**
* 根据文献对象和附加标签生成 YAML Frontmatter 字符串。
* @param literature 文献对象
* @param additionalTags 附加标签
* @returns 包含 YAML 文档的字符串(以 "---\n" 包围)
*/
function generateYamlFrontmatter(literature, additionalTags = []) {
const frontmatter = {
title: literature.title,
authors: literature.authors,
abstract: literature.abstract,
publication_year: literature.publication_year,
citation_count: literature.citation_count,
source: literature.source,
doi: literature.doi,
date_saved: new Date().toISOString().split('T')[0],
};
const tags = [...(literature.keywords || []), ...additionalTags];
if (tags.length > 0) {
frontmatter.tags = tags;
}
const cleanFrontmatter = Object.fromEntries(Object.entries(frontmatter).filter(([_, v]) => v !== undefined && v !== null));
const yamlStr = yaml_1.default.stringify(cleanFrontmatter, { indent: 2 });
return `---\n${yamlStr}---`;
}
//# sourceMappingURL=yamlHelper.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"yamlHelper.js","sourceRoot":"","sources":["../../src/utils/yamlHelper.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;;;AAWH,0DAuBC;AA/BD,gDAAwB;AAExB;;;;;GAKG;AACH,SAAgB,uBAAuB,CAAC,UAAsB,EAAE,iBAA2B,EAAE;IAC3F,MAAM,WAAW,GAAwB;QACvC,KAAK,EAAE,UAAU,CAAC,KAAK;QACvB,OAAO,EAAE,UAAU,CAAC,OAAO;QAC3B,QAAQ,EAAE,UAAU,CAAC,QAAQ;QAC7B,gBAAgB,EAAE,UAAU,CAAC,gBAAgB;QAC7C,cAAc,EAAE,UAAU,CAAC,cAAc;QACzC,MAAM,EAAE,UAAU,CAAC,MAAM;QACzB,GAAG,EAAE,UAAU,CAAC,GAAG;QACnB,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KACnD,CAAC;IAEF,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,GAAG,cAAc,CAAC,CAAC;IACjE,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,WAAW,CAAC,IAAI,GAAG,IAAI,CAAC;IAC1B,CAAC;IAED,MAAM,gBAAgB,GAAG,MAAM,CAAC,WAAW,CACzC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,IAAI,CAAC,CAC9E,CAAC;IAEF,MAAM,OAAO,GAAG,cAAI,CAAC,SAAS,CAAC,gBAAgB,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IAChE,OAAO,QAAQ,OAAO,KAAK,CAAC;AAC9B,CAAC"}

View File

@@ -0,0 +1,6 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['/node_modules/', '/dist/'] // 忽略 dist 目录!
};

View File

@@ -0,0 +1 @@
../baseline-browser-mapping/dist/cli.cjs

View File

@@ -0,0 +1 @@
../browserslist/cli.js

View File

@@ -0,0 +1 @@
../copyfiles/copyfiles

1
projects/arabica/src/sprint3/node_modules/.bin/copyup generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../copyfiles/copyfiles

View File

@@ -0,0 +1 @@
../create-jest/bin/create-jest.js

1
projects/arabica/src/sprint3/node_modules/.bin/esparse generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../esprima/bin/esparse.js

View File

@@ -0,0 +1 @@
../esprima/bin/esvalidate.js

View File

@@ -0,0 +1 @@
../handlebars/bin/handlebars

View File

@@ -0,0 +1 @@
../import-local/fixtures/cli.js

1
projects/arabica/src/sprint3/node_modules/.bin/jest generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../jest/bin/jest.js

1
projects/arabica/src/sprint3/node_modules/.bin/js-yaml generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../js-yaml/bin/js-yaml.js

1
projects/arabica/src/sprint3/node_modules/.bin/jsesc generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../jsesc/bin/jsesc

1
projects/arabica/src/sprint3/node_modules/.bin/json5 generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../json5/lib/cli.js

1
projects/arabica/src/sprint3/node_modules/.bin/mkdirp generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../mkdirp/bin/cmd.js

View File

@@ -0,0 +1 @@
../which/bin/node-which

1
projects/arabica/src/sprint3/node_modules/.bin/parser generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../@babel/parser/bin/babel-parser.js

1
projects/arabica/src/sprint3/node_modules/.bin/resolve generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../resolve/bin/resolve

1
projects/arabica/src/sprint3/node_modules/.bin/semver generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../semver/bin/semver.js

1
projects/arabica/src/sprint3/node_modules/.bin/ts-jest generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../ts-jest/cli.js

1
projects/arabica/src/sprint3/node_modules/.bin/tsc generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../typescript/bin/tsc

1
projects/arabica/src/sprint3/node_modules/.bin/tsserver generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../typescript/bin/tsserver

1
projects/arabica/src/sprint3/node_modules/.bin/uglifyjs generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../uglify-js/bin/uglifyjs

View File

@@ -0,0 +1 @@
../update-browserslist-db/cli.js

Some files were not shown because too many files have changed in this diff Show More