docs: add n8n integration context

This commit is contained in:
2026-04-20 19:05:05 +08:00
parent ab3f8fecc3
commit 804fd756e7
16 changed files with 2145 additions and 2 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# N8N Context # N8N Context
Generated at: 2026-04-20T08:55:12.297Z Generated at: 2026-04-20T11:04:30.112Z
This file is generated from `docs/integrations/n8n/registry.json` plus repository scanning. This file is generated from `docs/integrations/n8n/registry.json` plus repository scanning.
It exists so external n8n workflows become committed, reviewable context for AI agents and GSD. It exists so external n8n workflows become committed, reviewable context for AI agents and GSD.
+375
View File
@@ -0,0 +1,375 @@
# N8N Context
Generated at: 2026-04-20T11:04:30.112Z
This file is generated from `docs/integrations/n8n/registry.json` plus repository scanning.
It exists so external n8n workflows become committed, reviewable context for AI agents and GSD.
## Workflow Inventory
## AI Search
- Status: `confirmed`
- ID: `ai-search`
- Purpose: Resolve semantic search candidates from n8n and hydrate them into project results.
- n8n workflow id: `F5cQ06DykBfpeyfqL-pd7`
- Export file: `not recorded`
### Entrypoints
- webhook: GET ai-search
### Repository Touchpoints
- src/app/api/search/ai/route.ts
### Environment
- N8N_AI_SEARCH_WEBHOOK
### Contracts
- Request fields: `desc`, `limit`, `page`, `offset`, `tags`, `domains`, `productForms`
- Response fields: `results[].id`, `results[].similarity`, `pagination.total`, `pagination.totalPages`, `pagination.hasMore`
### Schemas
- N8NSearchResponseSchema
### Related Systems
- Upstreams: RAG项目搜索, pgvector similarity search, SiliconFlow embeddings
- Downstreams: src/hooks/useProjects.ts#getProjectsByIds, POST /api/search/ai response
### Ownership
- none
### Notes
- Confirmed against live n8n MCP: webhook path is `ai-search` and the workflow returns `results[].id` plus `similarity`.
## Signals Aggregation
- Status: `confirmed`
- ID: `signals-aggregation`
- Purpose: Aggregate multi-source discussion signals, filter them with AI, and ingest them into the repository signal store.
- n8n workflow id: `bAxNZKGq2ApUUiw9`
- Export file: `not recorded`
### Entrypoints
- schedule: multi-source discussion crawl
### Repository Touchpoints
- src/lib/auth.ts
- src/app/api/webhook/signals/route.ts
- src/app/api/signals/route.ts
- src/lib/validations.ts
- prisma/schema.prisma
### Environment
- WEBHOOK_API_KEY
### Contracts
- Request fields: `apiKey`, `signals[].source`, `signals[].sourceUrl`, `signals[].title`, `signals[].titleEn`, `signals[].summary`, `signals[].summaryEn`, `signals[].topic`, `signals[].topicEn`, `signals[].tags`, `signals[].sections`, `signals[].engagement`, `signals[].hotScore`, `signals[].isHot`, `signals[].publishedAt`, `signals[].isActive`
- Response fields: `success`, `processed`, `created`, `updated`, `failed`, `errors[].index`, `errors[].field`, `errors[].message`
### Schemas
- SignalWebhookPayloadSchema
- SignalIngestionInputSchema
- SignalQuerySchema
### Related Systems
- Upstreams: Hacker News, GitHub, arXiv, Reddit, Product Hunt, Hugging Face
- Downstreams: GET /api/signals, signals page feed, signal hotness computation
### Ownership
- none
### Notes
- Confirmed against live n8n MCP: workflow posts to `/api/webhook/signals` and also triggers external discovery dedupe/task creation. Workflow currently embeds a shared secret in HTTP body and should move to credentials/env.
## Project Tag Reset
- Status: `confirmed`
- ID: `tag-reset`
- Purpose: Reset selected project tags in bulk from n8n classification results.
- n8n workflow id: `8tIgBqLyWrBewJPs`
- Export file: `not recorded`
### Entrypoints
- manual: bulk tag reset
### Repository Touchpoints
- src/lib/auth.ts
- src/app/api/tags/reset-projects/route.ts
- src/lib/validations.ts
- prisma/schema.prisma
### Environment
- WEBHOOK_API_KEY
### Contracts
- Request fields: `apiKey`, `dryRun`, `replaceAllCategories`, `categories`, `projects[].projectSlug`, `projects[].selectedTagSlugsByCategory`
- Response fields: `success`, `result.dryRun`, `result.categories`, `result.updatedCount`, `result.failedCount`, `result.results[].projectSlug`, `result.results[].status`, `result.results[].details`
### Schemas
- ProjectTagResetRequestSchema
### Related Systems
- Upstreams: n8n tag classification
- Downstreams: project tag relations, project detail page revalidation, project list revalidation
### Ownership
- none
### Notes
- Confirmed against live n8n MCP: workflow reads `/api/tags` and `/api/projects`, then posts bulk updates into `/api/tags/reset-projects`. Workflow currently embeds a shared secret in HTTP body and should move to credentials/env.
## Project Ingestion (Multi-source)
- Status: `external-upstream`
- ID: `project-ingestion-multi-source`
- Purpose: Consume queued discovery tasks, enrich project metadata with AI/browser steps, and write final ingestion results back to Agent Park.
- n8n workflow id: `1Ig1CyVMsGJFaHOe`
- Export file: `not recorded`
### Entrypoints
- schedule: every 10 minutes
### Repository Touchpoints
- prisma/schema.prisma
- src/app/api/projects/route.ts
- src/app/api/projects/[slug]/route.ts
### Environment
- none
### Contracts
- Request fields: `task.status`, `task.sourceUrl`, `task.sourceType`
- Response fields: `project content`, `tag assignments`, `task completion status`
### Related Systems
- Upstreams: discovery task queue, browser/AI extraction, tag catalog
- Downstreams: project records visible in repository APIs, task completion callbacks, task failure callbacks
### Ownership
- none
### Notes
- Confirmed against live n8n MCP: workflow polls `/api/discovery/tasks`, marks tasks `IN_PROGRESS`, enriches candidates, then completes or fails tasks. The current repo does not contain `/api/discovery/*` handlers, so this is an upstream system dependency rather than a route implemented here.
## GitHub Star Refresh
- Status: `confirmed`
- ID: `github-star-refresh`
- Purpose: Refresh `projects.githubStars` and `projects.githubStarsUpdatedAt` directly from GitHub repository metadata.
- n8n workflow id: `ewx9Gs6cjrTXvwD0`
- Export file: `not recorded`
### Entrypoints
- schedule: daily at 04:00
### Repository Touchpoints
- prisma/schema.prisma
- src/app/api/search/ai/route.ts
- src/hooks/useProjects.ts
### Environment
- none
### Contracts
- Request fields: `projects.id`, `projects.slug`, `external_links.url(type=GITHUB)`
- Response fields: `projects.githubStars`, `projects.githubStarsUpdatedAt`
### Related Systems
- Upstreams: GitHub repository API, projects table, external_links table
- Downstreams: project ranking, star sorting, home ranking display
### Ownership
- none
### Notes
- Confirmed against live n8n MCP: workflow reads active project GitHub links from Postgres, fetches repository metadata from GitHub, then writes star counts directly back to Postgres. This bypasses repository API routes.
## Project Description Vectorization
- Status: `confirmed`
- ID: `project-description-vectorization`
- Purpose: Generate and persist project embeddings used by semantic search.
- n8n workflow id: `1AvejnM5n-WPApU1vFt9C`
- Export file: `not recorded`
### Entrypoints
- schedule: every 30 minutes
### Repository Touchpoints
- prisma/schema.prisma
- prisma/migrations/20260126000000_add_project_embedding/migration.sql
- src/app/api/search/ai/route.ts
### Environment
- none
### Contracts
- Request fields: `projects.id`, `projects.name`, `projects.nameEn`, `projects.description`, `projects.descriptionEn`, `projects.content`, `projects.contentEn`
- Response fields: `projects.embedding`, `projects.embeddingUpdatedAt`
### Related Systems
- Upstreams: SiliconFlow embeddings API, projects table
- Downstreams: RAG项目搜索, semantic search quality
### Ownership
- none
### Notes
- Confirmed against live n8n MCP: workflow selects active projects with null embeddings, generates `BAAI/bge-m3` vectors, and writes them directly into the `vector` column. This is a direct DB maintenance job, not a repository API route.
## GitHub Trending Discovery
- Status: `external-upstream`
- ID: `github-trending-discovery`
- Purpose: Scrape GitHub Trending, dedupe candidates, filter them with AI, and enqueue project discovery tasks.
- n8n workflow id: `hughGsWismCpk7jd`
- Export file: `not recorded`
### Entrypoints
- schedule: daily at 01:00
### Repository Touchpoints
- src/app/api/projects/route.ts
- src/app/api/projects/[slug]/route.ts
### Environment
- none
### Contracts
- Request fields: `GitHub trending repository URL`, `apiKey`, `tasks[].sourceUrl`, `tasks[].sourceType`
- Response fields: `dedupe shouldCreate`, `task creation result`
### Related Systems
- Upstreams: https://github.com/trending, AI keep/discard filter
- Downstreams: /api/discovery/check-duplicates, /api/discovery/tasks, project ingestion queue
### Ownership
- none
### Notes
- Confirmed against live n8n MCP: workflow scrapes GitHub Trending, filters candidates with an LLM, then posts queued tasks into discovery webhook endpoints. The current repo does not implement `/api/discovery/*`, so treat this as upstream data intake.
## Topic Discovery
- Status: `external-upstream`
- ID: `topic-discovery`
- Purpose: Search GitHub topics and keywords for agent/LLM engineering repos, dedupe them, and enqueue discovery tasks.
- n8n workflow id: `iw9vx9ih5Lt0Mobk`
- Export file: `not recorded`
### Entrypoints
- schedule: daily at 01:00
### Repository Touchpoints
- src/app/api/projects/route.ts
- src/app/api/projects/[slug]/route.ts
### Environment
- none
### Contracts
- Request fields: `GitHub search query`, `apiKey`, `tasks[].sourceUrl`, `tasks[].sourceType`
- Response fields: `dedupe shouldCreate`, `task creation result`, `low recall alert`
### Related Systems
- Upstreams: GitHub Search API, AI keep/discard filter, topic watchlist
- Downstreams: /api/discovery/check-duplicates, /api/discovery/tasks, project ingestion queue
### Ownership
- none
### Notes
- Confirmed against live n8n MCP: workflow searches agent, infra, observability, evaluation, and MCP-related repositories, then posts accepted candidates into discovery webhooks. The current repo does not implement `/api/discovery/*`, so this is upstream context rather than in-repo routing.
## AI Chat Gateway
- Status: `adjacent`
- ID: `ai-chat-gateway`
- Purpose: Expose a chat-oriented webhook wrapper around `RAG项目搜索` and package search hits into chat blocks/citations.
- n8n workflow id: `Rncc22jmHEaYOG58`
- Export file: `not recorded`
### Entrypoints
- webhook: POST agent-park-chat
### Repository Touchpoints
- none
### Environment
- none
### Contracts
- Request fields: `requestId`, `sessionId`, `clientId`, `locale`, `mode`, `message`
- Response fields: `message.blocks`, `message.citations`, `message.meta.source`, `progress.stage`
### Related Systems
- Upstreams: RAG项目搜索, n8n webhook `ai-search`
- Downstreams: external chat clients, project detail URLs
### Ownership
- none
### Notes
- Confirmed against live n8n MCP: this workflow is related to Agent Park but does not call a repository route directly. It wraps the RAG webhook and formats citations pointing at project pages.
## Exported Workflow Files
- none
## Detected Repository Touchpoints
- `src/app/api/search/ai/route.ts` (39 matches)
- L6: `const N8N_WEBHOOK_URL = process.env.N8N_AI_SEARCH_WEBHOOK!`
- L8: `if (!N8N_WEBHOOK_URL) {`
- L9: `throw new Error('N8N_AI_SEARCH_WEBHOOK environment variable is not set')`
- L12: `// n8n 返回的搜索结果 Schema(统一格式)`
- L13: `const N8NSearchResponseSchema = z.object({`
- `src/app/api/tags/maintenance/route.test.ts` (1 matches)
- L65: `process.env.WEBHOOK_API_KEY = validApiKey;`
- `src/app/api/tags/reset-projects/route.test.ts` (1 matches)
- L78: `process.env.WEBHOOK_API_KEY = validApiKey;`
- `src/app/api/webhook/signals/route.ts` (7 matches)
- L13: `SignalWebhookPayloadSchema,`
- L15: `type SignalWebhookPayload,`
- L39: `const validationResult = SignalWebhookPayloadSchema.safeParse(body)`
- L51: `const payload = validationResult.data as SignalWebhookPayload`
- L184: `console.error(\`[Webhook Signals] Error at index ${i}:\`, error)`
- `src/hooks/useProjects.ts` (2 matches)
- L563: `// n8n 返回的简化搜索结果类型`
- L564: `export type N8NSearchResult = {`
- `src/lib/auth.ts` (1 matches)
- L9: `expectedApiKey: string | undefined = process.env.WEBHOOK_API_KEY`
- `src/lib/validations.ts` (5 matches)
- L60: `export const WebhookAuthSchema = z.object({`
- L106: `export const SignalWebhookPayloadSchema = WebhookAuthSchema.extend({`
- L147: `export type SignalWebhookPayload = z.infer<typeof SignalWebhookPayloadSchema>;`
- `src/messages/en.json` (2 matches)
- L220: `"contractTitle": "n8n Field Contract",`
- L221: `"contractDescription": "Every visual element maps to fields that can be produced from n8n workflow outputs.",`
- `src/messages/zh.json` (2 matches)
- L220: `"contractTitle": "n8n 字段契约",`
- L221: `"contractDescription": "页面元素都对应可由 n8n 输出的字段,避免出现无法供数的设计组件。",`
## Environment Variables
- N8N_AI_SEARCH_WEBHOOK
- WEBHOOK_API_KEY
## Gaps To Fill
- All detected repo touchpoints are mapped to documented workflows.
## Maintenance Rules
- When an n8n workflow changes, update `docs/integrations/n8n/registry.json` in the same PR.
- If possible, export the workflow JSON into `docs/integrations/n8n/exports/` and reference it from the registry.
- Re-run `pnpm n8n:context` after every workflow, contract, or route change.
- Treat this file as generated output; edit the registry instead of editing this file directly.
+321
View File
@@ -0,0 +1,321 @@
# AgentPark n8n Dataflow
这份文档回答两个问题:
1. 这 8 条生产流程分别负责什么。
2. 数据怎样从外部源头进入 AgentPark,再进入数据库、API 和页面展示。
## 范围
当前纳入范围的 8 条生产流程:
1. `Topic项目计划新增`
2. `每日Github Trending项目计划新增`
3. `项目分析入库(多源)`
4. `GitHub Star 每日刷新`
5. `项目描述向量化`
6. `RAG项目搜索`
7. `前沿信号聚合(多源+AI Agent过滤)`
8. `项目标签重置`
不在这 8 条内,但已登记在 [registry.json](D:/Code/AI/agent-park/docs/integrations/n8n/registry.json) 的旁路流程:
- `AI对话网关(Agent Park`
## 系统边界
当前体系不是“单仓库闭环”,而是三层:
- 外部数据源层:GitHub Search API、GitHub Trending、Hacker News、Reddit、arXiv、Product Hunt、Hugging Face
- n8n 编排层:抓取、去重、AI 过滤、标签重置、向量化、信号结构化
- AgentPark 应用层:Postgres/Prisma、Next.js API、页面组件
还有一个明确存在但当前仓库里没有实现代码的外部服务边界:
- `Discovery Task Service`
- `GET/POST/PATCH /api/discovery/tasks`
- `POST /api/discovery/check-duplicates`
也就是说,项目发现与入库链路的“队列与去重接口”不在当前 Next.js 仓库中实现,但其输出最终进入当前仓库使用的数据库与项目展示链路。
## 核心实体
- `projects`
- 项目主体记录,供 `/api/projects`、详情页、搜索页、首页排行使用
- `external_links`
- 项目外链,尤其是 GitHub 链接,供 Star 刷新流程使用
- `tags`
- 标签池,供项目筛选、标签重置、入库分类使用
- `project_tags`
- 项目与标签的关系表
- `signals`
- 前沿讨论信号,供 `/api/signals` 和 Signals 页面使用
- `projects.embedding`
- 项目向量,供语义检索工作流使用
## 总流图
```mermaid
flowchart TD
subgraph Sources["External Sources"]
GHSearch["GitHub Search API"]
GHTrend["GitHub Trending"]
GHRepo["GitHub Repository API"]
HN["Hacker News"]
Reddit["Reddit"]
Arxiv["arXiv"]
PH["Product Hunt"]
HF["Hugging Face"]
Silicon["SiliconFlow Embeddings"]
end
subgraph N8N["n8n Workflows"]
W1["1 Topic项目计划新增"]
W2["2 每日Github Trending项目计划新增"]
W3["3 项目分析入库(多源)"]
W4["4 GitHub Star 每日刷新"]
W5["5 项目描述向量化"]
W6["6 RAG项目搜索"]
W7["7 前沿信号聚合"]
W8["8 项目标签重置"]
end
subgraph Discovery["External Discovery Service"]
Dedupe["/api/discovery/check-duplicates"]
Tasks["/api/discovery/tasks"]
Complete["/api/discovery/tasks/:id/complete"]
end
subgraph App["AgentPark App + DB"]
DBProjects["projects"]
DBLinks["external_links"]
DBTags["tags / project_tags"]
DBSignals["signals"]
APIProjects["/api/projects"]
APISearch["/api/search/ai"]
APISignals["/api/signals"]
APITagReset["/api/tags/reset-projects"]
UIProjects["Projects pages"]
UISignals["Signals page"]
UIHome["Home rankings"]
end
GHSearch --> W1
GHTrend --> W2
W1 --> Dedupe
W2 --> Dedupe
Dedupe --> Tasks
Tasks --> W3
W3 --> Complete
Complete --> DBProjects
Complete --> DBLinks
Complete --> DBTags
DBProjects --> W5
Silicon --> W5
W5 --> DBProjects
DBProjects --> W6
Silicon --> W6
W6 --> APISearch
APISearch --> UIProjects
DBProjects --> W4
DBLinks --> W4
GHRepo --> W4
W4 --> DBProjects
DBProjects --> UIHome
DBProjects --> UIProjects
HN --> W7
Reddit --> W7
Arxiv --> W7
PH --> W7
HF --> W7
W7 --> DBSignals
W7 --> Dedupe
DBSignals --> APISignals
APISignals --> UISignals
DBTags --> W8
DBProjects --> W8
W8 --> APITagReset
APITagReset --> DBTags
APITagReset --> UIProjects
```
## 主链路拆解
### 1. 项目发现链路
入口流程:
- `Topic项目计划新增`
- `每日Github Trending项目计划新增`
职责:
- 从 GitHub 搜索结果和 Trending 列表中找候选项目
- 先走 discovery 去重
- 再用 LLM 做保留/丢弃判断
- 最后把可入库项目写进 discovery 任务队列
注意:
- 这两条流程不会直接写 `projects`
- 它们只负责“造任务”
### 2. 项目入库链路
核心流程:
- `项目分析入库(多源)`
职责:
- 轮询 discovery 任务队列
- 把任务置为 `IN_PROGRESS`
- 用浏览器/AI 工具从入口 URL 收集事实
- 生成标准化项目内容、外链、标签候选
- 调用 completion 接口完成入库
- 失败时把任务置为 `FAILED`
当前仓库边界:
- 当前仓库没有 `/api/discovery/*` 的实现
- 但入库后的结果最终会出现在:
- [route.ts](D:/Code/AI/agent-park/src/app/api/projects/route.ts)
- [route.ts](D:/Code/AI/agent-park/src/app/api/projects/[slug]/route.ts)
- [useProjects.ts](D:/Code/AI/agent-park/src/hooks/useProjects.ts)
### 3. 项目检索链路
核心流程:
- `项目描述向量化`
- `RAG项目搜索`
职责分工:
- `项目描述向量化`
- 扫描 `embedding IS NULL` 的活跃项目
-`BAAI/bge-m3` 生成向量
- 写回 `projects.embedding``embeddingUpdatedAt`
- `RAG项目搜索`
- 接收 `ai-search` webhook
- 对用户输入生成向量
- 直接在 Postgres 中做向量相似度搜索
- 返回 `results[].id``similarity`
仓库接点:
- [route.ts](D:/Code/AI/agent-park/src/app/api/search/ai/route.ts)
- 转发到 n8n webhook
- 再根据 ID 批量回库拿完整项目
- [useProjects.ts](D:/Code/AI/agent-park/src/hooks/useProjects.ts)
- `getProjectsByIds()` 负责补全项目详情
最终展示:
- 项目搜索页
- 项目列表筛选结果
### 4. Signals 展示链路
核心流程:
- `前沿信号聚合(多源+AI Agent过滤)`
职责:
- 从 6 类外部源抓取讨论或发布内容
- 先做规则过滤和去重
- 再用 LLM 判断是否属于 AI Agent 相关信号
- 输出中英双语结构化字段
- 计算热度字段
- 通过 webhook 写入 `signals`
仓库接点:
- [route.ts](D:/Code/AI/agent-park/src/app/api/webhook/signals/route.ts)
- [route.ts](D:/Code/AI/agent-park/src/app/api/signals/route.ts)
- [validations.ts](D:/Code/AI/agent-park/src/lib/validations.ts)
最终展示:
- Signals 页数据流
- 讨论聚合展示卡片
副作用:
- 该流程还会从已保留信号中提取 GitHub 仓库链接,回流到 discovery 任务系统
### 5. 标签治理链路
核心流程:
- `项目标签重置`
职责:
- 拉取标签池与项目列表
- 逐项目调用 LLM 做 5 类标签归类
- 调用仓库内的标签重置接口
仓库接点:
- [route.ts](D:/Code/AI/agent-park/src/app/api/tags/reset-projects/route.ts)
- [auth.ts](D:/Code/AI/agent-park/src/lib/auth.ts)
- [validations.ts](D:/Code/AI/agent-park/src/lib/validations.ts)
最终影响:
- 项目筛选
- 详情页标签
- 入库后标签整洁度
### 6. Star 刷新链路
核心流程:
- `GitHub Star 每日刷新`
职责:
-`projects` + `external_links` 找出 GitHub 仓库
- 调 GitHub API 拉仓库详情
- 更新 `githubStars``githubStarsUpdatedAt`
最终影响:
- 首页排行
- 项目列表星标排序
- AI 搜索结果里的 `stars_desc` / `stars_asc`
## 当前“就绪状态”定义
现在仓库已经具备:
- 8 条生产流程的仓库内登记
- 代码触点与 workflow 的映射关系
- 从“源头 -> n8n -> DB/API -> 页面”的主链路图
- `.planning/codebase` 中可供 GSD 读取的 n8n 总览
但仍有 2 个外部依赖不在当前仓库闭环:
- discovery task service
- n8n credentials / secrets / runtime env
因此,“代码库就绪”应理解为:
- AI 和 GSD 已经能正确理解全局结构与边界
- 但不能假设当前仓库单独包含所有后端实现
## 建议维护规则
- 任何一条生产 workflow 变更时,优先更新:
- [registry.json](D:/Code/AI/agent-park/docs/integrations/n8n/registry.json)
- 对应 `workflows/*.md`
- 更新后执行:
- `pnpm n8n:context`
- 如果 discovery 服务代码后续被并入仓库,优先补齐 `/api/discovery/*` 的实现文档与路由映射
+58
View File
@@ -0,0 +1,58 @@
# n8n Context
This directory is the repository-side source of truth for n8n workflows that feed or depend on this app.
Why this exists:
- n8n workflows live outside the application repository, so AI agents only see partial context from code scanning.
- The fix is to commit workflow metadata, contracts, and touchpoint mapping into the repo.
- Generated context is mirrored into `.planning/codebase/N8N-CONTEXT.md` so GSD can read it without guessing.
Key files:
- [registry.json](D:/Code/AI/agent-park/docs/integrations/n8n/registry.json): editable workflow registry and contract source of truth
- [CONTEXT.generated.md](D:/Code/AI/agent-park/docs/integrations/n8n/CONTEXT.generated.md): generated inventory and repo touchpoint report
- [DATAFLOW.md](D:/Code/AI/agent-park/docs/integrations/n8n/DATAFLOW.md): end-to-end source to UI dataflow
- [workflows/README.md](D:/Code/AI/agent-park/docs/integrations/n8n/workflows/README.md): per-workflow documentation index
- [N8N-CONTEXT.md](D:/Code/AI/agent-park/.planning/codebase/N8N-CONTEXT.md): GSD-facing generated mirror
- [N8N-DATAFLOW.md](D:/Code/AI/agent-park/.planning/codebase/N8N-DATAFLOW.md): GSD-facing dataflow overview
Expected workflow:
1. Add or update an entry in `registry.json` for every n8n workflow that touches this repository.
2. If possible, export the workflow JSON from n8n into `docs/integrations/n8n/exports/`.
3. Run `pnpm n8n:context`.
4. Commit the registry change together with the generated context file.
5. If runtime behavior changed, also update `DATAFLOW.md` and the affected `workflows/*.md`.
Rules:
- `registry.json` is the editable source of truth.
- `CONTEXT.generated.md` is generated output.
- `DATAFLOW.md` is the cross-workflow end-to-end view.
- `workflows/*.md` are the single-workflow execution notes.
- Keep repository file paths repo-relative, for example `src/app/api/search/ai/route.ts`.
- Record request and response fields at the contract level, not only business descriptions.
- If a repo touchpoint is not linked to any workflow, the generated file will report it as a gap.
Minimum fields for each workflow entry:
- `id`
- `status`
- `name`
- `purpose`
- `n8n.entrypoints`
- `repository.consumers`
- `repository.env`
- `contracts.requestFields`
- `contracts.responseFields`
Recommended:
- `n8n.workflowId`
- `n8n.exportFile`
- `repository.schemas`
- `upstreams`
- `downstreams`
- `owners`
- `notes`
+498
View File
@@ -0,0 +1,498 @@
{
"meta": {
"lastReviewed": "2026-04-20",
"instructions": [
"Add one workflow entry for every n8n workflow that feeds or is triggered by this repository.",
"Keep repository.consumers paths repo-relative.",
"Optional: export workflow JSON into docs/integrations/n8n/exports/ and reference it from n8n.exportFile."
]
},
"workflows": [
{
"id": "ai-search",
"status": "confirmed",
"name": "AI Search",
"purpose": "Resolve semantic search candidates from n8n and hydrate them into project results.",
"n8n": {
"workflowId": "F5cQ06DykBfpeyfqL-pd7",
"exportFile": "",
"entrypoints": [
{
"kind": "webhook",
"method": "GET",
"path": "ai-search"
}
]
},
"repository": {
"consumers": [
"src/app/api/search/ai/route.ts"
],
"env": [
"N8N_AI_SEARCH_WEBHOOK"
],
"schemas": [
"N8NSearchResponseSchema"
]
},
"contracts": {
"requestFields": [
"desc",
"limit",
"page",
"offset",
"tags",
"domains",
"productForms"
],
"responseFields": [
"results[].id",
"results[].similarity",
"pagination.total",
"pagination.totalPages",
"pagination.hasMore"
]
},
"upstreams": [
"RAG项目搜索",
"pgvector similarity search",
"SiliconFlow embeddings"
],
"downstreams": [
"src/hooks/useProjects.ts#getProjectsByIds",
"POST /api/search/ai response"
],
"owners": [],
"notes": "Confirmed against live n8n MCP: webhook path is `ai-search` and the workflow returns `results[].id` plus `similarity`."
},
{
"id": "signals-aggregation",
"status": "confirmed",
"name": "Signals Aggregation",
"purpose": "Aggregate multi-source discussion signals, filter them with AI, and ingest them into the repository signal store.",
"n8n": {
"workflowId": "bAxNZKGq2ApUUiw9",
"exportFile": "",
"entrypoints": [
{
"kind": "schedule",
"path": "multi-source discussion crawl"
}
]
},
"repository": {
"consumers": [
"src/lib/auth.ts",
"src/app/api/webhook/signals/route.ts",
"src/app/api/signals/route.ts",
"src/lib/validations.ts",
"prisma/schema.prisma"
],
"env": [
"WEBHOOK_API_KEY"
],
"schemas": [
"SignalWebhookPayloadSchema",
"SignalIngestionInputSchema",
"SignalQuerySchema"
]
},
"contracts": {
"requestFields": [
"apiKey",
"signals[].source",
"signals[].sourceUrl",
"signals[].title",
"signals[].titleEn",
"signals[].summary",
"signals[].summaryEn",
"signals[].topic",
"signals[].topicEn",
"signals[].tags",
"signals[].sections",
"signals[].engagement",
"signals[].hotScore",
"signals[].isHot",
"signals[].publishedAt",
"signals[].isActive"
],
"responseFields": [
"success",
"processed",
"created",
"updated",
"failed",
"errors[].index",
"errors[].field",
"errors[].message"
]
},
"upstreams": [
"Hacker News",
"GitHub",
"arXiv",
"Reddit",
"Product Hunt",
"Hugging Face"
],
"downstreams": [
"GET /api/signals",
"signals page feed",
"signal hotness computation"
],
"owners": [],
"notes": "Confirmed against live n8n MCP: workflow posts to `/api/webhook/signals` and also triggers external discovery dedupe/task creation. Workflow currently embeds a shared secret in HTTP body and should move to credentials/env."
},
{
"id": "tag-reset",
"status": "confirmed",
"name": "Project Tag Reset",
"purpose": "Reset selected project tags in bulk from n8n classification results.",
"n8n": {
"workflowId": "8tIgBqLyWrBewJPs",
"exportFile": "",
"entrypoints": [
{
"kind": "manual",
"path": "bulk tag reset"
}
]
},
"repository": {
"consumers": [
"src/lib/auth.ts",
"src/app/api/tags/reset-projects/route.ts",
"src/lib/validations.ts",
"prisma/schema.prisma"
],
"env": [
"WEBHOOK_API_KEY"
],
"schemas": [
"ProjectTagResetRequestSchema"
]
},
"contracts": {
"requestFields": [
"apiKey",
"dryRun",
"replaceAllCategories",
"categories",
"projects[].projectSlug",
"projects[].selectedTagSlugsByCategory"
],
"responseFields": [
"success",
"result.dryRun",
"result.categories",
"result.updatedCount",
"result.failedCount",
"result.results[].projectSlug",
"result.results[].status",
"result.results[].details"
]
},
"upstreams": [
"n8n tag classification"
],
"downstreams": [
"project tag relations",
"project detail page revalidation",
"project list revalidation"
],
"owners": [],
"notes": "Confirmed against live n8n MCP: workflow reads `/api/tags` and `/api/projects`, then posts bulk updates into `/api/tags/reset-projects`. Workflow currently embeds a shared secret in HTTP body and should move to credentials/env."
},
{
"id": "project-ingestion-multi-source",
"status": "external-upstream",
"name": "Project Ingestion (Multi-source)",
"purpose": "Consume queued discovery tasks, enrich project metadata with AI/browser steps, and write final ingestion results back to Agent Park.",
"n8n": {
"workflowId": "1Ig1CyVMsGJFaHOe",
"exportFile": "",
"entrypoints": [
{
"kind": "schedule",
"path": "every 10 minutes"
}
]
},
"repository": {
"consumers": [
"prisma/schema.prisma",
"src/app/api/projects/route.ts",
"src/app/api/projects/[slug]/route.ts"
],
"env": [],
"schemas": []
},
"contracts": {
"requestFields": [
"task.status",
"task.sourceUrl",
"task.sourceType"
],
"responseFields": [
"project content",
"tag assignments",
"task completion status"
]
},
"upstreams": [
"discovery task queue",
"browser/AI extraction",
"tag catalog"
],
"downstreams": [
"project records visible in repository APIs",
"task completion callbacks",
"task failure callbacks"
],
"owners": [],
"notes": "Confirmed against live n8n MCP: workflow polls `/api/discovery/tasks`, marks tasks `IN_PROGRESS`, enriches candidates, then completes or fails tasks. The current repo does not contain `/api/discovery/*` handlers, so this is an upstream system dependency rather than a route implemented here."
},
{
"id": "github-star-refresh",
"status": "confirmed",
"name": "GitHub Star Refresh",
"purpose": "Refresh `projects.githubStars` and `projects.githubStarsUpdatedAt` directly from GitHub repository metadata.",
"n8n": {
"workflowId": "ewx9Gs6cjrTXvwD0",
"exportFile": "",
"entrypoints": [
{
"kind": "schedule",
"path": "daily at 04:00"
}
]
},
"repository": {
"consumers": [
"prisma/schema.prisma",
"src/app/api/search/ai/route.ts",
"src/hooks/useProjects.ts"
],
"env": [],
"schemas": []
},
"contracts": {
"requestFields": [
"projects.id",
"projects.slug",
"external_links.url(type=GITHUB)"
],
"responseFields": [
"projects.githubStars",
"projects.githubStarsUpdatedAt"
]
},
"upstreams": [
"GitHub repository API",
"projects table",
"external_links table"
],
"downstreams": [
"project ranking",
"star sorting",
"home ranking display"
],
"owners": [],
"notes": "Confirmed against live n8n MCP: workflow reads active project GitHub links from Postgres, fetches repository metadata from GitHub, then writes star counts directly back to Postgres. This bypasses repository API routes."
},
{
"id": "project-description-vectorization",
"status": "confirmed",
"name": "Project Description Vectorization",
"purpose": "Generate and persist project embeddings used by semantic search.",
"n8n": {
"workflowId": "1AvejnM5n-WPApU1vFt9C",
"exportFile": "",
"entrypoints": [
{
"kind": "schedule",
"path": "every 30 minutes"
}
]
},
"repository": {
"consumers": [
"prisma/schema.prisma",
"prisma/migrations/20260126000000_add_project_embedding/migration.sql",
"src/app/api/search/ai/route.ts"
],
"env": [],
"schemas": []
},
"contracts": {
"requestFields": [
"projects.id",
"projects.name",
"projects.nameEn",
"projects.description",
"projects.descriptionEn",
"projects.content",
"projects.contentEn"
],
"responseFields": [
"projects.embedding",
"projects.embeddingUpdatedAt"
]
},
"upstreams": [
"SiliconFlow embeddings API",
"projects table"
],
"downstreams": [
"RAG项目搜索",
"semantic search quality"
],
"owners": [],
"notes": "Confirmed against live n8n MCP: workflow selects active projects with null embeddings, generates `BAAI/bge-m3` vectors, and writes them directly into the `vector` column. This is a direct DB maintenance job, not a repository API route."
},
{
"id": "github-trending-discovery",
"status": "external-upstream",
"name": "GitHub Trending Discovery",
"purpose": "Scrape GitHub Trending, dedupe candidates, filter them with AI, and enqueue project discovery tasks.",
"n8n": {
"workflowId": "hughGsWismCpk7jd",
"exportFile": "",
"entrypoints": [
{
"kind": "schedule",
"path": "daily at 01:00"
}
]
},
"repository": {
"consumers": [
"src/app/api/projects/route.ts",
"src/app/api/projects/[slug]/route.ts"
],
"env": [],
"schemas": []
},
"contracts": {
"requestFields": [
"GitHub trending repository URL",
"apiKey",
"tasks[].sourceUrl",
"tasks[].sourceType"
],
"responseFields": [
"dedupe shouldCreate",
"task creation result"
]
},
"upstreams": [
"https://github.com/trending",
"AI keep/discard filter"
],
"downstreams": [
"/api/discovery/check-duplicates",
"/api/discovery/tasks",
"project ingestion queue"
],
"owners": [],
"notes": "Confirmed against live n8n MCP: workflow scrapes GitHub Trending, filters candidates with an LLM, then posts queued tasks into discovery webhook endpoints. The current repo does not implement `/api/discovery/*`, so treat this as upstream data intake."
},
{
"id": "topic-discovery",
"status": "external-upstream",
"name": "Topic Discovery",
"purpose": "Search GitHub topics and keywords for agent/LLM engineering repos, dedupe them, and enqueue discovery tasks.",
"n8n": {
"workflowId": "iw9vx9ih5Lt0Mobk",
"exportFile": "",
"entrypoints": [
{
"kind": "schedule",
"path": "daily at 01:00"
}
]
},
"repository": {
"consumers": [
"src/app/api/projects/route.ts",
"src/app/api/projects/[slug]/route.ts"
],
"env": [],
"schemas": []
},
"contracts": {
"requestFields": [
"GitHub search query",
"apiKey",
"tasks[].sourceUrl",
"tasks[].sourceType"
],
"responseFields": [
"dedupe shouldCreate",
"task creation result",
"low recall alert"
]
},
"upstreams": [
"GitHub Search API",
"AI keep/discard filter",
"topic watchlist"
],
"downstreams": [
"/api/discovery/check-duplicates",
"/api/discovery/tasks",
"project ingestion queue"
],
"owners": [],
"notes": "Confirmed against live n8n MCP: workflow searches agent, infra, observability, evaluation, and MCP-related repositories, then posts accepted candidates into discovery webhooks. The current repo does not implement `/api/discovery/*`, so this is upstream context rather than in-repo routing."
},
{
"id": "ai-chat-gateway",
"status": "adjacent",
"name": "AI Chat Gateway",
"purpose": "Expose a chat-oriented webhook wrapper around `RAG项目搜索` and package search hits into chat blocks/citations.",
"n8n": {
"workflowId": "Rncc22jmHEaYOG58",
"exportFile": "",
"entrypoints": [
{
"kind": "webhook",
"method": "POST",
"path": "agent-park-chat"
}
]
},
"repository": {
"consumers": [],
"env": [],
"schemas": []
},
"contracts": {
"requestFields": [
"requestId",
"sessionId",
"clientId",
"locale",
"mode",
"message"
],
"responseFields": [
"message.blocks",
"message.citations",
"message.meta.source",
"progress.stage"
]
},
"upstreams": [
"RAG项目搜索",
"n8n webhook `ai-search`"
],
"downstreams": [
"external chat clients",
"project detail URLs"
],
"owners": [],
"notes": "Confirmed against live n8n MCP: this workflow is related to Agent Park but does not call a repository route directly. It wraps the RAG webhook and formats citations pointing at project pages."
}
]
}
@@ -0,0 +1,63 @@
# Topic项目计划新增
- Registry ID: `topic-discovery`
- n8n Workflow ID: `iw9vx9ih5Lt0Mobk`
- Status: `external-upstream`
- 角色: 从 GitHub Search API 按 topic 和关键词发现候选仓库,经过去重与 AI 筛选后,写入 discovery 任务队列。
## 触发方式
- 定时触发
- 当前已核查行为:每日约 `01:00` 运行
## 外部输入
- GitHub Search API
- 预设 topic / keyword watchlist
- AI 保留或丢弃判断
## 主流程
1. 组合 topic 与关键词查询 GitHub 仓库。
2. 提取候选仓库 URL、基础描述和来源信息。
3. 调用 discovery 去重接口判断是否应该继续创建任务。
4. 使用 LLM 对候选项目做保留或丢弃判断。
5. 把保留结果写入 discovery 任务队列。
6. 当召回偏低时发出低召回告警。
## 输出结果
- `tasks[].sourceUrl`
- `tasks[].sourceType`
- `dedupe shouldCreate`
- `task creation result`
- `low recall alert`
## 与仓库的关系
这条流程不直接写当前仓库的 `projects` 表。它的作用是“发现项目并造任务”,后续由 `项目分析入库(多源)` 消费任务并完成入库。
当前仓库内最终会消费它产出的结果:
- [route.ts](D:/Code/AI/agent-park/src/app/api/projects/route.ts)
- [route.ts](D:/Code/AI/agent-park/src/app/api/projects/[slug]/route.ts)
## 系统边界
当前仓库没有实现以下接口,这部分应视为外部上游系统:
- `POST /api/discovery/check-duplicates`
- `POST /api/discovery/tasks`
因此,这条流程在仓库侧属于 `external-upstream`,不是应用内路由。
## 已确认要点
- 已通过 live n8n MCP 核查 workflow 元数据与职责。
- 该流程围绕 agent / infra / observability / evaluation / MCP 等方向搜索仓库。
- 任务入队后,真实入库并不在当前流程内完成。
## 维护要求
- GitHub 搜索 query、topic watchlist、保留规则变更时,同步更新 [registry.json](D:/Code/AI/agent-park/docs/integrations/n8n/registry.json)。
- 若未来 discovery 服务代码并入当前仓库,应把这里的外部边界改成具体路由映射。
@@ -0,0 +1,62 @@
# 每日Github Trending项目计划新增
- Registry ID: `github-trending-discovery`
- n8n Workflow ID: `hughGsWismCpk7jd`
- Status: `external-upstream`
- 角色: 抓取 GitHub Trending,筛出值得跟踪的新项目,并写入 discovery 任务队列。
## 触发方式
- 定时触发
- 当前已核查行为:每日约 `01:00` 运行
## 外部输入
- [GitHub Trending](https://github.com/trending)
- 页面抓取结果
- AI 保留或丢弃判断
## 主流程
1. 抓取 GitHub Trending 页面,提取仓库 URL 和基础描述。
2. 规范化候选项目数据。
3. 调用 discovery 去重接口,判断当前候选是否已经存在。
4. 使用 LLM 对候选项目做保留或丢弃判断。
5. 把保留候选写入 discovery 任务队列。
## 输出结果
- `GitHub trending repository URL`
- `tasks[].sourceUrl`
- `tasks[].sourceType`
- `dedupe shouldCreate`
- `task creation result`
## 与仓库的关系
这条流程不直接写当前仓库数据库。它只生成“待分析任务”,真正的项目详情写入由 `项目分析入库(多源)` 完成。
最终影响到当前仓库的展示结果:
- [route.ts](D:/Code/AI/agent-park/src/app/api/projects/route.ts)
- [route.ts](D:/Code/AI/agent-park/src/app/api/projects/[slug]/route.ts)
## 系统边界
当前仓库没有实现以下接口:
- `POST /api/discovery/check-duplicates`
- `POST /api/discovery/tasks`
因此它是当前仓库的外部上游,而不是仓库内闭环的一部分。
## 已确认要点
- 已通过 live n8n MCP 核查 workflow 元数据与职责。
- 该流程从 Trending 抓取候选,再由 AI 过滤,避免把纯噪声仓库直接入库。
- 当前仓库只能看到最终被入库后的项目,不能独立重放这条发现链路。
## 维护要求
- Trending 抓取逻辑、筛选规则、入队字段变更时,同步更新 [registry.json](D:/Code/AI/agent-park/docs/integrations/n8n/registry.json)。
- 若后续保留了 workflow 导出文件,应在 registry 中补上 `exportFile`
@@ -0,0 +1,67 @@
# 项目分析入库(多源)
- Registry ID: `project-ingestion-multi-source`
- n8n Workflow ID: `1Ig1CyVMsGJFaHOe`
- Status: `external-upstream`
- 角色: 消费 discovery 任务队列,补齐项目事实、外链和标签,再把结果写回 AgentPark 的项目主数据。
## 触发方式
- 定时触发
- 当前已核查行为:约每 `10` 分钟轮询一次
## 外部输入
- discovery task queue
- 浏览器抓取与页面解析
- AI 提取和结构化能力
- 标签池 / 分类规则
## 主流程
1. 轮询 discovery 任务队列,拉取待处理项目。
2. 把任务置为 `IN_PROGRESS`
3. 基于 `sourceUrl``sourceType` 打开外部页面,采集项目事实。
4. 生成标准化项目资料,如标题、描述、正文、分类、外链、标签候选。
5. 写回任务完成接口,完成项目入库。
6. 出错时写回失败状态。
## 输出结果
- `project content`
- `tag assignments`
- `task completion status`
## 与仓库的关系
这是“项目从发现到落库”的核心桥梁,但它不通过当前仓库中的显式 `/api/discovery/*` 路由实现。当前仓库能确认的消费面主要是项目表结构和展示接口:
- [schema.prisma](D:/Code/AI/agent-park/prisma/schema.prisma)
- [route.ts](D:/Code/AI/agent-park/src/app/api/projects/route.ts)
- [route.ts](D:/Code/AI/agent-park/src/app/api/projects/[slug]/route.ts)
## 系统边界
当前仓库没有以下接口实现:
- `GET /api/discovery/tasks`
- `PATCH /api/discovery/tasks/:id`
- `POST /api/discovery/tasks/:id/complete`
- 失败回写相关接口
这说明项目入库队列服务是外部系统。当前仓库只能消费最终入库结果,而不能独立运行整条入库工作流。
## 已确认要点
- 已通过 live n8n MCP 核查 workflow 元数据与职责。
- 该流程会先标记 `IN_PROGRESS`,再进行浏览器与 AI 分析。
- 当前仓库看到的是结果表和查询接口,不是入库执行器本身。
## 对 AI/GSD 的意义
后续如果要梳理“项目数据源头 -> 展示”,必须把这条流程当作核心中间层,而不是假设项目直接从 GitHub 写入 `projects`
## 维护要求
- 任务字段、回写结构、标签落库策略变化时,同步更新 [registry.json](D:/Code/AI/agent-park/docs/integrations/n8n/registry.json)。
- 若 discovery 服务未来并入仓库,应第一时间把这里改成具体路由与 schema 映射。
@@ -0,0 +1,54 @@
# GitHub Star 每日刷新
- Registry ID: `github-star-refresh`
- n8n Workflow ID: `ewx9Gs6cjrTXvwD0`
- Status: `confirmed`
- 角色: 每日刷新项目的 GitHub Star 数,保证排序、排行和展示的时效性。
## 触发方式
- 定时触发
- 当前已核查行为:每日约 `04:00` 运行
## 数据来源
- `projects`
- `external_links`
- GitHub Repository API
## 主流程
1. 从数据库读取活跃项目及其 GitHub 外链。
2. 解析 GitHub 仓库 owner/repo。
3. 调用 GitHub Repository API 获取最新 star 数。
4. 直接写回 `projects.githubStars``projects.githubStarsUpdatedAt`
## 直接写入字段
- `projects.githubStars`
- `projects.githubStarsUpdatedAt`
## 与仓库的关系
这条流程不是调用仓库 API,而是直接维护数据库中的展示字段。当前仓库内受其影响的消费面包括:
- [schema.prisma](D:/Code/AI/agent-park/prisma/schema.prisma)
- [route.ts](D:/Code/AI/agent-park/src/app/api/search/ai/route.ts)
- [useProjects.ts](D:/Code/AI/agent-park/src/hooks/useProjects.ts)
## 下游影响
- 首页排行
- 项目列表按 star 排序
- AI 搜索结果中的 `stars_desc` / `stars_asc`
## 已确认要点
- 已通过 live n8n MCP 核查 workflow 元数据与职责。
- 该流程直接读写 Postgres,而不是走 Next.js API。
- 所以如果星标不更新,优先排查 n8n 与数据库连接,而不是先看前端排序代码。
## 维护要求
- 如果 `external_links` 的 GitHub 链接筛选规则改变,要同步更新这里和 [registry.json](D:/Code/AI/agent-park/docs/integrations/n8n/registry.json)。
- 如果后续改成走仓库 API 写入,应把“直接 DB 写”改成“仓库 API 触点”。
@@ -0,0 +1,55 @@
# 项目描述向量化
- Registry ID: `project-description-vectorization`
- n8n Workflow ID: `1AvejnM5n-WPApU1vFt9C`
- Status: `confirmed`
- 角色: 为项目生成 embedding,供语义检索工作流使用。
## 触发方式
- 定时触发
- 当前已核查行为:约每 `30` 分钟运行一次
## 数据来源
- `projects` 表中的活跃项目
- SiliconFlow Embeddings API
- 模型:`BAAI/bge-m3`
## 主流程
1. 选出 `embedding IS NULL` 或需要补算的活跃项目。
2. 读取项目的中英文名称、描述和正文。
3. 拼接向量化输入文本。
4. 调用 SiliconFlow Embeddings API 生成向量。
5. 直接写回 `projects.embedding``embeddingUpdatedAt`
## 直接写入字段
- `projects.embedding`
- `projects.embeddingUpdatedAt`
## 与仓库的关系
这条流程直接维护数据库向量列,而不是通过仓库 API 写入。仓库侧主要消费点:
- [schema.prisma](D:/Code/AI/agent-park/prisma/schema.prisma)
- [migration.sql](D:/Code/AI/agent-park/prisma/migrations/20260126000000_add_project_embedding/migration.sql)
- [route.ts](D:/Code/AI/agent-park/src/app/api/search/ai/route.ts)
## 下游影响
- `RAG项目搜索`
- 语义搜索命中质量
- 以自然语言搜索项目的相关性
## 已确认要点
- 已通过 live n8n MCP 核查 workflow 元数据与职责。
- 当前实现是直连数据库维护向量列。
- 所以如果 AI 搜索结果变差,既要查 webhook 搜索流程,也要查这条补向量流程是否落后或失败。
## 维护要求
- 向量输入字段、模型、补算规则变化时,同步更新 [registry.json](D:/Code/AI/agent-park/docs/integrations/n8n/registry.json)。
- 如果后续 embedding 改成异步队列或应用内任务,需要把这里的数据库直写描述同步改掉。
@@ -0,0 +1,66 @@
# RAG项目搜索
- Registry ID: `ai-search`
- n8n Workflow ID: `F5cQ06DykBfpeyfqL-pd7`
- Status: `confirmed`
- 角色: 把用户查询转成向量相似度检索结果,再把候选项目 ID 返回给仓库 API 做二次补全。
## 触发方式
- webhook 触发
- 已核查 webhook path: `ai-search`
## 输入契约
- `desc`
- `limit`
- `page`
- `offset`
- `tags`
- `domains`
- `productForms`
## 主流程
1. 仓库 API 接收搜索请求。
2. API 把请求转发给 n8n webhook `ai-search`
3. n8n 为查询文本生成 embedding。
4. n8n 在 Postgres 中执行向量相似度搜索。
5. n8n 返回候选项目 ID 和相似度。
6. 仓库 API 再按 ID 回库查询完整项目数据并返回前端。
## 输出契约
- `results[].id`
- `results[].similarity`
- `pagination.total`
- `pagination.totalPages`
- `pagination.hasMore`
## 与仓库的关系
这是当前仓库里最直接可见的 n8n 搜索接点:
- [route.ts](D:/Code/AI/agent-park/src/app/api/search/ai/route.ts)
- [useProjects.ts](D:/Code/AI/agent-park/src/hooks/useProjects.ts)
## 关键实现边界
- n8n 负责“召回候选 ID”
- 仓库 API 负责“按 ID 补全项目字段”
- 前端不直接信任 n8n 返回完整项目对象,而是以仓库数据库为准
这个分层是正确的,因为它避免把页面展示完全绑死到 n8n 返回结构。
## 已确认要点
- 已通过 live n8n MCP 核查 workflow 元数据、webhook path 和返回字段。
- 当前搜索依赖 `projects.embedding`,因此和 `项目描述向量化` 强耦合。
- 如果 `N8N_AI_SEARCH_WEBHOOK` 缺失或返回结构变化,搜索 API 会直接受影响。
## 维护要求
- 搜索输入字段、分页规则、n8n 返回结构变化时,必须同步更新:
- [registry.json](D:/Code/AI/agent-park/docs/integrations/n8n/registry.json)
- [route.ts](D:/Code/AI/agent-park/src/app/api/search/ai/route.ts)
- 如未来增加 rerank 或 hybrid search,也应先更新这里,再调整 API 契约。
@@ -0,0 +1,89 @@
# 前沿信号聚合(多源+AI Agent过滤)
- Registry ID: `signals-aggregation`
- n8n Workflow ID: `bAxNZKGq2ApUUiw9`
- Status: `confirmed`
- 角色: 聚合多源讨论与发布内容,筛出 AI Agent 相关信号,结构化后写入 AgentPark 的 `signals` 数据流。
## 触发方式
- 定时触发
- 当前已核查行为:多源周期抓取
## 数据来源
- Hacker News
- GitHub
- arXiv
- Reddit
- Product Hunt
- Hugging Face
## 主流程
1. 从 6 类外部源抓取候选讨论或发布内容。
2. 做基础规则过滤与去重。
3. 使用 LLM 判断是否属于 AI Agent 相关前沿信号。
4. 生成中英双语标题、摘要、主题、标签、sections 和热度字段。
5. 调用仓库 webhook 写入 `signals`
6. 从保留结果中抽取 GitHub 仓库链接,回流 discovery 系统继续发现项目。
## 输入契约
- `apiKey`
- `signals[].source`
- `signals[].sourceUrl`
- `signals[].title`
- `signals[].titleEn`
- `signals[].summary`
- `signals[].summaryEn`
- `signals[].topic`
- `signals[].topicEn`
- `signals[].tags`
- `signals[].sections`
- `signals[].engagement`
- `signals[].hotScore`
- `signals[].isHot`
- `signals[].publishedAt`
- `signals[].isActive`
## 输出契约
- `success`
- `processed`
- `created`
- `updated`
- `failed`
- `errors[].index`
- `errors[].field`
- `errors[].message`
## 与仓库的关系
仓库内直接接点:
- [auth.ts](D:/Code/AI/agent-park/src/lib/auth.ts)
- [route.ts](D:/Code/AI/agent-park/src/app/api/webhook/signals/route.ts)
- [route.ts](D:/Code/AI/agent-park/src/app/api/signals/route.ts)
- [validations.ts](D:/Code/AI/agent-park/src/lib/validations.ts)
- [schema.prisma](D:/Code/AI/agent-park/prisma/schema.prisma)
## 下游影响
- Signals 页面内容
- 热门信号排序与过滤
- 从信号反向发现 GitHub 项目的回流链路
## 已确认要点
- 已通过 live n8n MCP 核查 workflow 元数据与 webhook 写入方向。
- 该流程会向 `/api/webhook/signals` 写入结构化 signals。
- 它还会触发外部 discovery 去重与任务创建,因此不只是“信号展示流”,也是项目发现的旁路入口。
- workflow 当前在 HTTP body 中携带共享密钥,仓库侧应视为待治理项,后续改为 n8n credential 或环境变量注入。
## 维护要求
- 字段结构、验证 schema、热度算法变更时,同步更新:
- [registry.json](D:/Code/AI/agent-park/docs/integrations/n8n/registry.json)
- [validations.ts](D:/Code/AI/agent-park/src/lib/validations.ts)
- [route.ts](D:/Code/AI/agent-park/src/app/api/webhook/signals/route.ts)
@@ -0,0 +1,73 @@
# 项目标签重置
- Registry ID: `tag-reset`
- n8n Workflow ID: `8tIgBqLyWrBewJPs`
- Status: `confirmed`
- 角色: 基于 n8n 分类结果批量重置项目标签,保证项目标签体系的一致性。
## 触发方式
- 手动触发
- 当前已核查行为:面向批量标签治理任务
## 数据来源
- `GET /api/tags`
- `GET /api/projects`
- n8n 内的 LLM 标签分类
## 主流程
1. 从仓库读取标签池和项目列表。
2. 在 n8n 内对项目做多分类标签判断。
3. 组装批量标签重置请求。
4. 调用仓库 API `/api/tags/reset-projects`
5. 返回更新结果、失败信息和 dry-run 结果。
## 输入契约
- `apiKey`
- `dryRun`
- `replaceAllCategories`
- `categories`
- `projects[].projectSlug`
- `projects[].selectedTagSlugsByCategory`
## 输出契约
- `success`
- `result.dryRun`
- `result.categories`
- `result.updatedCount`
- `result.failedCount`
- `result.results[].projectSlug`
- `result.results[].status`
- `result.results[].details`
## 与仓库的关系
仓库内直接接点:
- [auth.ts](D:/Code/AI/agent-park/src/lib/auth.ts)
- [route.ts](D:/Code/AI/agent-park/src/app/api/tags/reset-projects/route.ts)
- [validations.ts](D:/Code/AI/agent-park/src/lib/validations.ts)
- [schema.prisma](D:/Code/AI/agent-park/prisma/schema.prisma)
## 下游影响
- 项目列表筛选
- 项目详情标签展示
- 标签一致性与后续搜索效果
## 已确认要点
- 已通过 live n8n MCP 核查 workflow 元数据与请求方向。
- 该流程会先读取仓库标签和项目,再把批量结果回写到仓库 API。
- workflow 当前在请求体中携带共享密钥,仓库侧应视为待治理项,后续改为 n8n credential 或环境变量注入。
## 维护要求
- 标签分类规则、分类维度、批量请求结构变更时,同步更新:
- [registry.json](D:/Code/AI/agent-park/docs/integrations/n8n/registry.json)
- [route.ts](D:/Code/AI/agent-park/src/app/api/tags/reset-projects/route.ts)
- [validations.ts](D:/Code/AI/agent-park/src/lib/validations.ts)
+32
View File
@@ -0,0 +1,32 @@
# Workflow Specs
这里存放 8 条已核查生产流程的仓库内说明文件。
作用:
- 给 AI 和工程协作者提供“单流程级别”的说明,而不是只看总表
- 固定每条流程的触发方式、数据源、关键节点、写入位置、仓库触点和边界
- 当 n8n 流程有变更时,可以快速定位应该更新哪一份说明
## 索引
1. [1 Topic项目计划新增](D:/Code/AI/agent-park/docs/integrations/n8n/workflows/01-topic-discovery.md)
2. [2 每日Github Trending项目计划新增](D:/Code/AI/agent-park/docs/integrations/n8n/workflows/02-github-trending-discovery.md)
3. [3 项目分析入库(多源)](D:/Code/AI/agent-park/docs/integrations/n8n/workflows/03-project-ingestion-multi-source.md)
4. [4 GitHub Star 每日刷新](D:/Code/AI/agent-park/docs/integrations/n8n/workflows/04-github-star-refresh.md)
5. [5 项目描述向量化](D:/Code/AI/agent-park/docs/integrations/n8n/workflows/05-project-description-vectorization.md)
6. [6 RAG项目搜索](D:/Code/AI/agent-park/docs/integrations/n8n/workflows/06-rag-project-search.md)
7. [7 前沿信号聚合(多源+AI Agent过滤)](D:/Code/AI/agent-park/docs/integrations/n8n/workflows/07-signals-aggregation.md)
8. [8 项目标签重置](D:/Code/AI/agent-park/docs/integrations/n8n/workflows/08-project-tag-reset.md)
## 使用规则
- `registry.json` 记录的是映射和契约
- `workflows/*.md` 记录的是流程结构和职责
- [DATAFLOW.md](D:/Code/AI/agent-park/docs/integrations/n8n/DATAFLOW.md) 记录的是全链路视图
建议在每次生产流程改动后同时更新:
1. 对应 `workflows/*.md`
2. [registry.json](D:/Code/AI/agent-park/docs/integrations/n8n/registry.json)
3. `pnpm n8n:context`
+2 -1
View File
@@ -7,7 +7,8 @@
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"test": "vitest" "test": "vitest",
"n8n:context": "node scripts/generate-n8n-context.mjs"
}, },
"dependencies": { "dependencies": {
"@prisma/client": "^6.1.0", "@prisma/client": "^6.1.0",
+329
View File
@@ -0,0 +1,329 @@
import fs from "node:fs";
import path from "node:path";
const ROOT = process.cwd();
const REGISTRY_PATH = path.join(ROOT, "docs", "integrations", "n8n", "registry.json");
const DOC_OUTPUT_PATH = path.join(
ROOT,
"docs",
"integrations",
"n8n",
"CONTEXT.generated.md"
);
const PLANNING_OUTPUT_PATH = path.join(ROOT, ".planning", "codebase", "N8N-CONTEXT.md");
const SOURCE_DIR = path.join(ROOT, "src");
const ENV_EXAMPLE_PATH = path.join(ROOT, ".env.example");
const WORKFLOW_EXPORT_DIR = path.join(ROOT, "docs", "integrations", "n8n", "exports");
const TOUCHPOINT_PATTERN = /\bN8N_[A-Z0-9_]+\b|n8n|webhook/gi;
const TOUCHPOINT_LINE_PATTERN = /\bN8N_[A-Z0-9_]+\b|n8n|webhook/i;
const CODE_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"]);
function ensureDir(targetPath) {
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
}
function readJsonIfExists(targetPath, fallback) {
if (!fs.existsSync(targetPath)) {
return fallback;
}
try {
return JSON.parse(fs.readFileSync(targetPath, "utf8"));
} catch (error) {
throw new Error(`Failed to parse JSON at ${targetPath}: ${error.message}`);
}
}
function readTextIfExists(targetPath) {
if (!fs.existsSync(targetPath)) {
return "";
}
return fs.readFileSync(targetPath, "utf8");
}
function walkFiles(dirPath) {
if (!fs.existsSync(dirPath)) {
return [];
}
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const absolutePath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
files.push(...walkFiles(absolutePath));
continue;
}
if (!CODE_EXTENSIONS.has(path.extname(entry.name))) {
continue;
}
files.push(absolutePath);
}
return files;
}
function isPrimaryTouchpoint(relativePath) {
if (relativePath.includes(".test.")) {
return false;
}
if (relativePath.startsWith("src/messages/")) {
return false;
}
return true;
}
function collectEnvVars() {
const content = readTextIfExists(ENV_EXAMPLE_PATH);
const envVars = new Set();
for (const line of content.split(/\r?\n/)) {
const match = line.match(/^([A-Z0-9_]+)=/);
if (!match) {
continue;
}
if (match[1].includes("N8N") || match[1].includes("WEBHOOK")) {
envVars.add(match[1]);
}
}
return [...envVars].sort();
}
function detectTouchpoints() {
const files = walkFiles(SOURCE_DIR);
const touchpoints = [];
for (const filePath of files) {
const content = fs.readFileSync(filePath, "utf8");
const matches = [...content.matchAll(TOUCHPOINT_PATTERN)];
if (matches.length === 0) {
continue;
}
const relativePath = path.relative(ROOT, filePath).replaceAll("\\", "/");
const lines = content.split(/\r?\n/);
const highlights = [];
const seenLineNumbers = new Set();
lines.forEach((line, index) => {
if (!TOUCHPOINT_LINE_PATTERN.test(line)) {
return;
}
const lineNumber = index + 1;
if (seenLineNumbers.has(lineNumber)) {
return;
}
seenLineNumbers.add(lineNumber);
highlights.push({
lineNumber,
text: line.trim(),
});
});
touchpoints.push({
path: relativePath,
matchCount: matches.length,
highlights: highlights.slice(0, 5),
});
}
return touchpoints.sort((a, b) => a.path.localeCompare(b.path));
}
function listExports() {
if (!fs.existsSync(WORKFLOW_EXPORT_DIR)) {
return [];
}
return fs
.readdirSync(WORKFLOW_EXPORT_DIR, { withFileTypes: true })
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
.map((entry) => entry.name)
.sort();
}
function toList(value) {
return Array.isArray(value) ? value.filter(Boolean) : [];
}
function formatBullets(items, emptyText = "- none") {
if (items.length === 0) {
return [emptyText];
}
return items.map((item) => `- ${item}`);
}
function quoteInline(value) {
return String(value).replaceAll("`", "\\`");
}
function renderWorkflow(workflow) {
const entrypoints = toList(workflow?.n8n?.entrypoints).map((entrypoint) => {
const method = entrypoint.method ? `${entrypoint.method} ` : "";
const pathValue = entrypoint.path || "(missing path)";
return `${entrypoint.kind || "entrypoint"}: ${method}${pathValue}`.trim();
});
const consumers = toList(workflow?.repository?.consumers);
const envVars = toList(workflow?.repository?.env);
const schemas = toList(workflow?.repository?.schemas);
const requestFields = toList(workflow?.contracts?.requestFields).map((item) => `\`${item}\``);
const responseFields = toList(workflow?.contracts?.responseFields).map((item) => `\`${item}\``);
const upstreams = toList(workflow?.upstreams);
const downstreams = toList(workflow?.downstreams);
const owners = toList(workflow?.owners);
const notes = workflow?.notes ? [`- ${workflow.notes}`] : ["- none"];
const lines = [
`## ${workflow.name || workflow.id || "Unnamed workflow"}`,
"",
`- Status: \`${workflow.status || "unknown"}\``,
`- ID: \`${quoteInline(workflow.id || "missing-id")}\``,
`- Purpose: ${workflow.purpose || "missing purpose"}`,
`- n8n workflow id: \`${quoteInline(workflow?.n8n?.workflowId || "not recorded")}\``,
`- Export file: \`${quoteInline(workflow?.n8n?.exportFile || "not recorded")}\``,
"",
"### Entrypoints",
...formatBullets(entrypoints),
"",
"### Repository Touchpoints",
...formatBullets(consumers),
"",
"### Environment",
...formatBullets(envVars),
"",
"### Contracts",
`- Request fields: ${requestFields.length > 0 ? requestFields.join(", ") : "none"}`,
`- Response fields: ${responseFields.length > 0 ? responseFields.join(", ") : "none"}`,
"",
"### Related Systems",
`- Upstreams: ${upstreams.length > 0 ? upstreams.join(", ") : "none"}`,
`- Downstreams: ${downstreams.length > 0 ? downstreams.join(", ") : "none"}`,
"",
"### Ownership",
...formatBullets(owners),
"",
"### Notes",
...notes,
"",
];
if (schemas.length > 0) {
lines.splice(lines.indexOf("### Related Systems"), 0, "### Schemas", ...formatBullets(schemas), "");
}
return lines.join("\n");
}
function renderTouchpoint(touchpoint) {
const lines = [
`- \`${touchpoint.path}\` (${touchpoint.matchCount} matches)`,
];
for (const highlight of touchpoint.highlights) {
lines.push(` - L${highlight.lineNumber}: \`${quoteInline(highlight.text)}\``);
}
return lines.join("\n");
}
function buildMarkdown(registry, touchpoints, envVars, exportsList) {
const workflows = toList(registry?.workflows);
const documentedConsumers = new Set(
workflows.flatMap((workflow) => toList(workflow?.repository?.consumers))
);
const undocumentedTouchpoints = touchpoints.filter(
(touchpoint) => isPrimaryTouchpoint(touchpoint.path) && !documentedConsumers.has(touchpoint.path)
);
const sections = [
"# N8N Context",
"",
`Generated at: ${new Date().toISOString()}`,
"",
"This file is generated from `docs/integrations/n8n/registry.json` plus repository scanning.",
"It exists so external n8n workflows become committed, reviewable context for AI agents and GSD.",
"",
"## Workflow Inventory",
"",
];
if (workflows.length === 0) {
sections.push("No workflows documented in `docs/integrations/n8n/registry.json` yet.", "");
} else {
for (const workflow of workflows) {
sections.push(renderWorkflow(workflow));
}
}
sections.push("## Exported Workflow Files", "");
sections.push(...formatBullets(exportsList.map((fileName) => `docs/integrations/n8n/exports/${fileName}`)));
sections.push("");
sections.push("## Detected Repository Touchpoints", "");
if (touchpoints.length === 0) {
sections.push("No n8n or webhook references detected under `src/`.", "");
} else {
for (const touchpoint of touchpoints) {
sections.push(renderTouchpoint(touchpoint), "");
}
}
sections.push("## Environment Variables", "");
sections.push(...formatBullets(envVars), "");
sections.push("## Gaps To Fill", "");
if (undocumentedTouchpoints.length === 0) {
sections.push("- All detected repo touchpoints are mapped to documented workflows.", "");
} else {
sections.push(
"- These files mention n8n or webhook logic but are not mapped in `docs/integrations/n8n/registry.json`:"
);
sections.push(...formatBullets(undocumentedTouchpoints.map((item) => item.path)), "");
}
sections.push("## Maintenance Rules", "");
sections.push("- When an n8n workflow changes, update `docs/integrations/n8n/registry.json` in the same PR.");
sections.push("- If possible, export the workflow JSON into `docs/integrations/n8n/exports/` and reference it from the registry.");
sections.push("- Re-run `pnpm n8n:context` after every workflow, contract, or route change.");
sections.push("- Treat this file as generated output; edit the registry instead of editing this file directly.", "");
return sections.join("\n");
}
function writeOutput(outputPath, content) {
ensureDir(outputPath);
fs.writeFileSync(outputPath, content, "utf8");
}
function main() {
const registry = readJsonIfExists(REGISTRY_PATH, { workflows: [] });
const touchpoints = detectTouchpoints();
const envVars = collectEnvVars();
const exportsList = listExports();
const markdown = buildMarkdown(registry, touchpoints, envVars, exportsList);
writeOutput(DOC_OUTPUT_PATH, markdown);
if (fs.existsSync(path.dirname(PLANNING_OUTPUT_PATH))) {
writeOutput(PLANNING_OUTPUT_PATH, markdown);
}
console.log(`Generated ${path.relative(ROOT, DOC_OUTPUT_PATH)}`);
if (fs.existsSync(path.dirname(PLANNING_OUTPUT_PATH))) {
console.log(`Generated ${path.relative(ROOT, PLANNING_OUTPUT_PATH)}`);
}
}
main();