From 09fc78f32168a8285840704853464bf62f9cb7fe Mon Sep 17 00:00:00 2001 From: Lum1104 Date: Sat, 14 Mar 2026 22:49:37 +0800 Subject: [PATCH] feat(skill): add /understand-onboard command for team onboarding guides Generates a structured markdown onboarding guide from the knowledge graph, including project overview, architecture layers, key concepts, guided tour, file map, and complexity hotspots. Co-Authored-By: Claude Opus 4.6 --- .../.claude/skills/understand-onboard.md | 23 ++++ .../src/__tests__/onboard-builder.test.ts | 87 ++++++++++++ packages/skill/src/index.ts | 1 + packages/skill/src/onboard-builder.ts | 124 ++++++++++++++++++ 4 files changed, 235 insertions(+) create mode 100644 packages/skill/.claude/skills/understand-onboard.md create mode 100644 packages/skill/src/__tests__/onboard-builder.test.ts create mode 100644 packages/skill/src/onboard-builder.ts diff --git a/packages/skill/.claude/skills/understand-onboard.md b/packages/skill/.claude/skills/understand-onboard.md new file mode 100644 index 0000000..873a1b9 --- /dev/null +++ b/packages/skill/.claude/skills/understand-onboard.md @@ -0,0 +1,23 @@ +--- +name: understand-onboard +description: Generate a structured onboarding guide for new team members using the knowledge graph +--- + +# /understand-onboard + +Generate a comprehensive onboarding guide from the project's knowledge graph. + +## Instructions + +1. Read the knowledge graph at `.understand-anything/knowledge-graph.json` +2. If it doesn't exist, tell the user to run `/understand` first +3. Generate a structured onboarding guide that includes: + - Project overview (name, languages, frameworks, description) + - Architecture layers and their responsibilities + - Key concepts to understand + - Guided tour (step-by-step walkthrough) + - File map (what each key file does) + - Complexity hotspots (what to be careful with) +4. Format as clean markdown +5. Offer to save the guide to `docs/ONBOARDING.md` in the project +6. Suggest the user commit it to the repo for the team diff --git a/packages/skill/src/__tests__/onboard-builder.test.ts b/packages/skill/src/__tests__/onboard-builder.test.ts new file mode 100644 index 0000000..4408b11 --- /dev/null +++ b/packages/skill/src/__tests__/onboard-builder.test.ts @@ -0,0 +1,87 @@ +import { describe, it, expect } from "vitest"; +import { buildOnboardingGuide } from "../onboard-builder.js"; +import type { KnowledgeGraph } from "@understand-anything/core"; + +const sampleGraph: KnowledgeGraph = { + version: "1.0.0", + project: { + name: "test-project", + languages: ["typescript", "python"], + frameworks: ["express", "prisma"], + description: "A test REST API", + analyzedAt: "2026-03-14T00:00:00Z", + gitCommitHash: "abc123", + }, + nodes: [ + { id: "file:src/index.ts", type: "file", name: "index.ts", filePath: "src/index.ts", summary: "Entry point", tags: ["entry"], complexity: "simple" }, + { id: "file:src/service.ts", type: "file", name: "service.ts", filePath: "src/service.ts", summary: "Core service", tags: ["service"], complexity: "complex" }, + { id: "concept:auth", type: "concept", name: "Auth Flow", summary: "JWT-based authentication", tags: ["concept", "auth"], complexity: "complex" }, + ], + edges: [ + { source: "file:src/index.ts", target: "file:src/service.ts", type: "imports", direction: "forward", weight: 0.8 }, + ], + layers: [ + { id: "layer:api", name: "API Layer", description: "Routes and handlers", nodeIds: ["file:src/index.ts"] }, + { id: "layer:service", name: "Service Layer", description: "Business logic", nodeIds: ["file:src/service.ts"] }, + ], + tour: [ + { order: 1, title: "Start Here", description: "Begin with index.ts", nodeIds: ["file:src/index.ts"] }, + { order: 2, title: "Core Logic", description: "Service layer", nodeIds: ["file:src/service.ts"] }, + ], +}; + +describe("onboard-builder", () => { + it("includes project overview section", () => { + const guide = buildOnboardingGuide(sampleGraph); + expect(guide).toContain("# test-project"); + expect(guide).toContain("A test REST API"); + }); + + it("lists languages and frameworks", () => { + const guide = buildOnboardingGuide(sampleGraph); + expect(guide).toContain("typescript"); + expect(guide).toContain("express"); + }); + + it("includes architecture layers section", () => { + const guide = buildOnboardingGuide(sampleGraph); + expect(guide).toContain("## Architecture"); + expect(guide).toContain("API Layer"); + expect(guide).toContain("Service Layer"); + }); + + it("includes key concepts section", () => { + const guide = buildOnboardingGuide(sampleGraph); + expect(guide).toContain("## Key Concepts"); + expect(guide).toContain("Auth Flow"); + }); + + it("includes getting started / tour section", () => { + const guide = buildOnboardingGuide(sampleGraph); + expect(guide).toContain("## Getting Started"); + expect(guide).toContain("Start Here"); + }); + + it("includes complexity hotspots", () => { + const guide = buildOnboardingGuide(sampleGraph); + expect(guide).toContain("## Complexity Hotspots"); + expect(guide).toContain("service.ts"); + }); + + it("includes file map section", () => { + const guide = buildOnboardingGuide(sampleGraph); + expect(guide).toContain("## File Map"); + }); + + it("handles graph with no layers gracefully", () => { + const noLayers = { ...sampleGraph, layers: [] }; + const guide = buildOnboardingGuide(noLayers); + expect(guide).toContain("# test-project"); + }); + + it("handles graph with no tour gracefully", () => { + const noTour = { ...sampleGraph, tour: [] }; + const guide = buildOnboardingGuide(noTour); + expect(guide).toContain("# test-project"); + }); +}); diff --git a/packages/skill/src/index.ts b/packages/skill/src/index.ts index d9bbcac..c0526d3 100644 --- a/packages/skill/src/index.ts +++ b/packages/skill/src/index.ts @@ -14,3 +14,4 @@ export { formatExplainPrompt, type ExplainContext, } from "./explain-builder.js"; +export { buildOnboardingGuide } from "./onboard-builder.js"; diff --git a/packages/skill/src/onboard-builder.ts b/packages/skill/src/onboard-builder.ts new file mode 100644 index 0000000..ed79445 --- /dev/null +++ b/packages/skill/src/onboard-builder.ts @@ -0,0 +1,124 @@ +import type { KnowledgeGraph } from "@understand-anything/core"; + +/** + * Generate a structured onboarding guide from the knowledge graph. + * Output is standalone markdown suitable for a README, wiki, or docs. + */ +export function buildOnboardingGuide(graph: KnowledgeGraph): string { + const { project, nodes, edges, layers, tour } = graph; + const lines: string[] = []; + + // --- Project Overview --- + lines.push(`# ${project.name}`); + lines.push(""); + lines.push(`> ${project.description}`); + lines.push(""); + lines.push(`| | |`); + lines.push(`|---|---|`); + lines.push(`| **Languages** | ${project.languages.join(", ")} |`); + lines.push(`| **Frameworks** | ${project.frameworks.join(", ")} |`); + lines.push(`| **Components** | ${nodes.length} nodes, ${edges.length} relationships |`); + lines.push(`| **Last Analyzed** | ${project.analyzedAt} |`); + lines.push(""); + + // --- Architecture --- + if (layers.length > 0) { + lines.push("## Architecture"); + lines.push(""); + lines.push("The project is organized into the following layers:"); + lines.push(""); + + for (const layer of layers) { + const memberNames = layer.nodeIds + .map((id) => nodes.find((n) => n.id === id)?.name) + .filter(Boolean); + lines.push(`### ${layer.name}`); + lines.push(""); + lines.push(layer.description); + lines.push(""); + if (memberNames.length > 0) { + lines.push(`Key components: ${memberNames.join(", ")}`); + lines.push(""); + } + } + } + + // --- Key Concepts --- + const conceptNodes = nodes.filter((n) => n.type === "concept"); + if (conceptNodes.length > 0) { + lines.push("## Key Concepts"); + lines.push(""); + lines.push("Important architectural and domain concepts to understand:"); + lines.push(""); + for (const concept of conceptNodes) { + lines.push(`### ${concept.name}`); + lines.push(""); + lines.push(concept.summary); + lines.push(""); + } + } + + // --- Getting Started (Tour) --- + if (tour.length > 0) { + lines.push("## Getting Started"); + lines.push(""); + lines.push("Follow this guided tour to understand the codebase:"); + lines.push(""); + for (const step of tour) { + const stepNodes = step.nodeIds + .map((id) => nodes.find((n) => n.id === id)) + .filter(Boolean); + lines.push(`### ${step.order}. ${step.title}`); + lines.push(""); + lines.push(step.description); + lines.push(""); + if (stepNodes.length > 0) { + lines.push("**Files to look at:**"); + for (const node of stepNodes) { + if (node!.filePath) { + lines.push(`- \`${node!.filePath}\` — ${node!.summary}`); + } + } + lines.push(""); + } + if (step.languageLesson) { + lines.push(`> **Language Tip:** ${step.languageLesson}`); + lines.push(""); + } + } + } + + // --- File Map --- + const fileNodes = nodes.filter((n) => n.type === "file" && n.filePath); + if (fileNodes.length > 0) { + lines.push("## File Map"); + lines.push(""); + lines.push("| File | Purpose | Complexity |"); + lines.push("|------|---------|------------|"); + for (const node of fileNodes) { + lines.push(`| \`${node.filePath}\` | ${node.summary} | ${node.complexity} |`); + } + lines.push(""); + } + + // --- Complexity Hotspots --- + const complexNodes = nodes.filter((n) => n.complexity === "complex"); + if (complexNodes.length > 0) { + lines.push("## Complexity Hotspots"); + lines.push(""); + lines.push("These components are the most complex and deserve extra attention:"); + lines.push(""); + for (const node of complexNodes) { + lines.push(`- **${node.name}** (${node.type}): ${node.summary}`); + } + lines.push(""); + } + + // --- Footer --- + lines.push("---"); + lines.push(""); + lines.push(`*Generated by [Understand Anything](https://github.com/anthropics/understand-anything) from knowledge graph v${graph.version}*`); + lines.push(""); + + return lines.join("\n"); +}