docs: add n8n integration context
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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/*` 的实现文档与路由映射
|
||||||
@@ -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`
|
||||||
@@ -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)
|
||||||
@@ -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
@@ -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",
|
||||||
|
|||||||
@@ -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();
|
||||||
Reference in New Issue
Block a user