# Repository Knowledge Graph Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Generate repository-scoped SDD artifacts plus `knowledge-graph.json` and `knowledge-graph.mmd` from the current `src/`, `docs/`, and `examples/` structure.

**Architecture:** Add a small graph-generation script that scans existing repository files, normalizes them into graph nodes and edges, and writes both JSON and Mermaid outputs into `.aspirecode/sdd/feature_v1.0.0/`. Keep source-of-truth in the repository itself and add minimal SDD files describing the graph model and generation contract.

**Tech Stack:** Node.js, TypeScript-friendly repository layout, npm scripts, Markdown, Mermaid

---

### Task 1: Add SDD base directories and rules summary

**Files:**
- Create: `.aspirecode/sdd/rules.md`

- [ ] **Step 1: Write the rules document content**

```md
# Project Rules Summary

## Project Overview

- Package: `@aspire-ui/element-component-pro`
- Type: Vue 2.7 component library
- Public entry: `src/index.ts`
- Demo app: `examples/`
- Docs: `docs/`

## Tech Stack

- Vue 2.7
- TypeScript
- Vite
- Element UI 2.x
- Vue Router 3
- npm

## Repository Knowledge Sources

- `src/index.ts` is the primary export and registration source
- `src/**` defines components, composables, utilities, and exported types
- `docs/*.md` documents public capabilities
- `examples/router.ts` defines demo route groups
- `examples/pages/**` demonstrates component usage

## Knowledge Graph Constraints

- Model static repository structure only
- Do not infer runtime behavior not visible in code or docs
- Prefer source code over docs when they conflict
- Use exported names as canonical entity names
```

- [ ] **Step 2: Create `.aspirecode/sdd/rules.md` with the content above**

Run: create the file exactly at `.aspirecode/sdd/rules.md`
Expected: file exists and summarizes repository graph rules

- [ ] **Step 3: Verify the file exists**

Run: `ls .aspirecode/sdd`
Expected: output contains `rules.md`

- [ ] **Step 4: Commit**

```bash
git add .aspirecode/sdd/rules.md
git commit -m "docs: add repository graph rules summary"
```

### Task 2: Add versioned SDD graph spec files

**Files:**
- Create: `.aspirecode/sdd/feature_v1.0.0/spec.md`
- Create: `.aspirecode/sdd/feature_v1.0.0/data-model.md`
- Create: `.aspirecode/sdd/feature_v1.0.0/api-contract.md`
- Create: `.aspirecode/sdd/feature_v1.0.0/task.yaml`

- [ ] **Step 1: Create `spec.md`**

```md
# Repository Knowledge Graph Spec

## Objective

Generate a repository knowledge graph for the component library from source code, docs, and demo files.

## Coverage

- public exports in `src/index.ts`
- components in `src/`
- composables in `src/`
- utilities and shared types in `src/`
- docs in `docs/`
- demo routes in `examples/router.ts`
- demo pages in `examples/pages/`

## Output

- `knowledge-graph.json`
- `knowledge-graph.mmd`

## Source Priority

1. `src/index.ts`
2. `src/**`
3. `docs/*.md`
4. `examples/router.ts`
5. `examples/pages/**`
```

- [ ] **Step 2: Create `data-model.md`**

```md
# Repository Knowledge Graph Data Model

## Node Types

- `Component`
- `Composable`
- `Utility`
- `TypeExport`
- `DocPage`
- `RouteGroup`
- `ExamplePage`

## Edge Types

- `registers`
- `exports`
- `depends_on`
- `documents`
- `owns`
- `demonstrates`

## Node Shape

```json
{
  "id": "component:pro-table",
  "type": "Component",
  "name": "ProTable",
  "path": "src/ProTable/ProTable.vue"
}
```

## Edge Shape

```json
{
  "from": "doc:protable",
  "type": "documents",
  "to": "component:pro-table"
}
```
```

- [ ] **Step 3: Create `api-contract.md`**

```md
# Graph Generation Contract

## Inputs

- `src/index.ts`
- `src/**`
- `docs/*.md`
- `examples/router.ts`
- `examples/pages/**`

## Outputs

### JSON

```json
{
  "metadata": {
    "project": "@aspire-ui/element-component-pro",
    "version": "1.0.28"
  },
  "nodes": [],
  "edges": []
}
```

### Mermaid

- graph direction: `TD`
- one node per graph entity
- one labeled edge per graph relation
```

- [ ] **Step 4: Create `task.yaml`**

```yaml
version: feature_v1.0.0
tasks:
  - id: collect-repository-entities
    title: Collect repository entities from source, docs, and examples
  - id: build-graph-json
    title: Generate knowledge-graph.json
  - id: build-graph-mermaid
    title: Generate knowledge-graph.mmd
```

- [ ] **Step 5: Verify files exist**

Run: `ls .aspirecode/sdd/feature_v1.0.0`
Expected: output contains `spec.md`, `data-model.md`, `api-contract.md`, and `task.yaml`

- [ ] **Step 6: Commit**

```bash
git add .aspirecode/sdd/feature_v1.0.0
git commit -m "docs: add knowledge graph sdd artifacts"
```

### Task 3: Add graph generator script and npm entry

**Files:**
- Create: `scripts/generate-knowledge-graph.cjs`
- Modify: `package.json`

- [ ] **Step 1: Write the graph generator script**

```js
const fs = require('fs')
const path = require('path')

const root = process.cwd()
const outDir = path.join(root, '.aspirecode/sdd/feature_v1.0.0')

function read(file) {
  return fs.readFileSync(path.join(root, file), 'utf8')
}

function listFiles(dir, matcher) {
  const abs = path.join(root, dir)
  if (!fs.existsSync(abs)) return []
  const results = []
  for (const entry of fs.readdirSync(abs, { withFileTypes: true })) {
    const rel = path.join(dir, entry.name)
    if (entry.isDirectory()) {
      results.push(...listFiles(rel, matcher))
    } else if (matcher(rel)) {
      results.push(rel)
    }
  }
  return results
}

function kebab(value) {
  return value
    .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
    .replace(/\s+/g, '-')
    .replace(/_/g, '-')
    .toLowerCase()
}

function pushNode(nodes, node) {
  if (!nodes.some((item) => item.id === node.id)) nodes.push(node)
}

function pushEdge(edges, edge) {
  if (!edges.some((item) => item.from === edge.from && item.type === edge.type && item.to === edge.to)) {
    edges.push(edge)
  }
}

const pkg = JSON.parse(read('package.json'))
const indexSource = read('src/index.ts')
const nodes = []
const edges = []

pushNode(nodes, { id: 'entry:src-index', type: 'Entry', name: 'src/index.ts', path: 'src/index.ts' })

const componentNames = [
  'ProTable',
  'TableAction',
  'ProForm',
  'ProFormItem',
  'FormActions',
  'FormattedNumberInput',
  'ProDescriptions',
  'CollapseContainer',
  'ProTableForm'
]

componentNames.forEach((name) => {
  const id = 'component:' + kebab(name)
  pushNode(nodes, { id, type: 'Component', name, path: 'src/index.ts' })
  pushEdge(edges, { from: 'entry:src-index', type: 'registers', to: id })
})

const composables = [
  ['useProTable', 'src/ProTable/useProTable.ts'],
  ['useForm', 'src/ProForm/useForm.ts'],
  ['useDescription', 'src/ProDescriptions/useDescription.ts'],
  ['useProTableForm', 'src/ProTableForm/useProTableForm.ts']
]

composables.forEach(([name, file]) => {
  const id = 'composable:' + kebab(name)
  pushNode(nodes, { id, type: 'Composable', name, path: file })
  pushEdge(edges, { from: 'entry:src-index', type: 'exports', to: id })
})

pushNode(nodes, { id: 'utility:use-component-setting', type: 'Utility', name: 'useComponentSetting', path: 'src/useComponentSetting.ts' })
pushEdge(edges, { from: 'entry:src-index', type: 'exports', to: 'utility:use-component-setting' })

pushNode(nodes, { id: 'type:src-types', type: 'TypeExport', name: 'src/types', path: 'src/types/index.ts' })
pushNode(nodes, { id: 'type:protable-types', type: 'TypeExport', name: 'ProTable/types', path: 'src/ProTable/types/index.ts' })
pushEdge(edges, { from: 'entry:src-index', type: 'exports', to: 'type:src-types' })
pushEdge(edges, { from: 'entry:src-index', type: 'exports', to: 'type:protable-types' })

const docFiles = listFiles('docs', (file) => file.endsWith('.md'))
docFiles.forEach((file) => {
  const base = path.basename(file, '.md')
  const nodeId = 'doc:' + kebab(base)
  pushNode(nodes, { id: nodeId, type: 'DocPage', name: base, path: file })
  const componentId = 'component:' + kebab(base)
  if (nodes.some((node) => node.id === componentId)) {
    pushEdge(edges, { from: nodeId, type: 'documents', to: componentId })
  }
  if (base === 'ComponentSetting') {
    pushEdge(edges, { from: nodeId, type: 'documents', to: 'utility:use-component-setting' })
  }
})

const routeSource = read('examples/router.ts')
const routeMatches = [...routeSource.matchAll(/path:\s*'([^']+)'[\s\S]*?component:\s*([A-Za-z0-9_]+)/g)]
routeMatches.forEach((match) => {
  const routePath = match[1]
  const nodeId = 'route:' + kebab(routePath === '/' ? 'home' : routePath.replace(/^\//, ''))
  pushNode(nodes, { id: nodeId, type: 'RouteGroup', name: routePath, path: 'examples/router.ts' })
})

const exampleFiles = listFiles('examples/pages', (file) => file.endsWith('.vue'))
exampleFiles.forEach((file) => {
  const name = file.replace('examples/pages/', '')
  const nodeId = 'example:' + kebab(name.replace(/\.vue$/, ''))
  pushNode(nodes, { id: nodeId, type: 'ExamplePage', name, path: file })

  if (file.indexOf('ProTablePage/') !== -1) pushEdge(edges, { from: 'route:protable', type: 'owns', to: nodeId })
  if (file.indexOf('ProFormPage/') !== -1) pushEdge(edges, { from: 'route:proform', type: 'owns', to: nodeId })
  if (file.indexOf('ProDescriptionsPage/') !== -1) pushEdge(edges, { from: 'route:prodescriptions', type: 'owns', to: nodeId })
  if (file.indexOf('CollapseContainerPage/') !== -1) pushEdge(edges, { from: 'route:collapse-container', type: 'owns', to: nodeId })
  if (file.indexOf('ProTableFormPage/') !== -1) pushEdge(edges, { from: 'route:protableform', type: 'owns', to: nodeId })
  if (file.indexOf('ComponentSettingTest.vue') !== -1) pushEdge(edges, { from: 'route:component-setting-test', type: 'owns', to: nodeId })

  const exampleSource = read(file)
  componentNames.forEach((name) => {
    if (exampleSource.indexOf(name) !== -1) {
      pushEdge(edges, { from: nodeId, type: 'demonstrates', to: 'component:' + kebab(name) })
    }
  })
  if (exampleSource.indexOf('useComponentSetting') !== -1) {
    pushEdge(edges, { from: nodeId, type: 'demonstrates', to: 'utility:use-component-setting' })
  }
})

pushEdge(edges, { from: 'component:pro-table', type: 'depends_on', to: 'composable:use-pro-table' })
pushEdge(edges, { from: 'component:pro-table', type: 'depends_on', to: 'utility:use-component-setting' })
pushEdge(edges, { from: 'component:pro-form', type: 'depends_on', to: 'composable:use-form' })
pushEdge(edges, { from: 'component:pro-descriptions', type: 'depends_on', to: 'composable:use-description' })
pushEdge(edges, { from: 'component:pro-table-form', type: 'depends_on', to: 'composable:use-pro-table-form' })

const json = {
  metadata: {
    project: pkg.name,
    version: pkg.version,
    generatedAt: new Date().toISOString()
  },
  nodes,
  edges
}

const mermaid = ['graph TD']
nodes.forEach((node) => {
  mermaid.push(`  ${node.id.replace(/[^a-zA-Z0-9_]/g, '_')}["${node.type}: ${node.name}"]`)
})
edges.forEach((edge) => {
  mermaid.push(`  ${edge.from.replace(/[^a-zA-Z0-9_]/g, '_')} -- ${edge.type} --> ${edge.to.replace(/[^a-zA-Z0-9_]/g, '_')}`)
})

fs.mkdirSync(outDir, { recursive: true })
fs.writeFileSync(path.join(outDir, 'knowledge-graph.json'), JSON.stringify(json, null, 2) + '\n')
fs.writeFileSync(path.join(outDir, 'knowledge-graph.mmd'), mermaid.join('\n') + '\n')

console.log('Generated knowledge graph in .aspirecode/sdd/feature_v1.0.0')
```

- [ ] **Step 2: Add the npm script to `package.json`**

```json
{
  "scripts": {
    "generate:graph": "node scripts/generate-knowledge-graph.cjs"
  }
}
```

- [ ] **Step 3: Run the generator**

Run: `npm run generate:graph`
Expected: output contains `Generated knowledge graph in .aspirecode/sdd/feature_v1.0.0`

- [ ] **Step 4: Verify output files exist**

Run: `ls .aspirecode/sdd/feature_v1.0.0`
Expected: output contains `knowledge-graph.json` and `knowledge-graph.mmd`

- [ ] **Step 5: Commit**

```bash
git add package.json scripts/generate-knowledge-graph.cjs .aspirecode/sdd/feature_v1.0.0
git commit -m "feat: add repository knowledge graph generator"
```

### Task 4: Validate graph output against repository structure

**Files:**
- Test: `.aspirecode/sdd/feature_v1.0.0/knowledge-graph.json`
- Test: `.aspirecode/sdd/feature_v1.0.0/knowledge-graph.mmd`

- [ ] **Step 1: Inspect core component nodes in JSON**

Run: `node -e "const g=require('./.aspirecode/sdd/feature_v1.0.0/knowledge-graph.json'); console.log(g.nodes.filter(n=>n.type==='Component').map(n=>n.name).join(','))"`
Expected: output includes `ProTable,TableAction,ProForm,ProFormItem,FormActions,FormattedNumberInput,ProDescriptions,CollapseContainer,ProTableForm`

- [ ] **Step 2: Inspect documentation edges in JSON**

Run: `node -e "const g=require('./.aspirecode/sdd/feature_v1.0.0/knowledge-graph.json'); console.log(g.edges.filter(e=>e.type==='documents').length)"`
Expected: output is a positive integer greater than `0`

- [ ] **Step 3: Inspect Mermaid header**

Run: `node -e "const fs=require('fs'); const s=fs.readFileSync('./.aspirecode/sdd/feature_v1.0.0/knowledge-graph.mmd','utf8'); console.log(s.split('\n')[0])"`
Expected: output is `graph TD`

- [ ] **Step 4: Re-run build validation**

Run: `npm run build`
Expected: Vite build completes successfully

- [ ] **Step 5: Commit**

```bash
git add .aspirecode/sdd/feature_v1.0.0/knowledge-graph.json .aspirecode/sdd/feature_v1.0.0/knowledge-graph.mmd
git commit -m "chore: validate generated repository knowledge graph"
```
