intentpatch 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.ko.md +39 -21
  2. package/README.md +36 -16
  3. package/dist/application/ports/module-alias-resolver.d.ts +11 -0
  4. package/dist/application/ports/module-alias-resolver.d.ts.map +1 -0
  5. package/dist/application/ports/module-alias-resolver.js +2 -0
  6. package/dist/application/ports/module-alias-resolver.js.map +1 -0
  7. package/dist/application/ports/package-lock-parser.d.ts +20 -0
  8. package/dist/application/ports/package-lock-parser.d.ts.map +1 -0
  9. package/dist/application/ports/package-lock-parser.js +2 -0
  10. package/dist/application/ports/package-lock-parser.js.map +1 -0
  11. package/dist/application/rules/npm-lockfile-rule.d.ts +12 -0
  12. package/dist/application/rules/npm-lockfile-rule.d.ts.map +1 -0
  13. package/dist/application/rules/npm-lockfile-rule.js +109 -0
  14. package/dist/application/rules/npm-lockfile-rule.js.map +1 -0
  15. package/dist/application/rules/npm-workspace-dependency-rule.d.ts +10 -0
  16. package/dist/application/rules/npm-workspace-dependency-rule.d.ts.map +1 -0
  17. package/dist/application/rules/npm-workspace-dependency-rule.js +155 -0
  18. package/dist/application/rules/npm-workspace-dependency-rule.js.map +1 -0
  19. package/dist/application/rules/package-dependency-rule.d.ts +4 -3
  20. package/dist/application/rules/package-dependency-rule.d.ts.map +1 -1
  21. package/dist/application/rules/package-dependency-rule.js +33 -28
  22. package/dist/application/rules/package-dependency-rule.js.map +1 -1
  23. package/dist/application/services/analyze-import-impact.d.ts +3 -1
  24. package/dist/application/services/analyze-import-impact.d.ts.map +1 -1
  25. package/dist/application/services/analyze-import-impact.js +12 -7
  26. package/dist/application/services/analyze-import-impact.js.map +1 -1
  27. package/dist/infrastructure/git/git-file-snapshot-source.d.ts.map +1 -1
  28. package/dist/infrastructure/git/git-file-snapshot-source.js +4 -2
  29. package/dist/infrastructure/git/git-file-snapshot-source.js.map +1 -1
  30. package/dist/infrastructure/npm/npm-package-lock-parser.d.ts +6 -0
  31. package/dist/infrastructure/npm/npm-package-lock-parser.d.ts.map +1 -0
  32. package/dist/infrastructure/npm/npm-package-lock-parser.js +93 -0
  33. package/dist/infrastructure/npm/npm-package-lock-parser.js.map +1 -0
  34. package/dist/infrastructure/typescript/typescript-config-loader.d.ts +17 -0
  35. package/dist/infrastructure/typescript/typescript-config-loader.d.ts.map +1 -0
  36. package/dist/infrastructure/typescript/typescript-config-loader.js +99 -0
  37. package/dist/infrastructure/typescript/typescript-config-loader.js.map +1 -0
  38. package/dist/infrastructure/typescript/typescript-module-alias-resolver.d.ts +10 -0
  39. package/dist/infrastructure/typescript/typescript-module-alias-resolver.d.ts.map +1 -0
  40. package/dist/infrastructure/typescript/typescript-module-alias-resolver.js +78 -0
  41. package/dist/infrastructure/typescript/typescript-module-alias-resolver.js.map +1 -0
  42. package/dist/presentation/analyze-repository.d.ts.map +1 -1
  43. package/dist/presentation/analyze-repository.js +7 -3
  44. package/dist/presentation/analyze-repository.js.map +1 -1
  45. package/package.json +1 -1
package/README.ko.md CHANGED
@@ -21,9 +21,16 @@ LLM 없이도 재현 가능한 분석을 제공하고, AI 설명 기능은 선
21
21
 
22
22
  ## 빠른 시작
23
23
 
24
- Node.js 20 이상과 Git이 설치되어 있어야 합니다. 현재 npm 공개 전에는 저장소를 빌드해 바로
24
+ Node.js 20 이상과 Git이 설치되어 있어야 합니다. npm에 공개된 v0.2.0은 전역 설치 없이
25
25
  실행할 수 있습니다.
26
26
 
27
+ ```bash
28
+ npx intentpatch analyze --cwd /path/to/repository
29
+ npx intentpatch analyze --format html --output intentpatch-report.html
30
+ ```
31
+
32
+ 현재 소스 버전을 실행하려면 저장소를 빌드합니다.
33
+
27
34
  ```bash
28
35
  git clone https://github.com/xx2xxjaeil/intent-patch.git
29
36
  cd intent-patch
@@ -33,13 +40,6 @@ node dist/presentation/cli/main.js --version
33
40
  node dist/presentation/cli/main.js analyze --cwd /path/to/repository
34
41
  ```
35
42
 
36
- npm 릴리스가 공개된 뒤에는 설치 없이 같은 CLI를 실행할 수 있습니다.
37
-
38
- ```bash
39
- npx intentpatch analyze --cwd /path/to/repository
40
- npx intentpatch analyze --format html --output intentpatch-report.html
41
- ```
42
-
43
43
  핵심 분석에는 API key, 유료 AI 모델, 서버 또는 데이터베이스가 필요하지 않습니다. 분석할 Git
44
44
  저장소의 파일은 로컬에서 처리하며 LLM 연결은 현재 기본 실행 경로에 포함되지 않습니다.
45
45
 
@@ -67,7 +67,7 @@ jobs:
67
67
 
68
68
  - name: Analyze pull request
69
69
  id: intentpatch
70
- uses: xx2xxjaeil/intent-patch@main
70
+ uses: xx2xxjaeil/intent-patch@v0.2.0
71
71
  with:
72
72
  fail-on: high
73
73
 
@@ -79,9 +79,9 @@ jobs:
79
79
  path: intentpatch-report
80
80
  ```
81
81
 
82
- 현재 개발 버전은 `@main`으로 실행합니다. 첫 번째 정식 릴리스 후에는 불변 SHA 또는 `@v1`
83
- 태그로 고정하는 방식을 권장합니다. Action은 PR·push 이벤트의 commit 범위를 자동으로
84
- 선택하고 다음 결과를 남깁니다.
82
+ Action은 릴리스 버전이나 불변 commit SHA로 고정하는 방식을 권장합니다. v0.2.0에는 TypeScript
83
+ 경로 별칭과 npm lockfile/workspace 분석이 포함되며, `@main`은 미릴리스 변경을 따라갑니다.
84
+ Action은 PR·push 이벤트의 commit 범위를 자동으로 선택하고 다음 결과를 남깁니다.
85
85
 
86
86
  - GitHub Actions Job Summary의 Markdown 보고서
87
87
  - 심각도별 workflow annotation
@@ -129,7 +129,7 @@ LOW 구현체가 하나뿐인 추상화 추가
129
129
 
130
130
  ## 현재 구현된 기능
131
131
 
132
- 현재 버전은 Git 변경사항 수집, 루트 `package.json`의 직접 dependency 분석, TypeScript
132
+ 현재 소스는 Git 변경사항 수집, 루트·npm workspace `package.json`의 직접 dependency와 npm lockfile 분석, TypeScript
133
133
  최상위 심볼과 공개 API 변경 분석, 기존 구현 중복·단일 구현 추상화 신호, import graph 기반
134
134
  영향 범위와 테스트 동반 변경 분석을 제공합니다.
135
135
 
@@ -146,6 +146,11 @@ LOW 구현체가 하나뿐인 추상화 추가
146
146
  - production·development dependency 추가 탐지
147
147
  - dependency 삭제, 버전 변경, 섹션 이동 탐지
148
148
  - 잘못된 `package.json`을 예외 대신 근거가 포함된 finding으로 보고
149
+ - npm `package-lock.json` v2/v3의 실제 설치 패키지 추가·삭제·버전 변경 비교
150
+ - scoped·중첩 설치 경로를 구분하고 로컬 workspace 링크는 제외
151
+ - 잘못되었거나 미지원인 lockfile을 누락하지 않고 finding으로 보고
152
+ - 루트 `workspaces` 패턴에 속한 변경 manifest의 직접 dependency를 workspace별로 보고
153
+ - workspace 설정 변경과 manifest의 workspace 안팎 이동을 탐지
149
154
  - finding 심각도(`high`, `medium`, `low`) 집계
150
155
  - CI 품질 게이트를 위한 `--fail-on` 종료 코드
151
156
  - 설치된 패키지 버전을 확인하는 `--version` 명령
@@ -160,6 +165,8 @@ LOW 구현체가 하나뿐인 추상화 추가
160
165
  - rename 전후 파일 경로를 사용한 심볼 비교
161
166
  - 구문 오류가 있는 파일을 누락시키지 않고 분석 불가 근거로 보고
162
167
  - `.ts`·`.tsx` 파일의 상대 경로 정적 import와 re-export 관계 수집
168
+ - 가장 가까운 `tsconfig.json`·`jsconfig.json`의 `paths`·`baseUrl`과 상대 경로 `extends`를
169
+ 사용해 로컬 별칭 import 해석
163
170
  - `.js`·`.jsx`·`.mjs`·`.cjs` specifier를 대응하는 TypeScript 소스로 해석
164
171
  - 변경 모듈을 import하는 직접 의존자와 여러 단계를 거친 간접 영향 파일 계산
165
172
  - 해결하지 못한 상대 import와 읽기·파싱 실패를 분석 근거로 보존
@@ -174,7 +181,7 @@ LOW 구현체가 하나뿐인 추상화 추가
174
181
  - Pull Request·push commit 범위를 자동 판별하는 GitHub Action
175
182
  - GitHub Job Summary, workflow annotation, JSON·HTML artifact용 보고서 생성
176
183
 
177
- 아직 lockfile의 전이 dependency 분석, path alias 해석, JavaScript·메서드 구조 분석과 AI 리뷰
184
+ 아직 lockfile 전이 dependency의 부모 귀속 분석, JavaScript·메서드 구조 분석과 AI 리뷰
178
185
  기능은 구현되지 않았습니다.
179
186
 
180
187
  ## 상세 사용법
@@ -412,9 +419,13 @@ npm provenance가 자동 생성됩니다. 아직 npm에 존재하지 않는 신
412
419
  1. ✅ `package.json` 직접 dependency 변경 탐지와 규칙 엔진
413
420
  2. ✅ TypeScript AST 기반 함수·클래스·인터페이스·타입 변경 분석
414
421
  3. ✅ 상대 경로 정적 import graph 기반 변경 영향 범위 계산
422
+ - ✅ 가장 가까운 TypeScript 프로젝트 설정의 `paths`·`baseUrl` 별칭 해석
415
423
  4. ✅ Change Contract 기반 예상 범위 이탈과 변경량 예산 탐지
416
424
  5. ✅ Contract 기반 관련 테스트 변경 누락 탐지
417
425
  6. lockfile과 workspace를 고려한 package manager adapter
426
+ - ✅ npm `package-lock.json` v2/v3의 설치 패키지 변경
427
+ - ✅ npm workspace manifest의 직접 dependency 변경
428
+ - pnpm·Yarn adapter
418
429
  7. ✅ 직접 export된 공개 심볼 삭제와 export 해제 탐지
419
430
  8. ✅ 단일 HTML 대시보드와 SVG 기반 dependency 영향 그래프
420
431
  9. ✅ re-export·함수/인터페이스 호환성과 기존 코드 중복 가능성 탐지
@@ -428,9 +439,14 @@ npm provenance가 자동 생성됩니다. 아직 npm에 존재하지 않는 신
428
439
  - 분석 대상은 최소 한 번 이상 커밋된 Git 저장소여야 합니다.
429
440
  - untracked symbolic link는 안전을 위해 내용을 읽지 않습니다.
430
441
  - 10 MiB를 초과하는 untracked 파일은 라인 수를 측정하지 않습니다.
431
- - dependency 분석은 저장소 루트의 npm `package.json`에 선언된 `dependencies`와
432
- `devDependencies`를 대상으로 합니다.
433
- - lockfile의 전이 dependency, workspace package, 코드에서의 실제 사용 여부는 아직 분석하지 않습니다.
442
+ - 직접 dependency 선언 분석은 저장소 루트와 변경된 npm workspace `package.json`의
443
+ `dependencies`와 `devDependencies`를 대상으로 합니다.
444
+ - npm `package-lock.json` v2/v3은 설치 경로와 버전을 비교합니다. 부모 dependency 귀속,
445
+ integrity hash나 취약점 정보까지 분석하지는 않습니다.
446
+ - workspace 판별은 루트의 npm `workspaces` 배열과 명시 경로, `*`·`**`·`?` 패턴을 지원합니다.
447
+ 부정·중괄호·문자 클래스 패턴은 미지원 finding으로 보고합니다.
448
+ - pnpm·Yarn lockfile, 코드에서의 실제 dependency 사용 여부는 아직 분석하지 않습니다.
449
+ - working tree의 `package-lock.json`은 10 MiB, 그 외 텍스트 snapshot은 1 MiB로 제한합니다.
434
450
  - 심볼 분석은 `.ts`와 `.tsx`의 이름이 있는 최상위 함수, 클래스, 인터페이스, 타입
435
451
  별칭만 지원합니다.
436
452
  - 공개 API 분석은 이름이 있는 최상위 함수·인터페이스, 로컬 `export { name }`, 외부
@@ -445,10 +461,12 @@ npm provenance가 자동 생성됩니다. 아직 npm에 존재하지 않는 신
445
461
  보고합니다. TypeScript의 구조적 구현, factory 반환 타입과 런타임 등록은 계산하지 않습니다.
446
462
  - 메서드, 변수 선언, enum, 중첩 선언, JavaScript 파일은 아직 심볼 분석 대상이 아닙니다.
447
463
  - 선언 내부의 포맷이나 주석 변경도 심볼 수정으로 집계될 수 있습니다.
448
- - 영향 분석은 `.ts`·`.tsx` 파일의 상대 경로 정적 `import`, side-effect import,
449
- `export ... from`, `import = require()`를 대상으로 합니다.
450
- - 외부 package import는 그래프에서 제외하며 path alias, dynamic `import()`, 일반 `require()`는
451
- 아직 해석하지 않습니다.
464
+ - 영향 분석은 `.ts`·`.tsx` 파일의 정적 `import`, side-effect import, `export ... from`,
465
+ `import = require()`를 대상으로 합니다. 로컬 별칭은 가장 가까운 `tsconfig.json` 또는
466
+ `jsconfig.json`과 상대 경로 `extends`를 해석합니다.
467
+ - 외부 패키지 설정을 참조하는 `extends`, project references, 임의 이름의 설정 파일,
468
+ package `imports`·`exports`, 외부 package import, dynamic `import()`, 일반 `require()`는
469
+ 아직 그래프에 포함하지 않습니다.
452
470
  - 영향 그래프는 비교 결과점(working tree 또는 head ref)의 파일을 기준으로 만듭니다. 따라서
453
471
  삭제된 모듈을 가리키던 과거 import의 영향은 현재 단계에서 계산할 수 없습니다.
454
472
  - 영향 분석용 소스 파일은 파일당 1 MiB로 제한하며, symbolic link는 읽지 않습니다.
package/README.md CHANGED
@@ -18,7 +18,15 @@ IntentPatch combines **Git diff analysis, static analysis, and dependency analys
18
18
 
19
19
  ## Quick start
20
20
 
21
- IntentPatch requires Node.js 20 or later and Git. Until the first public npm release is approved, build and run it directly from the repository:
21
+ IntentPatch requires Node.js 20 or later and Git. Run the published v0.2.0 CLI without
22
+ installing it globally:
23
+
24
+ ```bash
25
+ npx intentpatch analyze --cwd /path/to/repository
26
+ npx intentpatch analyze --format html --output intentpatch-report.html
27
+ ```
28
+
29
+ To run the current source version instead, build it from the repository:
22
30
 
23
31
  ```bash
24
32
  git clone https://github.com/xx2xxjaeil/intent-patch.git
@@ -29,13 +37,6 @@ node dist/presentation/cli/main.js --version
29
37
  node dist/presentation/cli/main.js analyze --cwd /path/to/repository
30
38
  ```
31
39
 
32
- After the npm release becomes public, run the same CLI without installing it globally:
33
-
34
- ```bash
35
- npx intentpatch analyze --cwd /path/to/repository
36
- npx intentpatch analyze --format html --output intentpatch-report.html
37
- ```
38
-
39
40
  The core analyzer needs no API key, paid AI model, server, or database. Repository files are processed locally, and no LLM is used in the default execution path.
40
41
 
41
42
  ## GitHub Action
@@ -61,7 +62,7 @@ jobs:
61
62
 
62
63
  - name: Analyze pull request
63
64
  id: intentpatch
64
- uses: xx2xxjaeil/intent-patch@main
65
+ uses: xx2xxjaeil/intent-patch@v0.2.0
65
66
  with:
66
67
  fail-on: high
67
68
 
@@ -73,7 +74,8 @@ jobs:
73
74
  path: intentpatch-report
74
75
  ```
75
76
 
76
- The development version currently runs from `@main`. After the first stable release, pin the action to an immutable commit SHA or a major tag such as `@v1`.
77
+ Pin the action to a released version or an immutable commit SHA. The v0.2.0 release includes
78
+ TypeScript path aliases and npm lockfile/workspace analysis; `@main` follows unreleased changes.
77
79
 
78
80
  The action detects the comparison range for pull request and push events and produces:
79
81
 
@@ -121,7 +123,7 @@ Every finding includes the relevant file, a stable rule ID, and evidence for the
121
123
 
122
124
  ## Implemented capabilities
123
125
 
124
- The current version analyzes Git changes, direct dependencies in the root `package.json`, top-level TypeScript symbols and public API changes, possible duplicate implementations, single-implementation abstractions, import-graph impact, and related test changes.
126
+ The current source tree analyzes Git changes, direct dependencies in the root `package.json`, top-level TypeScript symbols and public API changes, possible duplicate implementations, single-implementation abstractions, import-graph impact, and related test changes.
125
127
 
126
128
  ### Git and reporting
127
129
 
@@ -143,6 +145,11 @@ The current version analyzes Git changes, direct dependencies in the root `packa
143
145
  - Detect additions to production and development dependencies.
144
146
  - Detect dependency removal, version changes, and section moves.
145
147
  - Report malformed `package.json` files as evidence-backed findings instead of crashing.
148
+ - Compare resolved package additions, removals, and version changes in npm `package-lock.json` v2/v3.
149
+ - Keep scoped and nested installation paths distinct, and exclude local workspace links.
150
+ - Report malformed or unsupported lockfiles as findings rather than silently skipping them.
151
+ - Attribute direct dependency changes to changed npm workspace manifests selected by the root `workspaces` patterns.
152
+ - Report changes to workspace patterns and manifest moves into or out of a configured workspace.
146
153
  - Extract top-level named functions, classes, interfaces, and type aliases from `.ts` and `.tsx` files.
147
154
  - Detect symbol additions, modifications, and removals with source locations.
148
155
  - Preserve direct exports, local export lists, and external re-exports as public-symbol evidence.
@@ -157,6 +164,8 @@ The current version analyzes Git changes, direct dependencies in the root `packa
157
164
  ### Impact, scope, and tests
158
165
 
159
166
  - Build relative static import and re-export relationships for `.ts` and `.tsx` files.
167
+ - Resolve local imports through the nearest `tsconfig.json` or `jsconfig.json` `paths` and
168
+ `baseUrl`, including relative `extends` files.
160
169
  - Resolve `.js`, `.jsx`, `.mjs`, and `.cjs` import specifiers to matching TypeScript sources.
161
170
  - Calculate direct importers and transitively impacted files for changed modules.
162
171
  - Preserve unresolved relative imports and file read or parse failures as evidence.
@@ -169,7 +178,7 @@ The current version analyzes Git changes, direct dependencies in the root `packa
169
178
  - Report source files without a related test change as medium-severity findings.
170
179
  - Support repository-relative `*`, `**`, and `?` path patterns.
171
180
 
172
- Lockfile transitive dependency analysis, path aliases, JavaScript and method-level structure analysis, and optional AI review are not implemented yet.
181
+ Transitive lockfile dependency provenance, JavaScript and method-level structure analysis, and optional AI review are not implemented yet.
173
182
 
174
183
  ## CLI usage
175
184
 
@@ -396,9 +405,13 @@ No long-lived npm token is stored as a GitHub secret. Public packages published
396
405
  1. ✅ Direct `package.json` dependency detection and rule engine
397
406
  2. ✅ TypeScript AST analysis for functions, classes, interfaces, and types
398
407
  3. ✅ Relative static import graph and change-impact calculation
408
+ - ✅ TypeScript `paths` and `baseUrl` aliases from the nearest project config
399
409
  4. ✅ Change Contract scope and change-budget analysis
400
410
  5. ✅ Contract-based detection of missing related test changes
401
411
  6. Package-manager adapters for lockfiles and workspaces
412
+ - ✅ npm `package-lock.json` v2/v3 resolved package changes
413
+ - ✅ npm workspace manifest dependency changes
414
+ - pnpm and Yarn adapters
402
415
  7. ✅ Detection of removed public exports
403
416
  8. ✅ Standalone HTML dashboard and SVG dependency-impact graph
404
417
  9. ✅ Re-export and function/interface compatibility checks plus possible code duplication
@@ -412,8 +425,11 @@ No long-lived npm token is stored as a GitHub secret. Public packages published
412
425
  - The target must be a Git repository with at least one commit.
413
426
  - Untracked symbolic links are not read for safety.
414
427
  - Line counts are skipped for untracked files larger than 10 MiB.
415
- - Dependency analysis covers only `dependencies` and `devDependencies` in the root npm `package.json`.
416
- - Transitive lockfile dependencies, workspace packages, and real dependency usage in source code are not analyzed yet.
428
+ - Direct dependency declarations are analyzed from `dependencies` and `devDependencies` in the root npm `package.json` and changed npm workspace manifests.
429
+ - npm `package-lock.json` v2/v3 analysis compares installed package paths and versions, not dependency provenance, integrity hashes, or vulnerability data. It does not attribute transitive packages to their parents.
430
+ - Workspace detection supports the root npm `workspaces` array with explicit paths and `*`, `**`, and `?` globs. Negation, brace, and character-class patterns are reported as unsupported.
431
+ - pnpm and Yarn lockfiles and real dependency usage in source code are not analyzed yet.
432
+ - Working-tree `package-lock.json` files are limited to 10 MiB; other text snapshots retain the 1 MiB limit.
417
433
  - Symbol analysis supports named top-level functions, classes, interfaces, and type aliases in `.ts` and `.tsx` files.
418
434
  - Public API analysis supports named top-level functions and interfaces, local `export { name }`, external `export { name } from`, and `export * from`. Package `exports`, anonymous default exports, and detailed class or type-alias contracts are not interpreted yet.
419
435
  - Function compatibility compares explicitly written parameter and return-type text. It does not evaluate inferred return types or full TypeScript structural assignability.
@@ -421,8 +437,12 @@ No long-lived npm token is stored as a GitHub secret. Public packages published
421
437
  - A single-implementation abstraction is reported only when exactly one named class explicitly uses `implements` for a new interface. Structural implementation, factory return types, and runtime registration are not calculated.
422
438
  - Methods, variable declarations, enums, nested declarations, and JavaScript files are not included in symbol analysis.
423
439
  - Formatting or comment changes inside a declaration can be counted as symbol modifications.
424
- - Impact analysis covers relative static `import`, side-effect imports, `export ... from`, and `import = require()` in `.ts` and `.tsx` files.
425
- - External package imports, path aliases, dynamic `import()`, and ordinary `require()` are not included in the graph.
440
+ - Impact analysis covers static `import`, side-effect imports, `export ... from`, and
441
+ `import = require()` in `.ts` and `.tsx` files. Local aliases use the nearest `tsconfig.json` or
442
+ `jsconfig.json`, with relative `extends` support.
443
+ - Package-based config `extends`, project references, arbitrary config filenames, package
444
+ `imports`/`exports`, external package imports, dynamic `import()`, and ordinary `require()` are
445
+ not included in the graph.
426
446
  - The graph is built from files at the comparison result point: the working tree or head ref. Impact from historical imports that referenced a deleted module cannot be calculated yet.
427
447
  - Source files used for impact analysis are limited to 1 MiB per file, and symbolic links are not read.
428
448
  - IntentPatch does not infer expected paths from natural-language intent. Scope decisions use the contract's explicit `include` and `allow` patterns.
@@ -0,0 +1,11 @@
1
+ import type { ImpactAnalysisIssue } from "../../domain/impact.js";
2
+ import type { AnalysisTarget } from "../../domain/report.js";
3
+ /** 분석 결과점에서 해석 가능한 비상대 경로 import를 찾는 언어별 출력 포트다. */
4
+ export interface ModuleAliasResolver {
5
+ prepare(target: AnalysisTarget, repositoryPaths: readonly string[], sourcePaths: ReadonlySet<string>): Promise<PreparedModuleAliasResolver>;
6
+ }
7
+ export interface PreparedModuleAliasResolver {
8
+ readonly issues: readonly ImpactAnalysisIssue[];
9
+ resolve(importer: string, specifier: string): string | undefined;
10
+ }
11
+ //# sourceMappingURL=module-alias-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-alias-resolver.d.ts","sourceRoot":"","sources":["../../../src/application/ports/module-alias-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAClC,OAAO,CACL,MAAM,EAAE,cAAc,EACtB,eAAe,EAAE,SAAS,MAAM,EAAE,EAClC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,GAC/B,OAAO,CAAC,2BAA2B,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,MAAM,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAChD,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CAClE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=module-alias-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-alias-resolver.js","sourceRoot":"","sources":["../../../src/application/ports/module-alias-resolver.ts"],"names":[],"mappings":""}
@@ -0,0 +1,20 @@
1
+ /** npm lockfile의 설치 위치를 기준으로 비교할 수 있는 패키지 정보다. */
2
+ export interface LockedPackage {
3
+ readonly path: string;
4
+ readonly name: string;
5
+ readonly version: string;
6
+ readonly rootDirect: boolean;
7
+ }
8
+ export type PackageLockParseResult = Readonly<{
9
+ ok: true;
10
+ packages: ReadonlyMap<string, LockedPackage>;
11
+ }> | Readonly<{
12
+ ok: false;
13
+ kind: "invalid" | "unsupported";
14
+ reason: string;
15
+ }>;
16
+ /** 패키지 매니저의 파일 형식을 분석 규칙에서 분리하는 입력 포트다. */
17
+ export interface PackageLockParser {
18
+ parse(content: string): PackageLockParseResult;
19
+ }
20
+ //# sourceMappingURL=package-lock-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-lock-parser.d.ts","sourceRoot":"","sources":["../../../src/application/ports/package-lock-parser.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,MAAM,sBAAsB,GAC9B,QAAQ,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;CAAE,CAAC,GACpE,QAAQ,CAAC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,SAAS,GAAG,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE7E,2CAA2C;AAC3C,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,sBAAsB,CAAC;CAChD"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=package-lock-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-lock-parser.js","sourceRoot":"","sources":["../../../src/application/ports/package-lock-parser.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ import type { Finding } from "../../domain/finding.js";
2
+ import type { AnalysisContext, AnalysisRule } from "../ports/analysis-rule.js";
3
+ import type { FileSnapshotSource } from "../ports/file-snapshot-source.js";
4
+ import type { PackageLockParser } from "../ports/package-lock-parser.js";
5
+ /** 선언 버전이 아닌, npm이 실제로 고정한 패키지 버전과 설치 위치의 변화를 분석한다. */
6
+ export declare class NpmLockfileRule implements AnalysisRule {
7
+ private readonly snapshots;
8
+ private readonly parser;
9
+ constructor(snapshots: FileSnapshotSource, parser: PackageLockParser);
10
+ analyze(context: AnalysisContext): Promise<readonly Finding[]>;
11
+ }
12
+ //# sourceMappingURL=npm-lockfile-rule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npm-lockfile-rule.d.ts","sourceRoot":"","sources":["../../../src/application/rules/npm-lockfile-rule.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,KAAK,EAAiB,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAIxF,uDAAuD;AACvD,qBAAa,eAAgB,YAAW,YAAY;IAEhD,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,SAAS,EAAE,kBAAkB,EAC7B,MAAM,EAAE,iBAAiB;IAG/B,OAAO,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,OAAO,EAAE,CAAC;CAiC5E"}
@@ -0,0 +1,109 @@
1
+ const lockfilePath = "package-lock.json";
2
+ /** 선언 버전이 아닌, npm이 실제로 고정한 패키지 버전과 설치 위치의 변화를 분석한다. */
3
+ export class NpmLockfileRule {
4
+ snapshots;
5
+ parser;
6
+ constructor(snapshots, parser) {
7
+ this.snapshots = snapshots;
8
+ this.parser = parser;
9
+ }
10
+ async analyze(context) {
11
+ if (!context.changes.files.some((change) => change.path === lockfilePath || change.previousPath === lockfilePath)) {
12
+ return [];
13
+ }
14
+ const snapshot = await this.snapshots.read(context.target, lockfilePath);
15
+ const before = snapshot.before === undefined ? undefined : this.parser.parse(snapshot.before);
16
+ const after = snapshot.after === undefined ? undefined : this.parser.parse(snapshot.after);
17
+ const errors = [
18
+ ...(before !== undefined && !before.ok ? [parseFinding("base", before)] : []),
19
+ ...(after !== undefined && !after.ok ? [parseFinding("current", after)] : []),
20
+ ];
21
+ if (errors.length > 0) {
22
+ return errors;
23
+ }
24
+ if (before === undefined && after === undefined) {
25
+ return [];
26
+ }
27
+ if (before === undefined && after?.ok) {
28
+ return [lockfilePresenceFinding("added", after.packages.size)];
29
+ }
30
+ if (after === undefined && before?.ok) {
31
+ return [lockfilePresenceFinding("removed", before.packages.size)];
32
+ }
33
+ if (before?.ok && after?.ok) {
34
+ return comparePackages(before.packages, after.packages);
35
+ }
36
+ return [];
37
+ }
38
+ }
39
+ function comparePackages(before, after) {
40
+ const paths = [...new Set([...before.keys(), ...after.keys()])].sort();
41
+ const findings = [];
42
+ for (const path of paths) {
43
+ const previous = before.get(path);
44
+ const current = after.get(path);
45
+ if (previous === undefined && current !== undefined) {
46
+ findings.push({
47
+ ruleId: "dependency/locked-package-added",
48
+ severity: "low",
49
+ title: "Resolved package added",
50
+ description: `${current.name}@${current.version} was added at ${path}.`,
51
+ file: lockfilePath,
52
+ evidence: packageEvidence(current),
53
+ });
54
+ }
55
+ else if (previous !== undefined && current === undefined) {
56
+ findings.push({
57
+ ruleId: "dependency/locked-package-removed",
58
+ severity: "low",
59
+ title: "Resolved package removed",
60
+ description: `${previous.name}@${previous.version} was removed from ${path}.`,
61
+ file: lockfilePath,
62
+ evidence: packageEvidence(previous),
63
+ });
64
+ }
65
+ else if (previous !== undefined &&
66
+ current !== undefined &&
67
+ previous.version !== current.version) {
68
+ findings.push({
69
+ ruleId: "dependency/locked-version-changed",
70
+ severity: "low",
71
+ title: "Resolved package version changed",
72
+ description: `${current.name} at ${path} changed from ${previous.version} to ${current.version}.`,
73
+ file: lockfilePath,
74
+ evidence: { ...packageEvidence(current), from: previous.version, to: current.version },
75
+ });
76
+ }
77
+ }
78
+ return findings;
79
+ }
80
+ function packageEvidence(item) {
81
+ return {
82
+ package: item.name,
83
+ version: item.version,
84
+ installPath: item.path,
85
+ rootDirect: item.rootDirect,
86
+ };
87
+ }
88
+ function parseFinding(side, result) {
89
+ const unsupported = result.kind === "unsupported";
90
+ return {
91
+ ruleId: unsupported ? "dependency/unsupported-lockfile" : "dependency/invalid-lockfile",
92
+ severity: unsupported ? "low" : "high",
93
+ title: unsupported ? "Unsupported npm lockfile" : "Invalid npm lockfile",
94
+ description: `Could not analyze the ${side} package-lock.json: ${result.reason}`,
95
+ file: lockfilePath,
96
+ evidence: { side, reason: result.reason },
97
+ };
98
+ }
99
+ function lockfilePresenceFinding(kind, packageCount) {
100
+ return {
101
+ ruleId: `dependency/lockfile-${kind}`,
102
+ severity: "low",
103
+ title: kind === "added" ? "npm lockfile added" : "npm lockfile removed",
104
+ description: `package-lock.json was ${kind} with ${packageCount} resolved packages.`,
105
+ file: lockfilePath,
106
+ evidence: { packageCount },
107
+ };
108
+ }
109
+ //# sourceMappingURL=npm-lockfile-rule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npm-lockfile-rule.js","sourceRoot":"","sources":["../../../src/application/rules/npm-lockfile-rule.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { Finding } from "../../domain/finding.js";
2
+ import type { AnalysisContext, AnalysisRule } from "../ports/analysis-rule.js";
3
+ import type { FileSnapshotSource } from "../ports/file-snapshot-source.js";
4
+ /** npm workspaces에 속한 변경 manifest를 찾아 기존 직접 dependency 규칙으로 분석한다. */
5
+ export declare class NpmWorkspaceDependencyRule implements AnalysisRule {
6
+ private readonly snapshots;
7
+ constructor(snapshots: FileSnapshotSource);
8
+ analyze(context: AnalysisContext): Promise<readonly Finding[]>;
9
+ }
10
+ //# sourceMappingURL=npm-workspace-dependency-rule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npm-workspace-dependency-rule.d.ts","sourceRoot":"","sources":["../../../src/application/rules/npm-workspace-dependency-rule.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAW3E,qEAAqE;AACrE,qBAAa,0BAA2B,YAAW,YAAY;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,kBAAkB;IAEpD,OAAO,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,OAAO,EAAE,CAAC;CAwF5E"}
@@ -0,0 +1,155 @@
1
+ import { matchesPathPattern } from "../services/path-pattern.js";
2
+ import { PackageDependencyRule } from "./package-dependency-rule.js";
3
+ const rootManifestPath = "package.json";
4
+ const manifestSuffix = "/package.json";
5
+ /** npm workspaces에 속한 변경 manifest를 찾아 기존 직접 dependency 규칙으로 분석한다. */
6
+ export class NpmWorkspaceDependencyRule {
7
+ snapshots;
8
+ constructor(snapshots) {
9
+ this.snapshots = snapshots;
10
+ }
11
+ async analyze(context) {
12
+ const manifestChanges = context.changes.files.filter(isNestedManifestChange);
13
+ const rootChanged = context.changes.files.some((change) => change.path === rootManifestPath || change.previousPath === rootManifestPath);
14
+ if (manifestChanges.length === 0 && !rootChanged) {
15
+ return [];
16
+ }
17
+ const snapshot = await this.snapshots.read(context.target, rootManifestPath);
18
+ const before = parseWorkspaceConfig(snapshot.before);
19
+ const after = parseWorkspaceConfig(snapshot.after);
20
+ const errors = [
21
+ ...(!before.ok ? [configFinding("base", before)] : []),
22
+ ...(!after.ok ? [configFinding("current", after)] : []),
23
+ ];
24
+ if (errors.length > 0) {
25
+ return errors;
26
+ }
27
+ if (!before.ok || !after.ok) {
28
+ return [];
29
+ }
30
+ const findings = [];
31
+ if (rootChanged && JSON.stringify(before.patterns) !== JSON.stringify(after.patterns)) {
32
+ findings.push({
33
+ ruleId: "dependency/workspace-config-changed",
34
+ severity: "low",
35
+ title: "npm workspace configuration changed",
36
+ description: "The root package.json workspace patterns changed.",
37
+ file: rootManifestPath,
38
+ evidence: {
39
+ from: JSON.stringify(before.patterns),
40
+ to: JSON.stringify(after.patterns),
41
+ },
42
+ });
43
+ }
44
+ const results = await Promise.all(manifestChanges.map(async (change) => {
45
+ const previousPath = change.kind === "renamed" ? (change.previousPath ?? change.path) : change.path;
46
+ const currentPath = change.path;
47
+ const wasWorkspace = change.kind !== "added" &&
48
+ change.kind !== "copied" &&
49
+ isConfiguredWorkspace(previousPath, before.patterns);
50
+ const isWorkspace = change.kind !== "deleted" && isConfiguredWorkspace(currentPath, after.patterns);
51
+ if (!wasWorkspace && !isWorkspace) {
52
+ return [];
53
+ }
54
+ const manifestPath = isNestedManifestPath(currentPath) ? currentPath : previousPath;
55
+ const workspacePath = manifestPath.slice(0, -manifestSuffix.length);
56
+ const membershipFinding = change.kind === "renamed" && wasWorkspace !== isWorkspace
57
+ ? [
58
+ {
59
+ ruleId: "dependency/workspace-membership-changed",
60
+ severity: "low",
61
+ title: "npm workspace membership changed",
62
+ description: `${previousPath} moved to ${currentPath}, changing workspace membership.`,
63
+ file: currentPath,
64
+ evidence: {
65
+ previousPath,
66
+ currentPath,
67
+ fromWorkspace: wasWorkspace,
68
+ toWorkspace: isWorkspace,
69
+ },
70
+ },
71
+ ]
72
+ : [];
73
+ const dependencyFindings = await new PackageDependencyRule(this.snapshots, manifestPath).analyze(context);
74
+ return [
75
+ ...membershipFinding,
76
+ ...dependencyFindings.map((finding) => ({
77
+ ...finding,
78
+ evidence: { ...finding.evidence, workspace: workspacePath },
79
+ })),
80
+ ];
81
+ }));
82
+ return [...findings, ...results.flat()];
83
+ }
84
+ }
85
+ function isNestedManifestChange(change) {
86
+ return isNestedManifestPath(change.path) || isNestedManifestPath(change.previousPath);
87
+ }
88
+ function isNestedManifestPath(path) {
89
+ return path?.endsWith(manifestSuffix) === true;
90
+ }
91
+ function isConfiguredWorkspace(path, patterns) {
92
+ if (!isNestedManifestPath(path)) {
93
+ return false;
94
+ }
95
+ const directory = path.slice(0, -manifestSuffix.length);
96
+ return patterns.some((pattern) => matchesPathPattern(directory, pattern));
97
+ }
98
+ function parseWorkspaceConfig(content) {
99
+ if (content === undefined) {
100
+ return { ok: true, patterns: [] };
101
+ }
102
+ let manifest;
103
+ try {
104
+ manifest = JSON.parse(content);
105
+ }
106
+ catch {
107
+ return { ok: false, kind: "invalid", reason: "The root package.json is not valid JSON." };
108
+ }
109
+ if (!isRecord(manifest)) {
110
+ return { ok: false, kind: "invalid", reason: "The root package.json must be an object." };
111
+ }
112
+ if (manifest.workspaces === undefined) {
113
+ return { ok: true, patterns: [] };
114
+ }
115
+ if (!Array.isArray(manifest.workspaces)) {
116
+ return { ok: false, kind: "invalid", reason: "workspaces must be an array of paths." };
117
+ }
118
+ const patterns = [];
119
+ for (const rawPattern of manifest.workspaces) {
120
+ if (typeof rawPattern !== "string" || rawPattern.trim().length === 0) {
121
+ return { ok: false, kind: "invalid", reason: "Each workspace path must be a string." };
122
+ }
123
+ const pattern = rawPattern.replace(/^(\.\/)+/, "").replace(/\/+$/, "");
124
+ if (pattern.length === 0 || pattern.startsWith("/") || pattern.split("/").includes("..")) {
125
+ return { ok: false, kind: "invalid", reason: `Invalid workspace path: ${rawPattern}.` };
126
+ }
127
+ if (["!", "{", "}", "[", "]", "\\"].some((character) => pattern.includes(character))) {
128
+ return {
129
+ ok: false,
130
+ kind: "unsupported",
131
+ reason: `Unsupported workspace pattern: ${rawPattern}. Only *, **, and ? globs are supported.`,
132
+ };
133
+ }
134
+ patterns.push(pattern);
135
+ }
136
+ return { ok: true, patterns };
137
+ }
138
+ function configFinding(side, result) {
139
+ return {
140
+ ruleId: result.kind === "invalid"
141
+ ? "dependency/invalid-workspace-config"
142
+ : "dependency/unsupported-workspace-pattern",
143
+ severity: result.kind === "invalid" ? "high" : "low",
144
+ title: result.kind === "invalid"
145
+ ? "Invalid npm workspace configuration"
146
+ : "Unsupported npm workspace pattern",
147
+ description: `Could not analyze the ${side} npm workspaces: ${result.reason}`,
148
+ file: rootManifestPath,
149
+ evidence: { side, reason: result.reason },
150
+ };
151
+ }
152
+ function isRecord(value) {
153
+ return typeof value === "object" && value !== null && !Array.isArray(value);
154
+ }
155
+ //# sourceMappingURL=npm-workspace-dependency-rule.js.map