From 804fd756e7be11e90f417e81c6ef18032b45eee4 Mon Sep 17 00:00:00 2001 From: Caihaohan Date: Mon, 20 Apr 2026 19:05:05 +0800 Subject: [PATCH] docs: add n8n integration context --- .planning/codebase/N8N-CONTEXT.md | 2 +- docs/integrations/n8n/CONTEXT.generated.md | 375 +++++++++++++ docs/integrations/n8n/DATAFLOW.md | 321 +++++++++++ docs/integrations/n8n/README.md | 58 ++ docs/integrations/n8n/registry.json | 498 ++++++++++++++++++ .../n8n/workflows/01-topic-discovery.md | 63 +++ .../workflows/02-github-trending-discovery.md | 62 +++ .../03-project-ingestion-multi-source.md | 67 +++ .../n8n/workflows/04-github-star-refresh.md | 54 ++ .../05-project-description-vectorization.md | 55 ++ .../n8n/workflows/06-rag-project-search.md | 66 +++ .../n8n/workflows/07-signals-aggregation.md | 89 ++++ .../n8n/workflows/08-project-tag-reset.md | 73 +++ docs/integrations/n8n/workflows/README.md | 32 ++ package.json | 3 +- scripts/generate-n8n-context.mjs | 329 ++++++++++++ 16 files changed, 2145 insertions(+), 2 deletions(-) create mode 100644 docs/integrations/n8n/CONTEXT.generated.md create mode 100644 docs/integrations/n8n/DATAFLOW.md create mode 100644 docs/integrations/n8n/README.md create mode 100644 docs/integrations/n8n/registry.json create mode 100644 docs/integrations/n8n/workflows/01-topic-discovery.md create mode 100644 docs/integrations/n8n/workflows/02-github-trending-discovery.md create mode 100644 docs/integrations/n8n/workflows/03-project-ingestion-multi-source.md create mode 100644 docs/integrations/n8n/workflows/04-github-star-refresh.md create mode 100644 docs/integrations/n8n/workflows/05-project-description-vectorization.md create mode 100644 docs/integrations/n8n/workflows/06-rag-project-search.md create mode 100644 docs/integrations/n8n/workflows/07-signals-aggregation.md create mode 100644 docs/integrations/n8n/workflows/08-project-tag-reset.md create mode 100644 docs/integrations/n8n/workflows/README.md create mode 100644 scripts/generate-n8n-context.mjs diff --git a/.planning/codebase/N8N-CONTEXT.md b/.planning/codebase/N8N-CONTEXT.md index 29eef74..16a71b3 100644 --- a/.planning/codebase/N8N-CONTEXT.md +++ b/.planning/codebase/N8N-CONTEXT.md @@ -1,6 +1,6 @@ # 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. It exists so external n8n workflows become committed, reviewable context for AI agents and GSD. diff --git a/docs/integrations/n8n/CONTEXT.generated.md b/docs/integrations/n8n/CONTEXT.generated.md new file mode 100644 index 0000000..16a71b3 --- /dev/null +++ b/docs/integrations/n8n/CONTEXT.generated.md @@ -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;` + +- `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. diff --git a/docs/integrations/n8n/DATAFLOW.md b/docs/integrations/n8n/DATAFLOW.md new file mode 100644 index 0000000..84adaf0 --- /dev/null +++ b/docs/integrations/n8n/DATAFLOW.md @@ -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/*` 的实现文档与路由映射 diff --git a/docs/integrations/n8n/README.md b/docs/integrations/n8n/README.md new file mode 100644 index 0000000..19f2e94 --- /dev/null +++ b/docs/integrations/n8n/README.md @@ -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` diff --git a/docs/integrations/n8n/registry.json b/docs/integrations/n8n/registry.json new file mode 100644 index 0000000..d2e702b --- /dev/null +++ b/docs/integrations/n8n/registry.json @@ -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." + } + ] +} diff --git a/docs/integrations/n8n/workflows/01-topic-discovery.md b/docs/integrations/n8n/workflows/01-topic-discovery.md new file mode 100644 index 0000000..379f033 --- /dev/null +++ b/docs/integrations/n8n/workflows/01-topic-discovery.md @@ -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 服务代码并入当前仓库,应把这里的外部边界改成具体路由映射。 diff --git a/docs/integrations/n8n/workflows/02-github-trending-discovery.md b/docs/integrations/n8n/workflows/02-github-trending-discovery.md new file mode 100644 index 0000000..6df5e15 --- /dev/null +++ b/docs/integrations/n8n/workflows/02-github-trending-discovery.md @@ -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`。 diff --git a/docs/integrations/n8n/workflows/03-project-ingestion-multi-source.md b/docs/integrations/n8n/workflows/03-project-ingestion-multi-source.md new file mode 100644 index 0000000..1b2ebef --- /dev/null +++ b/docs/integrations/n8n/workflows/03-project-ingestion-multi-source.md @@ -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 映射。 diff --git a/docs/integrations/n8n/workflows/04-github-star-refresh.md b/docs/integrations/n8n/workflows/04-github-star-refresh.md new file mode 100644 index 0000000..98830fe --- /dev/null +++ b/docs/integrations/n8n/workflows/04-github-star-refresh.md @@ -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 触点”。 diff --git a/docs/integrations/n8n/workflows/05-project-description-vectorization.md b/docs/integrations/n8n/workflows/05-project-description-vectorization.md new file mode 100644 index 0000000..2402729 --- /dev/null +++ b/docs/integrations/n8n/workflows/05-project-description-vectorization.md @@ -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 改成异步队列或应用内任务,需要把这里的数据库直写描述同步改掉。 diff --git a/docs/integrations/n8n/workflows/06-rag-project-search.md b/docs/integrations/n8n/workflows/06-rag-project-search.md new file mode 100644 index 0000000..5f2ece5 --- /dev/null +++ b/docs/integrations/n8n/workflows/06-rag-project-search.md @@ -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 契约。 diff --git a/docs/integrations/n8n/workflows/07-signals-aggregation.md b/docs/integrations/n8n/workflows/07-signals-aggregation.md new file mode 100644 index 0000000..aa645ea --- /dev/null +++ b/docs/integrations/n8n/workflows/07-signals-aggregation.md @@ -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) diff --git a/docs/integrations/n8n/workflows/08-project-tag-reset.md b/docs/integrations/n8n/workflows/08-project-tag-reset.md new file mode 100644 index 0000000..e4fc447 --- /dev/null +++ b/docs/integrations/n8n/workflows/08-project-tag-reset.md @@ -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) diff --git a/docs/integrations/n8n/workflows/README.md b/docs/integrations/n8n/workflows/README.md new file mode 100644 index 0000000..c3ddd2b --- /dev/null +++ b/docs/integrations/n8n/workflows/README.md @@ -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` diff --git a/package.json b/package.json index 3d31684..f7fc8d2 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "build": "next build", "start": "next start", "lint": "next lint", - "test": "vitest" + "test": "vitest", + "n8n:context": "node scripts/generate-n8n-context.mjs" }, "dependencies": { "@prisma/client": "^6.1.0", diff --git a/scripts/generate-n8n-context.mjs b/scripts/generate-n8n-context.mjs new file mode 100644 index 0000000..153482b --- /dev/null +++ b/scripts/generate-n8n-context.mjs @@ -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();