Skip to content

feat(kontext): add cross-file dependency graph tool#1458

Draft
junghyeonsu wants to merge 6 commits intodevfrom
feature/des-1580
Draft

feat(kontext): add cross-file dependency graph tool#1458
junghyeonsu wants to merge 6 commits intodevfrom
feature/des-1580

Conversation

@junghyeonsu
Copy link
Copy Markdown
Contributor

@junghyeonsu junghyeonsu commented Apr 14, 2026

Summary

SEED Design 모노레포에서 파일 간 의존 관계를 선언적으로 정의하고, 빌드/검증/시각화할 수 있는 Kontext 도구를 추가합니다.

배경

SEED Design은 rootage YAML → qvism-preset → css → react 생성 파이프라인을 따르는데, 이 파일 간 관계가 코드에 암묵적으로만 존재했습니다. Kontext는 이 관계를 kontext.yaml로 명시화하여:

  • 어떤 파일을 수정하면 어디가 영향받는지 즉시 파악
  • 생성 파일이 누락된 곳을 자동 감지
  • 네이밍/import 컨벤션 위반을 린트

구성

패키지 역할
@kontext/core 그래프 빌드, 쿼리, 린트, 스키마 검증
@kontext/cli kontext build, check, deps, lint, serve CLI
@kontext/dashboard Cytoscape 기반 의존 그래프 시각화 (React)

주요 기능

  • kontext buildkontext.yaml 파일들을 파싱하여 전체 의존 그래프 생성
  • kontext check — 선언된 의존 파일이 실제로 존재하는지 완전성 검증
  • kontext deps <file> — 특정 파일의 상/하위 의존 관계 조회
  • kontext lint — 네이밍 규칙, import 패턴, git co-change 분석
  • kontext serve — 대시보드에서 그래프 탐색 및 완전성 매트릭스 확인
  • Claude hook — 파일 편집 시 영향받는 파일을 자동으로 표시

보안/성능

  • 대시보드 serve에 path traversal 방어 적용 (resolve + startsWith 검증)
  • 쿼리 함수에서 Map 기반 O(1) 노드 조회 (O(n·m) → O(n))

Test plan

  • @kontext/core 단위 테스트: schema, resolver, graph-builder, query
  • kontext buildpackages/cli/kontext.yaml 빌드 확인
  • kontext serve 로 대시보드 접근 확인
  • path traversal 시도 시 403 응답 확인

🤖 Generated with Claude Code

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • Kontext 의존성 그래프 분석 CLI 도구 추가
    • 대화형 대시보드를 통한 의존성 시각화 지원
    • kontext.yaml 구성을 통한 관계 선언 기능
    • 의존성 확인, 완성도 검증, 미선언 관계 감지 명령어 제공
  • 문서화

    • YAML 스키마 참고 문서 추가
    • Kontext 워크플로우 사용 가이드 추가

junghyeonsu and others added 2 commits April 14, 2026 16:50
Add the Kontext ecosystem for tracking and visualizing cross-file
dependencies defined in kontext.yaml files. Includes:
- @kontext/core: graph builder, query API, linter, schema validation
- @kontext/cli: CLI with build, check, deps, lint, serve commands
- @kontext/dashboard: React + Cytoscape graph visualization
- Claude hook for showing affected files during edits
- Skill definition for AI-assisted usage

Security: path traversal protection in dashboard serve command.
Performance: O(1) node lookups via Map in all query functions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 14, 2026

⚠️ No Changeset found

Latest commit: e43a2be

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 14, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a2ce0ed3-ff76-444f-b116-a43a44d08c07

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Kontext 의존성 관리 시스템이 새로 도입되었습니다. 여기에는 YAML 설정으로부터 의존성 그래프를 구축하고 분석하는 TypeScript 핵심 라이브러리, 검증 및 시각화를 위한 Node.js CLI, 대화형 그래프 탐색용 React 대시보드, 그리고 자동 영향 분석을 위한 Claude 통합 훅이 포함됩니다.

Changes

Cohort / File(s) Summary
Claude Integration Hooks
.claude/hooks/kontext-deps-hook.sh, .claude/hooks/kontext-deps-hook.ts, .claude/settings.json
Claude editor 내에서 파일 쓰기 작업 후 실행되는 PostToolUse 훅을 추가하여 영향받는 파일과 생성 대상을 자동으로 분석하고 JSON 형식의 추가 컨텍스트를 반환합니다.
Core Library - Type System & Schemas
ecosystem/kontext/core/src/types.ts, ecosystem/kontext/core/src/schema.ts, ecosystem/kontext/core/kontext.schema.json, ecosystem/kontext/core/tsconfig.json
KontextConfig, GraphNode, GraphEdge, LintSuggestion 등의 타입 정의 및 JSON Schema 검증을 포함합니다. 엄격한 타입 체크와 선언 파일 생성이 활성화된 TypeScript 설정을 제공합니다.
Core Library - Graph Building
ecosystem/kontext/core/src/graph-builder.ts, ecosystem/kontext/core/src/parser.ts, ecosystem/kontext/core/src/resolver.ts, ecosystem/kontext/core/test/graph-builder.test.ts
kontext.yaml 파일을 스캔하여 의존성 그래프를 구축합니다. 글로브 패턴, ID 템플릿 확장, 조건부 오버라이드를 지원하며 파일 존재 여부를 추적합니다.
Core Library - Query & Analysis
ecosystem/kontext/core/src/query.ts, ecosystem/kontext/core/src/lint.ts, ecosystem/kontext/core/src/index.ts, ecosystem/kontext/core/test/query.test.ts
그래프를 쿼리하여 의존성 찾기, 완전성 검사, Git 코-변경 분석을 통한 린팅 기능을 제공합니다. 이름 지정, import, co-change 레이어별로 선언되지 않은 관계를 제안합니다.
CLI - Command Registration & Entry
ecosystem/kontext/cli/src/bin/kontext.ts, ecosystem/kontext/cli/bin/kontext.mjs, ecosystem/kontext/cli/tsconfig.json
CAC 기반 CLI 프레임워크를 초기화하고 build, check, deps, lint, serve 명령을 등록합니다.
CLI - Build & Check Commands
ecosystem/kontext/cli/src/commands/build.ts, ecosystem/kontext/cli/src/commands/check.ts
kontext.yaml 파일을 스캔하여 그래프를 구축하고 .kontext/graph.json에 저장합니다. CI 모드에서 누락된 파일 발견 시 종료 코드 1을 반환합니다.
CLI - Query & Serve Commands
ecosystem/kontext/cli/src/commands/deps.ts, ecosystem/kontext/cli/src/commands/serve.ts, ecosystem/kontext/cli/src/commands/lint.ts
파일의 의존성을 조회하고, 린트 분석을 실행하며, HTTP 서버를 시작하여 대시보드를 제공합니다.
CLI - Rendering Utilities
ecosystem/kontext/cli/src/utils/render.ts
CLI 출력을 위한 트리 형식, 테이블 형식, JSON 직렬화, 린트 결과 렌더링을 제공합니다.
Dashboard - Setup & Configuration
ecosystem/kontext/dashboard/index.html, ecosystem/kontext/dashboard/tsconfig.json, ecosystem/kontext/dashboard/vite.config.ts, ecosystem/kontext/dashboard/.gitignore
React 앱의 HTML 진입점과 Vite 빌드 설정을 포함합니다.
Dashboard - Core Components
ecosystem/kontext/dashboard/src/main.tsx, ecosystem/kontext/dashboard/src/App.tsx, ecosystem/kontext/dashboard/src/types.ts, ecosystem/kontext/dashboard/src/hooks/useGraph.ts
React 앱 진입점, 그래프 상태 관리, 그래프 데이터 페칭 훅을 구현합니다.
Dashboard - Views & Visualization
ecosystem/kontext/dashboard/src/views/GraphExplorer.tsx, ecosystem/kontext/dashboard/src/views/CompletenessMatrix.tsx
Cytoscape 기반의 대화형 그래프 시각화와 컴포넌트 완전성 매트릭스 뷰를 제공합니다.
Documentation & Configuration
skills/kontext/SKILL.md, skills/kontext/references/yaml-schema.md, packages/cli/kontext.yaml, .gitignore
Kontext 워크플로우 문서, YAML 스키마 참고서, CLI 패키지의 예제 설정, 그리고 .kontext/ 디렉토리 무시 규칙을 추가합니다.

Sequence Diagram(s)

sequenceDiagram
    participant User as 사용자
    participant CLI as Kontext CLI
    participant GraphBuilder as Graph Builder
    participant FileSystem as 파일 시스템
    participant Cache as 캐시<br/>(.kontext/)

    User->>CLI: kontext build --root .
    CLI->>FileSystem: kontext.yaml 파일 스캔
    FileSystem-->>CLI: 설정 목록
    CLI->>GraphBuilder: buildGraph({ rootDir })
    GraphBuilder->>FileSystem: 글로브 패턴 매칭 & 파일 읽기
    FileSystem-->>GraphBuilder: 파일 경로 & 내용
    GraphBuilder->>GraphBuilder: 엣지 생성, 노드 존재 여부 체크
    GraphBuilder-->>CLI: KontextGraph
    CLI->>Cache: graph.json 쓰기
    Cache-->>CLI: 저장 완료
    CLI-->>User: 빌드 통계 (노드/엣지/패키지)
Loading
sequenceDiagram
    participant User as 사용자
    participant CLI as CLI:<br/>check/deps
    participant Cache as 캐시<br/>(.kontext/)
    participant GraphBuilder as Graph Builder
    participant Query as Query 엔진

    User->>CLI: kontext check / kontext deps <file>
    CLI->>Cache: graph.json 읽기
    alt 캐시 미스 또는 오류
        CLI->>GraphBuilder: buildGraph() 빌드
        GraphBuilder-->>CLI: KontextGraph
    else 캐시 히트
        Cache-->>CLI: KontextGraph
    end
    CLI->>Query: checkCompleteness(graph)<br/>또는 findDeps(graph, file)
    Query->>Query: 그래프 쿼리 & 분석
    Query-->>CLI: CheckResult[] 또는<br/>DepResult[]
    CLI-->>User: 포맷된 결과<br/>(트리/테이블/JSON)
Loading
sequenceDiagram
    participant Browser as 브라우저
    participant Dashboard as React<br/>Dashboard
    participant Server as HTTP<br/>Server
    participant FileSystem as 파일 시스템
    participant GraphBuilder as Graph<br/>Builder

    Browser->>Dashboard: 페이지 로드
    Dashboard->>Dashboard: useGraph() 초기화
    Dashboard->>Server: GET /api/graph
    Server->>FileSystem: graph.json 확인
    alt 캐시 존재
        FileSystem-->>Server: graph.json
    else 캐시 미스
        Server->>GraphBuilder: buildGraph() 구축
        GraphBuilder-->>Server: KontextGraph
    end
    Server-->>Dashboard: JSON 응답
    Dashboard->>Dashboard: 그래프 상태 업데이트
    Dashboard-->>Browser: GraphExplorer 또는<br/>CompletenessMatrix 렌더링
    Browser->>Browser: Cytoscape / 표 시각화
Loading
sequenceDiagram
    participant Claude as Claude<br/>Editor
    participant Hook as Kontext<br/>Hook Script
    participant Cache as 캐시<br/>(.kontext/)
    participant GraphBuilder as Graph<br/>Builder
    participant Query as Query<br/>엔진

    Claude->>Hook: 파일 쓰기/편집 감지
    Hook->>Cache: graph.json 읽기 시도
    alt 캐시 미스 또는 오류
        Hook->>GraphBuilder: buildGraph() (10초 타임아웃)
        GraphBuilder-->>Hook: KontextGraph
    else 캐시 히트
        Cache-->>Hook: KontextGraph
    end
    Hook->>Query: 수정된 파일에 대해<br/>findDeps() 실행
    Query-->>Hook: DepResult[]
    Hook->>Hook: 수동/자동 생성 의존성<br/>분류
    Hook-->>Claude: 추가 컨텍스트 JSON<br/>(영향받는 파일 & 생성 명령)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 의존성의 미로를 밝히는 새 친구,
Kontext가 그래프를 그으며 변화의 파도를 헤아려,
모노레포의 복잡함을 정리하고 영향을 미리 알려주네!
빌드도 빨라지고 실수도 줄어드니,
우리 토끼도 흐뭇해라! 🌿✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely describes the main feature being added: a cross-file dependency graph tool named Kontext.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/des-1580

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread ecosystem/kontext/cli/bin/kontext.mjs Fixed
Comment thread ecosystem/kontext/cli/bin/kontext.mjs Fixed
Comment thread ecosystem/kontext/core/src/lint.ts Fixed
@junghyeonsu junghyeonsu marked this pull request as draft April 14, 2026 08:11
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 14, 2026

Alpha Preview (Docs)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

🧹 Nitpick comments (8)
ecosystem/kontext/cli/src/commands/build.ts (1)

11-11: 공통 CLI 옵션 타입을 공유 타입으로 분리하는 편이 좋습니다.

Line 11의 { root: string }는 다른 커맨드에도 반복되므로, 공통 타입(예: RootOption)으로 추출해 재사용하면 변경 전파가 쉬워집니다.

As per coding guidelines, ecosystem/**/*.{ts,tsx}: Maintain TypeScript ESM pattern for CLI and core modules with explicit type sharing for common types.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ecosystem/kontext/cli/src/commands/build.ts` at line 11, The inline anonymous
options type in the build command (.action((options: { root: string }) => { ...
}) in build.ts) should be extracted to a shared named type (e.g., RootOption) so
other CLI commands can reuse it; create/export RootOption from a common CLI
types module (or an existing shared module), replace the inline `{ root: string
}` with `RootOption` in the build command's .action signature, and update other
commands to import and use RootOption to maintain the TypeScript ESM pattern and
avoid duplicated option types.
ecosystem/kontext/cli/src/commands/check.ts (1)

36-43: 그래프 로더를 공용 유틸로 추출하는 편이 안전합니다.

동일한 loadOrBuildGraph 로직이 다른 명령 파일에도 복제되어 있어, 추후 로딩/에러 처리 정책이 쉽게 어긋납니다. 공용 유틸로 묶어 동작 일관성을 유지해 주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ecosystem/kontext/cli/src/commands/check.ts` around lines 36 - 43, Extract
the duplicated loadOrBuildGraph logic into a single exported utility (e.g.,
export function loadOrBuildGraph(rootDir: string): KontextGraph) in a shared
module, keep its current behavior (resolve .kontext/graph.json, readFileSync +
JSON.parse, fallback to buildGraph({ rootDir })), and update command files to
import this utility instead of duplicating the code; ensure you export/import
the KontextGraph type and buildGraph reference or import buildGraph inside the
util so error/typing behavior remains identical.
ecosystem/kontext/dashboard/src/types.ts (1)

7-20: 대시보드 GraphEdge 타입이 서버 payload와 이미 드리프트했습니다.

serve는 core graph를 그대로 JSON으로 내보내고, core의 GraphEdge에는 optionaldefinedBy도 포함됩니다. 여기서 빠뜨리면 클라이언트가 실제 응답보다 더 좁은 타입을 갖게 되어 이후 UI에서 해당 필드를 쓰려 할 때 타입 안전성이 깨집니다. @kontext/core의 공유 타입을 import type으로 재사용하거나, 최소한 동일 필드를 모두 반영해 두는 편이 좋습니다.

As per coding guidelines, "Maintain TypeScript ESM pattern for CLI and core modules with explicit type sharing for common types".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ecosystem/kontext/dashboard/src/types.ts` around lines 7 - 20, The client
GraphEdge type has drifted from the server/core shape; update the GraphEdge
definition to match core by importing the canonical type (import type {
GraphEdge } from "@kontext/core") and reuse it in KontextGraph (or, if you
cannot import, add the missing fields optional?: boolean and definedBy?: string
to the local GraphEdge) so the client signature matches the server payload;
ensure KontextGraph still uses GraphNode[] and GraphEdge[] types consistently
and follow the ESM explicit type import pattern when referencing shared types.
ecosystem/kontext/cli/bin/kontext.mjs (1)

151-161: loadOrBuildGraph 함수가 중복 정의되어 있습니다.

loadOrBuildGraph$1 (lines 151-161)과 loadOrBuildGraph (lines 184-195)가 동일한 로직을 포함합니다. 빌드 번들링 과정에서 발생한 것으로 보이지만, 원본 소스에서 단일 함수로 통합하는 것이 유지보수에 좋습니다.

Also applies to: 184-195

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ecosystem/kontext/cli/bin/kontext.mjs` around lines 151 - 161, There are two
identical implementations of the same logic: loadOrBuildGraph$1 and
loadOrBuildGraph; remove the duplicate by keeping a single canonical function
(e.g., loadOrBuildGraph) and delete the redundant loadOrBuildGraph$1 definition,
then update any internal references/usages to call the kept function name so the
bundle uses one implementation (ensure buildGraph and
resolve/readFileSync/JSON.parse usage remain unchanged).
ecosystem/kontext/cli/src/utils/render.ts (1)

71-75: LAYER_LABELS 매핑이 불필요합니다.

키와 값이 동일하므로 이 매핑 객체는 실질적인 변환을 수행하지 않습니다. 직접 layer 값을 사용하거나, 향후 다국어 지원을 위해 유지한다면 주석으로 의도를 명시하는 것이 좋습니다.

♻️ 불필요한 매핑 제거
-const LAYER_LABELS: Record<string, string> = {
-  naming: "naming",
-  import: "import",
-  "co-change": "co-change",
-};
-
 export function renderLintResults(result: LintResult): string {
   // ...
-      lines.push(pc.cyan(`  [${LAYER_LABELS[layer]}] ${items.length} suggestions`));
+      lines.push(pc.cyan(`  [${layer}] ${items.length} suggestions`));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ecosystem/kontext/cli/src/utils/render.ts` around lines 71 - 75, The
LAYER_LABELS constant is redundant because keys equal values; remove the
LAYER_LABELS object and replace references to
LAYER_LABELS[...]/LAYER_LABELS[layer] with the raw layer value (e.g., use layer
directly) in functions that render labels (search for usages of LAYER_LABELS in
this file), or if you intend to preserve it for future i18n, keep it but add a
comment above LAYER_LABELS explaining it's a placeholder for localization and
why keys mirror values.
ecosystem/kontext/core/src/query.ts (1)

6-47: findDepsfindAffectedBy의 중복 로직을 추출할 수 있습니다.

두 함수가 거의 동일한 패턴을 따릅니다. nodeMap 생성과 결과 매핑 로직이 중복되어 있으며, 차이점은 edge.source vs edge.target 비교와 결과의 path 필드뿐입니다.

현재 구현이 명확하고 읽기 쉬우므로 선택적 개선 사항입니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ecosystem/kontext/core/src/query.ts` around lines 6 - 47, Extract the shared
logic from findDeps and findAffectedBy into a single helper (e.g., collectDeps)
that builds nodeMap from graph.nodes and iterates graph.edges, taking a selector
or boolean flag to choose whether to compare edge.source or edge.target and
which path to emit; update findDeps and findAffectedBy to call this helper
(preserving returned DepResult shape with path, reason, generated, command,
exists using node?.exists ?? false) so the duplicated nodeMap creation and
result mapping are removed while behavior and symbols (findDeps, findAffectedBy,
nodeMap, DepResult, graph.edges) remain unchanged.
ecosystem/kontext/core/src/graph-builder.ts (1)

208-218: extractIdFromWatchedresolver.tstoKebabCase 로직을 중복합니다.

이 함수는 resolver.tstoKebabCase (lines 74-80)와 동일한 정규식 패턴을 사용합니다. 또한 resolver.ts에서 export된 extractId 함수가 더 정교한 패턴 기반 ID 추출을 지원하지만 사용되지 않습니다.

중복 제거를 위해 resolver.ts의 함수를 import하여 사용하거나, 사용하지 않는 export를 정리하는 것을 고려해 주세요.

♻️ resolver.ts의 toKebabCase를 재사용

resolver.ts에서 toKebabCase를 export하고:

// resolver.ts
export function toKebabCase(name: string): string {
  return name
    .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
    .replace(/([A-Z])([A-Z][a-z])/g, "$1-$2")
    .toLowerCase();
}

graph-builder.ts에서 import하여 사용:

-import { expandTemplate, hasTemplate } from "./resolver.js";
+import { expandTemplate, hasTemplate, toKebabCase } from "./resolver.js";

 function extractIdFromWatched(filePath: string): string | null {
   const name = filePath.split("/").pop() ?? "";
   const dotIdx = name.lastIndexOf(".");
   const base = dotIdx >= 0 ? name.slice(0, dotIdx) : name;
   if (!base || base === "index") return null;

-  return base
-    .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
-    .replace(/([A-Z])([A-Z][a-z])/g, "$1-$2")
-    .toLowerCase();
+  return toKebabCase(base);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ecosystem/kontext/core/src/graph-builder.ts` around lines 208 - 218, The
extractIdFromWatched function duplicates the kebab-casing logic in resolver.ts
(toKebabCase) and overlaps with resolver.ts's exported extractId; remove the
duplicated logic by importing and using resolver.ts's toKebabCase (or directly
use the exported extractId if it fits the behavior) in graph-builder.ts, replace
calls to extractIdFromWatched with the imported function (update any callers
inside graph-builder.ts), and delete the local extractIdFromWatched declaration
to avoid duplication.
ecosystem/kontext/core/src/schema.ts (1)

25-37: validateStringArray가 유효하지 않은 항목을 포함한 배열을 반환합니다.

검증 루프에서 문자열이 아닌 항목을 발견하면 issues에 추가하지만, 반환 시에는 원본 배열을 그대로 string[]으로 타입 단언하여 반환합니다. 유효하지 않은 항목이 포함된 상태로 사용될 수 있습니다.

현재 validateConfig에서 issues가 있으면 예외를 던지므로 실제 런타임 문제는 발생하지 않지만, 함수 자체의 계약을 명확히 하려면 필터링하거나 빈 배열을 반환하는 것이 좋습니다.

♻️ 유효한 항목만 반환하도록 개선
 function validateStringArray(value: unknown, fieldName: string, issues: string[]): string[] {
   if (value === undefined) return [];
   if (!Array.isArray(value)) {
     issues.push(`${fieldName} must be an array of strings`);
     return [];
   }
+  const valid: string[] = [];
   for (let i = 0; i < value.length; i++) {
     if (typeof value[i] !== "string") {
       issues.push(`${fieldName}[${i}] must be a string`);
+    } else {
+      valid.push(value[i]);
     }
   }
-  return value as string[];
+  return valid;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ecosystem/kontext/core/src/schema.ts` around lines 25 - 37,
validateStringArray currently records non-string items to issues but returns the
original array cast to string[], allowing invalid entries to slip through;
update validateStringArray to iterate value, collect only items of type "string"
into a new result array, push issues for non-string entries (using fieldName and
index like `${fieldName}[${i}] must be a string`), and return the filtered
result (or [] if undefined/not an array) so callers (e.g., validateConfig)
receive only valid strings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.claude/hooks/kontext-deps-hook.ts:
- Around line 20-32: The current hook only builds when .kontext/graph.json is
missing (graphPath) so edits to kontext.yaml or source files can leave a stale
graph; change the logic that currently calls execSync("bun
ecosystem/kontext/cli/bin/kontext.mjs build", { cwd: rootDir, ... }) and the
subsequent existsSync(graphPath) check to first detect whether the graph is
outdated by comparing graphPath mtime (or a stored input hash inside graph.json)
against kontext.yaml and relevant source files (e.g., project src tree or any
files that affect graph membership); if any input is newer or hashes differ,
invoke the existing execSync rebuild and then re-check graphPath (keeping the
same stdio/timeout options), otherwise keep using the existing graph.json.

In @.claude/settings.json:
- Around line 73-79: The matcher value "Write|Edit" in the settings is missing
the MultiEdit token so the kontext-deps-hook.sh command will not run for
multi-file edits; update the matcher to include "MultiEdit" (e.g.,
"Write|Edit|MultiEdit") and then verify the kontext-deps-hook.sh hook logic
(which currently expects a single filePath) to ensure it either iterates
per-file when invoked for MultiEdit or is adjusted to accept/process multiple
file paths; update any parsing/looping in the hook to handle multiple files if
necessary.

In `@ecosystem/kontext/cli/bin/kontext.mjs`:
- Around line 266-288: The code builds file paths from user-controlled req.url
into filePath and indexPath without sanitization, allowing path traversal; fix
by resolving the requested path with path.resolve(join(dashboardDir, url)) (use
path.resolve on dashboardDir + url) and verify the resolved path is inside the
dashboardDir using a safeBase = path.resolve(dashboardDir) and
safePath.startsWith(safeBase + path.sep) before reading; if the check fails
return 404 or fallback to index.html; apply the same resolve+startsWith guard
for indexPath handling so reads never escape dashboardDir.

In `@ecosystem/kontext/cli/src/commands/deps.ts`:
- Around line 18-20: The current check using resolve(file).startsWith(rootDir)
mis-identifies paths and fails on Windows separators; replace that logic in
deps.ts (where relFile is computed) with path.relative(rootDir, resolve(file))
to get a true relative path and normalize separators to forward slashes,
handling absolute inputs (use path.isAbsolute if needed) and falling back to the
original file when appropriate; apply the identical fix to graph-builder.ts's
relativeFromRoot function (add imports for path.relative and path.isAbsolute) so
both modules compute and normalize relative paths consistently and avoid false
positives like "/repo-other/...".

In `@ecosystem/kontext/cli/src/commands/lint.ts`:
- Line 18: The CLI currently exposes option("--fix", "Auto-apply suggestions to
kontext.yaml files") but doesn't implement writing (the handler only prints a
warning and returns); locate the option("--fix", ...) declaration in lint.ts and
either remove it or mark it hidden/experimental (e.g., use a hidden or
experimental flag label or rename to --experimental-fix) and update the help
text accordingly; also update the handler block that prints the warning and
returns (the current early-return path for the fix warning) so that when --fix
is passed with --json it does not silently exit—ensure it emits an explicit
warning/error and exits non-zero or implements the actual write behavior before
advertising the flag.

In `@ecosystem/kontext/cli/src/commands/serve.ts`:
- Around line 49-53: The handler currently checks only existsSync(filePath)
before calling readFileSync, which will throw EISDIR for directory requests
(e.g., requesting /assets); update the logic in the serve handler to stat the
path (use fs.statSync or fs.lstatSync on filePath) and verify stats.isFile()
before reading; if it's not a file, respond with 404 or delegate to the next
handler instead of calling readFileSync; also wrap the file read in a try/catch
to log and respond with 500 on unexpected I/O errors.
- Line 24: The resolved dashboardDir currently points to cli/dashboard/dist
(using "../../dashboard/dist") but the built dashboard lives at
kontext/dashboard/dist; update the path used when creating dashboardDir (the
resolve call that assigns dashboardDir in serve.ts) to go up one more directory
(use "../../../dashboard/dist") so it resolves to the actual
ecosystem/kontext/dashboard/dist output.
- Around line 40-47: The containment check using
filePath.startsWith(dashboardDir) can be bypassed by sibling prefixes; replace
it with a robust containment test: compute the resolved absolute filePath (from
url and dashboardDir) and use path.relative(dashboardDir, filePath) to ensure
the result does not start with '..' and is not equal to '..' (and also ensure
filePath is absolute), rejecting the request (403) if the relative path begins
with '..' or if path.isAbsolute check fails; update the check around the
variables filePath, dashboardDir, and url accordingly.

In `@ecosystem/kontext/core/kontext.schema.json`:
- Around line 27-30: The JSON schema allows empty strings but runtime validation
rejects empty values for the string fields, so update the schema entries for the
string properties referenced (the "when" property and the other string
occurrences at the later blocks referenced by "match" and "path") to enforce
non-empty values by adding "minLength": 1 to each of those string schemas
(update the "when" schema and the two other string schemas at the other
occurrences) so the schema matches the runtime validation behavior.

In `@ecosystem/kontext/core/src/lint.ts`:
- Around line 194-202: The git command construction in lint.ts inserts the
CLI-derived commitCount directly into a shell string causing potential shell
injection; update the code to validate and sanitize commitCount (parseInt,
ensure it's a finite positive integer and clamp to a safe max) and then call a
non-shell API that accepts argv arrays (e.g., child_process.spawnSync or
execFileSync) instead of execSync so you pass
['log','--name-only','--pretty=format:COMMIT:%H','-n', String(commitCount)] with
the same options (cwd, encoding, maxBuffer); reference the gitOutput variable
and the commitCount usage in the try/catch around execSync to locate where to
apply these changes.

In `@ecosystem/kontext/dashboard/src/App.tsx`:
- Around line 72-87: The button currently only uses color to indicate active
state which is inaccessible; update the JSX for the interactive element (the
button with props onClick, children and the active boolean) to include an ARIA
state by adding aria-pressed={active} (ensure active is a boolean) so screen
readers receive the pressed/active state; keep existing visual styles intact and
do not change the button's semantics.

In `@ecosystem/kontext/dashboard/src/hooks/useGraph.ts`:
- Line 15: The catch handler in useGraph currently assumes the thrown value has
a message property; update the .catch callback to accept err as unknown and
perform a type guard before calling setError — e.g., in the promise chain inside
useGraph, change the anonymous catch to check if err is an instance of Error and
use err.message, otherwise coerce the value (e.g., String(err)) and pass that to
setError so TypeScript type-safety is preserved for the setError call.

In `@ecosystem/kontext/dashboard/src/views/CompletenessMatrix.tsx`:
- Around line 61-66: The cell currently conveys status only via a colored dot
(in CompletenessMatrix.tsx inside the td rendering using row.layers[l]) which is
inaccessible; update the rendering to include an accessible status label for
assistive tech: when rendering the span for the dot (the element that shows ● or
○), add an aria-label (or role="img" + aria-label) with a clear text like
"complete" or "incomplete" based on row.layers[l], and/or include a
visually-hidden text node (e.g., an sr-only span) next to the symbol so screen
readers announce the state; keep the existing visual dot and tdStyle but ensure
the status text is derived from row.layers[l].

In `@ecosystem/kontext/dashboard/src/views/GraphExplorer.tsx`:
- Around line 29-37: The label computation for graph nodes uses
node.id.split("/").pop() which returns an empty string for IDs with a trailing
slash (e.g., "pkg-b/docs/"); update the label logic in GraphExplorer.tsx where
elements are built (the node loop that sets data.label) to strip any trailing
slashes from node.id before extracting the basename (or otherwise normalize the
ID) so labels are never empty; keep the rest of the data fields (packageDir,
color via getColor, exists) unchanged.

In `@skills/kontext/references/yaml-schema.md`:
- Line 22: 문서의 용어 불일치 문제: 스키마 키 이름은 `when`인데 설명 문장에 `watch`가 사용되어 혼동을 줍니다. 해당
문장(현재 "watch 패턴에서 매칭된 파일명에서 자동 추출됨.")에서 `watch`를 `when`으로 교체하여 "when 패턴에서 매칭된
파일명에서 자동 추출됨."처럼 스키마 키(`when`)와 용어를 일치시켜 주세요.

In `@skills/kontext/SKILL.md`:
- Around line 1-4: Add a YAML frontmatter block at the very top of SKILL.md
containing at minimum the required `name` and `description` fields (e.g., ---
name: "Kontext" description: "모노레포 의존성 그래프를 탐색하고, 변경 영향 범위를 파악한다." ---),
ensuring the frontmatter is the first content in the file so the skills metadata
collector can parse `name` and `description` correctly.

---

Nitpick comments:
In `@ecosystem/kontext/cli/bin/kontext.mjs`:
- Around line 151-161: There are two identical implementations of the same
logic: loadOrBuildGraph$1 and loadOrBuildGraph; remove the duplicate by keeping
a single canonical function (e.g., loadOrBuildGraph) and delete the redundant
loadOrBuildGraph$1 definition, then update any internal references/usages to
call the kept function name so the bundle uses one implementation (ensure
buildGraph and resolve/readFileSync/JSON.parse usage remain unchanged).

In `@ecosystem/kontext/cli/src/commands/build.ts`:
- Line 11: The inline anonymous options type in the build command
(.action((options: { root: string }) => { ... }) in build.ts) should be
extracted to a shared named type (e.g., RootOption) so other CLI commands can
reuse it; create/export RootOption from a common CLI types module (or an
existing shared module), replace the inline `{ root: string }` with `RootOption`
in the build command's .action signature, and update other commands to import
and use RootOption to maintain the TypeScript ESM pattern and avoid duplicated
option types.

In `@ecosystem/kontext/cli/src/commands/check.ts`:
- Around line 36-43: Extract the duplicated loadOrBuildGraph logic into a single
exported utility (e.g., export function loadOrBuildGraph(rootDir: string):
KontextGraph) in a shared module, keep its current behavior (resolve
.kontext/graph.json, readFileSync + JSON.parse, fallback to buildGraph({ rootDir
})), and update command files to import this utility instead of duplicating the
code; ensure you export/import the KontextGraph type and buildGraph reference or
import buildGraph inside the util so error/typing behavior remains identical.

In `@ecosystem/kontext/cli/src/utils/render.ts`:
- Around line 71-75: The LAYER_LABELS constant is redundant because keys equal
values; remove the LAYER_LABELS object and replace references to
LAYER_LABELS[...]/LAYER_LABELS[layer] with the raw layer value (e.g., use layer
directly) in functions that render labels (search for usages of LAYER_LABELS in
this file), or if you intend to preserve it for future i18n, keep it but add a
comment above LAYER_LABELS explaining it's a placeholder for localization and
why keys mirror values.

In `@ecosystem/kontext/core/src/graph-builder.ts`:
- Around line 208-218: The extractIdFromWatched function duplicates the
kebab-casing logic in resolver.ts (toKebabCase) and overlaps with resolver.ts's
exported extractId; remove the duplicated logic by importing and using
resolver.ts's toKebabCase (or directly use the exported extractId if it fits the
behavior) in graph-builder.ts, replace calls to extractIdFromWatched with the
imported function (update any callers inside graph-builder.ts), and delete the
local extractIdFromWatched declaration to avoid duplication.

In `@ecosystem/kontext/core/src/query.ts`:
- Around line 6-47: Extract the shared logic from findDeps and findAffectedBy
into a single helper (e.g., collectDeps) that builds nodeMap from graph.nodes
and iterates graph.edges, taking a selector or boolean flag to choose whether to
compare edge.source or edge.target and which path to emit; update findDeps and
findAffectedBy to call this helper (preserving returned DepResult shape with
path, reason, generated, command, exists using node?.exists ?? false) so the
duplicated nodeMap creation and result mapping are removed while behavior and
symbols (findDeps, findAffectedBy, nodeMap, DepResult, graph.edges) remain
unchanged.

In `@ecosystem/kontext/core/src/schema.ts`:
- Around line 25-37: validateStringArray currently records non-string items to
issues but returns the original array cast to string[], allowing invalid entries
to slip through; update validateStringArray to iterate value, collect only items
of type "string" into a new result array, push issues for non-string entries
(using fieldName and index like `${fieldName}[${i}] must be a string`), and
return the filtered result (or [] if undefined/not an array) so callers (e.g.,
validateConfig) receive only valid strings.

In `@ecosystem/kontext/dashboard/src/types.ts`:
- Around line 7-20: The client GraphEdge type has drifted from the server/core
shape; update the GraphEdge definition to match core by importing the canonical
type (import type { GraphEdge } from "@kontext/core") and reuse it in
KontextGraph (or, if you cannot import, add the missing fields optional?:
boolean and definedBy?: string to the local GraphEdge) so the client signature
matches the server payload; ensure KontextGraph still uses GraphNode[] and
GraphEdge[] types consistently and follow the ESM explicit type import pattern
when referencing shared types.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 90440f8e-ab49-4c32-986b-35ced69ee6c0

📥 Commits

Reviewing files that changed from the base of the PR and between 7ca8e6c and 6294d66.

⛔ Files ignored due to path filters (5)
  • bun.lock is excluded by !**/*.lock
  • ecosystem/kontext/cli/package.json is excluded by !**/package.json
  • ecosystem/kontext/core/package.json is excluded by !**/package.json
  • ecosystem/kontext/dashboard/package.json is excluded by !**/package.json
  • package.json is excluded by !**/package.json
📒 Files selected for processing (40)
  • .claude/hooks/kontext-deps-hook.sh
  • .claude/hooks/kontext-deps-hook.ts
  • .claude/settings.json
  • .gitignore
  • ecosystem/kontext/cli/bin/kontext.mjs
  • ecosystem/kontext/cli/src/bin/kontext.ts
  • ecosystem/kontext/cli/src/commands/build.ts
  • ecosystem/kontext/cli/src/commands/check.ts
  • ecosystem/kontext/cli/src/commands/deps.ts
  • ecosystem/kontext/cli/src/commands/lint.ts
  • ecosystem/kontext/cli/src/commands/serve.ts
  • ecosystem/kontext/cli/src/utils/render.ts
  • ecosystem/kontext/cli/tsconfig.json
  • ecosystem/kontext/core/kontext.schema.json
  • ecosystem/kontext/core/src/graph-builder.ts
  • ecosystem/kontext/core/src/index.ts
  • ecosystem/kontext/core/src/lint.ts
  • ecosystem/kontext/core/src/parser.ts
  • ecosystem/kontext/core/src/query.ts
  • ecosystem/kontext/core/src/resolver.ts
  • ecosystem/kontext/core/src/schema.ts
  • ecosystem/kontext/core/src/types.ts
  • ecosystem/kontext/core/test/graph-builder.test.ts
  • ecosystem/kontext/core/test/query.test.ts
  • ecosystem/kontext/core/test/resolver.test.ts
  • ecosystem/kontext/core/test/schema.test.ts
  • ecosystem/kontext/core/tsconfig.json
  • ecosystem/kontext/dashboard/.gitignore
  • ecosystem/kontext/dashboard/index.html
  • ecosystem/kontext/dashboard/src/App.tsx
  • ecosystem/kontext/dashboard/src/hooks/useGraph.ts
  • ecosystem/kontext/dashboard/src/main.tsx
  • ecosystem/kontext/dashboard/src/types.ts
  • ecosystem/kontext/dashboard/src/views/CompletenessMatrix.tsx
  • ecosystem/kontext/dashboard/src/views/GraphExplorer.tsx
  • ecosystem/kontext/dashboard/tsconfig.json
  • ecosystem/kontext/dashboard/vite.config.ts
  • packages/cli/kontext.yaml
  • skills/kontext/SKILL.md
  • skills/kontext/references/yaml-schema.md

Comment thread .claude/hooks/kontext-deps-hook.ts
Comment thread .claude/settings.json Outdated
Comment thread ecosystem/kontext/cli/bin/kontext.mjs Outdated
Comment thread ecosystem/kontext/cli/src/commands/deps.ts Outdated
Comment thread ecosystem/kontext/cli/src/commands/lint.ts Outdated
Comment thread ecosystem/kontext/dashboard/src/hooks/useGraph.ts Outdated
Comment thread ecosystem/kontext/dashboard/src/views/CompletenessMatrix.tsx Outdated
Comment thread ecosystem/kontext/dashboard/src/views/GraphExplorer.tsx Outdated
Comment thread skills/kontext/references/yaml-schema.md Outdated
Comment thread skills/kontext/SKILL.md
junghyeonsu and others added 2 commits April 14, 2026 17:53
- fix path traversal: sibling prefix guard, rootDir-based dashboard path, EISDIR check
- fix shell injection: use execFileSync with array args instead of string interpolation
- fix cache invalidation: compare kontext.yaml mtime vs graph.json for auto-rebuild
- fix MultiEdit matcher missing from PostToolUse hook
- fix deps path resolution for subdirectory execution
- fix JSON Schema minLength mismatch with runtime validation
- add accessibility: aria-pressed on tabs, aria-label on matrix cells
- fix trailing slash empty label in graph explorer
- fix --fix option marked as experimental (not yet implemented)
- fix SKILL.md missing frontmatter, yaml-schema.md terminology (watch→when)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- extract shared RootOption type and loadOrBuildGraph utility
- sync dashboard GraphEdge type with core (add optional, definedBy)
- remove redundant LAYER_LABELS mapping in render.ts
- reuse toKebabCase from resolver.ts in graph-builder.ts
- add overrides.affects entry validation in schema.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@junghyeonsu
Copy link
Copy Markdown
Contributor Author

@coderabbitai resolve

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 14, 2026

✅ Actions performed

Comments resolved. Approval is disabled; enable reviews.request_changes_workflow to allow explicit top-level @coderabbitai resolve or @coderabbitai approve commands.

…ighlighting

Replace Explorer/Editor dual-tab layout with a single unified KontextView:
- Left panel: KONTEXTS/FILES tabs with dependency-aware file tree
- Center panel: relation editor with exists/missing/optional status badges
- Right panel: YAML/File preview tabs with shiki syntax highlighting

Key features:
- Status-based tree highlights (when/exists/missing/optional/folder)
- Hover tooltips showing all related when patterns and reasons
- Active kontext.yaml glow animation in Files tab
- File content preview with line numbers via /api/file-content endpoint
- Auto-expand folders containing highlighted files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

Alpha Preview (Stackflow SPA)

@github-actions
Copy link
Copy Markdown
Contributor

Alpha Preview (Storybook)

- Delete 6 unused files (Editor, Explorer, ConnectionLines, DependencyTree,
  RelationCard, PackageSidebar) — 1,832 lines removed
- Remove dead types (ViewMode, RelationGroup, AffectedFile, PackageSummary)
  and dead functions (buildPackageSummaries, buildRelationGroups) from graph-utils
- serve.ts: hoist BINARY_EXTS to module scope, fix double statSync, remove
  unnecessary existsSync before mkdirSync
- useFileTree: fix loadDir identity churn via ref (was re-creating on every
  state update, causing cascading re-renders)
- CodePreview: useEffect → useMemo (synchronous highlight, no extra render)
- useConfig: remove unused currentPackage state
- handleDrop: fix stale deps (was [relations, ignorePatterns], now [])

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@malangcat
Copy link
Copy Markdown
Contributor

question: https://www.npmjs.com/package/madge 같은 기존 도구들이랑 어떤 부분에서 다른거에요?

@junghyeonsu
Copy link
Copy Markdown
Contributor Author

아직은 완전히 아이디어 단계긴한데...

관리해야 하는 패키지랑 챙겨야 하는 문서가 많아져서, 한 레포안에서 단순 AST로 의존 관계를 표현할 수 없는 경우를 표현하고 싶었어요.
한 예시를 들면 "CLI에 command가 추가되면, seed-design Skill에서 CLI 사용법과 content에서 command 혹은 configuration이 바뀔 수 있음"을 표현할 수 있고, Hook 시스템으로 항상 변경된 파일들에 대해서 affects 관계를 확인하고, 유저에게 해당 파일도 변경되어야 하는지 되질문을 할 수 있는 구조를 만들고 싶었어요.

근데 이게 도구를 만들기 위해서 만든 도구가 될 수 있어서 좀 조심스럽게 생각하는중입니다.
관리 포인트가 늘어나기도 해서, Git History를 주기적으로 봐서 관계를 이어줄 수 있는 부분이 있으면 자동적으로 체크하는 그런 아이디어로도 생각하고 있어요.

스크린샷 2026-04-17 오후 4 42 35

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants