vibe-commander 0.1.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.
- package/LICENSE +21 -0
- package/dist/adapters/cli/commands/context-resolver.d.ts +29 -0
- package/dist/adapters/cli/commands/context-resolver.js +93 -0
- package/dist/adapters/cli/commands/context.d.ts +26 -0
- package/dist/adapters/cli/commands/context.js +39 -0
- package/dist/adapters/cli/commands/git-helpers.d.ts +43 -0
- package/dist/adapters/cli/commands/git-helpers.js +99 -0
- package/dist/adapters/cli/commands/init-helpers.d.ts +30 -0
- package/dist/adapters/cli/commands/init-helpers.js +112 -0
- package/dist/adapters/cli/commands/init-interactive.d.ts +25 -0
- package/dist/adapters/cli/commands/init-interactive.js +143 -0
- package/dist/adapters/cli/commands/init.d.ts +25 -0
- package/dist/adapters/cli/commands/init.js +73 -0
- package/dist/adapters/cli/commands/io-helpers.d.ts +45 -0
- package/dist/adapters/cli/commands/io-helpers.js +144 -0
- package/dist/adapters/cli/commands/list-units.d.ts +39 -0
- package/dist/adapters/cli/commands/list-units.js +92 -0
- package/dist/adapters/cli/commands/prompt-helpers.d.ts +36 -0
- package/dist/adapters/cli/commands/prompt-helpers.js +113 -0
- package/dist/adapters/cli/commands/set-unit.d.ts +44 -0
- package/dist/adapters/cli/commands/set-unit.js +90 -0
- package/dist/adapters/cli/commands/skill-install.d.ts +31 -0
- package/dist/adapters/cli/commands/skill-install.js +85 -0
- package/dist/adapters/cli/commands/state-helpers.d.ts +28 -0
- package/dist/adapters/cli/commands/state-helpers.js +55 -0
- package/dist/adapters/cli/commands/update-commit.d.ts +42 -0
- package/dist/adapters/cli/commands/update-commit.js +180 -0
- package/dist/adapters/cli/commands/validate.d.ts +40 -0
- package/dist/adapters/cli/commands/validate.js +219 -0
- package/dist/adapters/cli/formatters-list.d.ts +36 -0
- package/dist/adapters/cli/formatters-list.js +106 -0
- package/dist/adapters/cli/formatters-unit.d.ts +44 -0
- package/dist/adapters/cli/formatters-unit.js +287 -0
- package/dist/adapters/cli/formatters.d.ts +2 -0
- package/dist/adapters/cli/formatters.js +3 -0
- package/dist/adapters/cli/index.d.ts +17 -0
- package/dist/adapters/cli/index.js +60 -0
- package/dist/adapters/cli/output.d.ts +58 -0
- package/dist/adapters/cli/output.js +159 -0
- package/dist/adapters/cli/router.d.ts +99 -0
- package/dist/adapters/cli/router.js +419 -0
- package/dist/adapters/cli/scanner.d.ts +19 -0
- package/dist/adapters/cli/scanner.js +227 -0
- package/dist/adapters/pipe/stdin-reader.d.ts +23 -0
- package/dist/adapters/pipe/stdin-reader.js +56 -0
- package/dist/config/loader.d.ts +27 -0
- package/dist/config/loader.js +84 -0
- package/dist/config/resolver.d.ts +71 -0
- package/dist/config/resolver.js +124 -0
- package/dist/config/schema.d.ts +205 -0
- package/dist/config/schema.js +186 -0
- package/dist/core/parsers/backlog-parser.d.ts +25 -0
- package/dist/core/parsers/backlog-parser.js +255 -0
- package/dist/core/parsers/dep-line-parser.d.ts +36 -0
- package/dist/core/parsers/dep-line-parser.js +176 -0
- package/dist/core/parsers/dependency-extractor.d.ts +64 -0
- package/dist/core/parsers/dependency-extractor.js +193 -0
- package/dist/core/parsers/index.d.ts +20 -0
- package/dist/core/parsers/index.js +20 -0
- package/dist/core/parsers/md-utils.d.ts +81 -0
- package/dist/core/parsers/md-utils.js +144 -0
- package/dist/core/parsers/metadata-parser.d.ts +49 -0
- package/dist/core/parsers/metadata-parser.js +133 -0
- package/dist/core/parsers/pairing-question-extractor.d.ts +42 -0
- package/dist/core/parsers/pairing-question-extractor.js +146 -0
- package/dist/core/parsers/plan-parser-helpers.d.ts +61 -0
- package/dist/core/parsers/plan-parser-helpers.js +90 -0
- package/dist/core/parsers/plan-parser.d.ts +55 -0
- package/dist/core/parsers/plan-parser.js +69 -0
- package/dist/core/parsers/title-extractor.d.ts +36 -0
- package/dist/core/parsers/title-extractor.js +101 -0
- package/dist/core/renderers/index.d.ts +15 -0
- package/dist/core/renderers/index.js +18 -0
- package/dist/core/renderers/interpolate.d.ts +92 -0
- package/dist/core/renderers/interpolate.js +117 -0
- package/dist/core/renderers/marker-utils.d.ts +60 -0
- package/dist/core/renderers/marker-utils.js +85 -0
- package/dist/core/renderers/section-renderer.d.ts +31 -0
- package/dist/core/renderers/section-renderer.js +171 -0
- package/dist/core/renderers/section-updater.d.ts +72 -0
- package/dist/core/renderers/section-updater.js +175 -0
- package/dist/core/renderers/template-engine.d.ts +69 -0
- package/dist/core/renderers/template-engine.js +92 -0
- package/dist/core/renderers/updater-helpers.d.ts +45 -0
- package/dist/core/renderers/updater-helpers.js +83 -0
- package/dist/core/resolvers/commit-filter.d.ts +84 -0
- package/dist/core/resolvers/commit-filter.js +95 -0
- package/dist/core/resolvers/config-generator.d.ts +32 -0
- package/dist/core/resolvers/config-generator.js +112 -0
- package/dist/core/resolvers/config-merger.d.ts +26 -0
- package/dist/core/resolvers/config-merger.js +89 -0
- package/dist/core/resolvers/config-validator.d.ts +42 -0
- package/dist/core/resolvers/config-validator.js +61 -0
- package/dist/core/resolvers/dep-commit-resolver.d.ts +63 -0
- package/dist/core/resolvers/dep-commit-resolver.js +158 -0
- package/dist/core/resolvers/dep-doc-resolver.d.ts +70 -0
- package/dist/core/resolvers/dep-doc-resolver.js +84 -0
- package/dist/core/resolvers/index.d.ts +15 -0
- package/dist/core/resolvers/index.js +12 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/types/config.d.ts +10 -0
- package/dist/types/config.js +10 -0
- package/dist/types/context.d.ts +55 -0
- package/dist/types/context.js +10 -0
- package/dist/types/index.d.ts +15 -0
- package/dist/types/index.js +10 -0
- package/dist/types/init.d.ts +56 -0
- package/dist/types/init.js +10 -0
- package/dist/types/tool-result.d.ts +75 -0
- package/dist/types/tool-result.js +40 -0
- package/dist/types/unit.d.ts +118 -0
- package/dist/types/unit.js +10 -0
- package/package.json +71 -0
- package/skills/claude/SKILL.md +375 -0
- package/skills/common/cli-reference.md +251 -0
- package/skills/cursor/SKILL.md +353 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod 4 스키마 정의 — vibe-commander.config.json
|
|
3
|
+
*
|
|
4
|
+
* PRD §5 설정 파일 구조를 Zod 4 스키마로 모델링.
|
|
5
|
+
* 각 서브 스키마는 독립적으로 정의하여 재사용 및 테스트 용이성 확보.
|
|
6
|
+
* 페어링 질문 Q2(Option B) 결정에 따라 snapmit 스타일 기본값 제공.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
// ── 경로 관련 ──
|
|
12
|
+
/** 기본 경로 설정 스키마 */
|
|
13
|
+
export const pathsSchema = z.object({
|
|
14
|
+
/** 커맨드 파일 경로 (프로젝트 루트 기준) */
|
|
15
|
+
commands: z.string().default('vibe/commands.md'),
|
|
16
|
+
/** 로드맵 파일 경로. null이면 list-units 비활성화 */
|
|
17
|
+
roadmap: z.string().nullable().default('vibe/roadmap.md'),
|
|
18
|
+
/** 역할별 문서 루트 디렉토리 (키: 역할명, 값: 상대 경로) */
|
|
19
|
+
docRoots: z.record(z.string(), z.string()).default({
|
|
20
|
+
plan: 'vibe/unit-plans',
|
|
21
|
+
result: 'vibe/unit-results',
|
|
22
|
+
runbook: 'vibe/unit-runbooks',
|
|
23
|
+
}),
|
|
24
|
+
/** 리팩토링 서브유닛 디렉토리. null이면 서브유닛 미사용 */
|
|
25
|
+
refactorSubDir: z.string().nullable().default(null),
|
|
26
|
+
});
|
|
27
|
+
// ── 유닛 유형 ──
|
|
28
|
+
/** 유닛 유형 정의 스키마 */
|
|
29
|
+
export const unitTypeSchema = z.object({
|
|
30
|
+
/** 유형 키 (예: "implement", "refactor") */
|
|
31
|
+
key: z.string(),
|
|
32
|
+
/** 표시 이름 (예: "유닛 구현"). 미제공 시 key 값 활용 */
|
|
33
|
+
displayName: z.string().optional(),
|
|
34
|
+
/** 유닛 ID 매칭 정규식 패턴 (예: "^(U-\\d+|CP-).*") */
|
|
35
|
+
idPattern: z.string().refine((val) => {
|
|
36
|
+
try {
|
|
37
|
+
new RegExp(val);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}, { message: '유효한 정규식 문자열이어야 합니다' }),
|
|
44
|
+
/** 계획서 디렉토리 (paths.docRoots 키 참조) */
|
|
45
|
+
planDir: z.string(),
|
|
46
|
+
/** 커맨드 파일 내 대상 섹션 헤더 (예: "# 유닛 구현") */
|
|
47
|
+
commandSection: z.string(),
|
|
48
|
+
/** 의존성 수집 여부 */
|
|
49
|
+
collectDeps: z.boolean(),
|
|
50
|
+
/** 섹션 헤더 템플릿. {{unitId}}, {{title}} 등 변수 치환 지원 */
|
|
51
|
+
headerTemplate: z.string(),
|
|
52
|
+
});
|
|
53
|
+
// ── 문서 탐색 ──
|
|
54
|
+
/** 개별 문서 역할의 탐색 규칙 스키마 */
|
|
55
|
+
export const docDiscoveryRuleSchema = z.object({
|
|
56
|
+
/** 파일 탐색 패턴 (예: "{{unitId}}.md") */
|
|
57
|
+
pattern: z.string(),
|
|
58
|
+
/** glob 패턴 사용 여부 (기본: false) */
|
|
59
|
+
glob: z.boolean().optional().default(false),
|
|
60
|
+
});
|
|
61
|
+
/** 문서 탐색 설정 스키마 (역할별 규칙 맵) */
|
|
62
|
+
export const docDiscoverySchema = z.record(z.string(), docDiscoveryRuleSchema);
|
|
63
|
+
// ── 계획서 파싱 ──
|
|
64
|
+
/** 메타데이터 테이블 파싱 설정 스키마 */
|
|
65
|
+
export const metadataTableSchema = z.object({
|
|
66
|
+
/** ID 필드명 */
|
|
67
|
+
idField: z.string().default('Unit ID'),
|
|
68
|
+
/** Phase 필드명 */
|
|
69
|
+
phaseField: z.string().default('Phase'),
|
|
70
|
+
/** 의존성 필드명 */
|
|
71
|
+
dependsField: z.string().default('의존성'),
|
|
72
|
+
});
|
|
73
|
+
/** 계획서 파싱 설정 스키마 */
|
|
74
|
+
export const planParsingSchema = z.object({
|
|
75
|
+
/** 제목 추출 소스 ("h1" | "frontmatter:title") */
|
|
76
|
+
titleSource: z.enum(['h1', 'frontmatter:title']).default('h1'),
|
|
77
|
+
/** 의존성 추출 소스 ("section" | "frontmatter" | "metadata-table") */
|
|
78
|
+
dependsSource: z.enum(['section', 'frontmatter', 'metadata-table']).default('section'),
|
|
79
|
+
/** 의존성 섹션 헤더명. null이면 섹션 기반 추출 비활성화 */
|
|
80
|
+
dependsSectionName: z.string().nullable().default('이전 작업에서 가져올 것'),
|
|
81
|
+
/** 페어링 질문 섹션 헤더명 */
|
|
82
|
+
pairingQuestionSection: z.string().default('페어링 질문'),
|
|
83
|
+
/** 메타데이터 테이블 파싱 설정 */
|
|
84
|
+
metadataTable: metadataTableSchema.prefault({}),
|
|
85
|
+
});
|
|
86
|
+
// ── 백로그 파싱 ──
|
|
87
|
+
/** 상태 표시 매핑 스키마 */
|
|
88
|
+
export const statusMapSchema = z.object({
|
|
89
|
+
/** 대기 상태 표시 */
|
|
90
|
+
ready: z.string().default('⏸️'),
|
|
91
|
+
/** 진행중 상태 표시 */
|
|
92
|
+
inProgress: z.string().default('🚧'),
|
|
93
|
+
/** 완료 상태 표시 */
|
|
94
|
+
completed: z.string().default('✅'),
|
|
95
|
+
/** 차단 상태 표시 */
|
|
96
|
+
blocked: z.string().default('❌'),
|
|
97
|
+
});
|
|
98
|
+
/** 백로그(로드맵) 파싱 설정 스키마 */
|
|
99
|
+
export const backlogParsingSchema = z.object({
|
|
100
|
+
/** 백로그 섹션 시작 헤더 */
|
|
101
|
+
sectionHeader: z.string().default('## 작업 백로그'),
|
|
102
|
+
/** 백로그 항목 매칭 정규식 (named group 지원) */
|
|
103
|
+
entryPattern: z
|
|
104
|
+
.string()
|
|
105
|
+
.default('ID=\\[(?<id>.+?)\\]\\((?<path>[^)]+)\\)\\s*\\|\\s*(?<title>[^|]+)\\|\\s*Depends=(?<deps>[^|]*)\\|\\s*(?<status>.+)'),
|
|
106
|
+
/** 완료된 유닛 섹션 헤더 */
|
|
107
|
+
completedSection: z.string().default('### 완료'),
|
|
108
|
+
/** 상태 문자열 ↔ 내부 상태 키 매핑 */
|
|
109
|
+
statusMap: statusMapSchema.prefault({}),
|
|
110
|
+
});
|
|
111
|
+
// ── 커밋 탐색 ──
|
|
112
|
+
/** 커밋 탐색 설정 스키마 */
|
|
113
|
+
export const commitSearchSchema = z.object({
|
|
114
|
+
/** 탐색 전략 ("git-log-grep" | "conventional-commit" | "disabled") */
|
|
115
|
+
strategy: z.string().default('git-log-grep'),
|
|
116
|
+
/** grep에 사용할 ID 변환 방식 (예: "shortId") */
|
|
117
|
+
extractId: z.string().default('shortId'),
|
|
118
|
+
/** 최대 결과 수 */
|
|
119
|
+
maxResults: z.number().default(5),
|
|
120
|
+
/**
|
|
121
|
+
* 제외할 경로 glob 패턴 목록.
|
|
122
|
+
* 변경 파일이 모두 이 패턴에 매칭되는 커밋은 필터링됨 (문서 전용 커밋 제거).
|
|
123
|
+
* 기본값 [] → 필터 미적용
|
|
124
|
+
*/
|
|
125
|
+
excludePaths: z.array(z.string()).default([]),
|
|
126
|
+
/**
|
|
127
|
+
* 필수 포함 경로 glob 패턴 목록.
|
|
128
|
+
* 변경 파일 중 하나라도 이 패턴에 매칭되어야 통과 (소스 코드 커밋만 허용).
|
|
129
|
+
* 기본값 [] → 필터 미적용
|
|
130
|
+
*/
|
|
131
|
+
requirePaths: z.array(z.string()).default([]),
|
|
132
|
+
});
|
|
133
|
+
// ── 커맨드 파일 섹션 ──
|
|
134
|
+
/** 커맨드 파일 섹션 설정 스키마 */
|
|
135
|
+
export const commandSectionsSchema = z.object({
|
|
136
|
+
/** 섹션 구분자 문자열 */
|
|
137
|
+
separator: z.string().default('---------------------------------------------------'),
|
|
138
|
+
/** 다른 섹션 보존 여부 */
|
|
139
|
+
preserveOtherSections: z.boolean().default(true),
|
|
140
|
+
/** Commit SHA를 기록할 필드 키워드 (기본: "Commit") */
|
|
141
|
+
commitFieldName: z.string().default('Commit'),
|
|
142
|
+
/**
|
|
143
|
+
* HTML 주석 마커 사용 여부.
|
|
144
|
+
* true이면 섹션 경계에 `<!-- vc:begin:{key} -->` / `<!-- vc:end:{key} -->` 마커를 삽입하고,
|
|
145
|
+
* 섹션 탐색 시 마커를 우선 사용한다 (Fallback으로 기존 헤더 기반 탐색).
|
|
146
|
+
* 기본값 false로 기존 프로젝트 하위 호환 보장.
|
|
147
|
+
*/
|
|
148
|
+
useMarkers: z.boolean().default(false),
|
|
149
|
+
});
|
|
150
|
+
// ── 최상위 설정 ──
|
|
151
|
+
/** 기본 문서 탐색 설정 */
|
|
152
|
+
export const DEFAULT_DOC_DISCOVERY = {
|
|
153
|
+
plan: { pattern: '{{unitId}}.md', glob: false },
|
|
154
|
+
result: { pattern: '{{unitId}}.md', glob: false },
|
|
155
|
+
runbook: { pattern: '{{shortId}}-*-runbook.md', glob: true },
|
|
156
|
+
};
|
|
157
|
+
/** 프로젝트 설정 스키마 (vibe-commander.config.json) */
|
|
158
|
+
export const projectConfigSchema = z.object({
|
|
159
|
+
/** JSON Schema URL (IDE 자동완성용) */
|
|
160
|
+
$schema: z.string().optional(),
|
|
161
|
+
/** 설정 파일 버전 (현재 1 고정) */
|
|
162
|
+
version: z.literal(1),
|
|
163
|
+
/** 기본 경로 설정 */
|
|
164
|
+
paths: pathsSchema.prefault({}),
|
|
165
|
+
/** 유닛 유형 정의 배열 (순서 = ID 매칭 우선순위, 최소 1개 필수) */
|
|
166
|
+
unitTypes: z.array(unitTypeSchema).min(1),
|
|
167
|
+
/** 문서 탐색 규칙 (역할별) */
|
|
168
|
+
docDiscovery: docDiscoverySchema.default(DEFAULT_DOC_DISCOVERY),
|
|
169
|
+
/** 계획서 파싱 규칙 */
|
|
170
|
+
planParsing: planParsingSchema.prefault({}),
|
|
171
|
+
/** 백로그(로드맵) 파싱 규칙 */
|
|
172
|
+
backlogParsing: backlogParsingSchema.prefault({}),
|
|
173
|
+
/** 커밋 탐색 설정 */
|
|
174
|
+
commitSearch: commitSearchSchema.prefault({}),
|
|
175
|
+
/** 커맨드 파일 섹션 설정 */
|
|
176
|
+
commandSections: commandSectionsSchema.prefault({}),
|
|
177
|
+
/** 기본 특별 요청사항 목록 */
|
|
178
|
+
defaultSpecialRequests: z.array(z.string()).default([]),
|
|
179
|
+
/** 유닛 유형별 추가 특별 요청사항 (키: unitType.key) */
|
|
180
|
+
specialRequestsByType: z.record(z.string(), z.array(z.string())).default({}),
|
|
181
|
+
/** 키 기반 선택적 특별 요청사항 (--request 옵션용, 키: 사용자 정의 식별자, 값: 텍스트) */
|
|
182
|
+
customRequests: z.record(z.string(), z.string()).default({}),
|
|
183
|
+
});
|
|
184
|
+
/** 설정 파일명 */
|
|
185
|
+
export const CONFIG_FILENAME = 'vibe-commander.config.json';
|
|
186
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 백로그(로드맵) 파서 — Layer 1 순수 함수
|
|
3
|
+
*
|
|
4
|
+
* 로드맵 파일의 작업 백로그 섹션을 파싱하여
|
|
5
|
+
* 유닛 목록, 상태, 의존성을 추출한다.
|
|
6
|
+
* `list-units` 커맨드의 데이터 소스.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import type { BacklogParsingConfig } from '../../types/index.js';
|
|
11
|
+
import type { BacklogEntry, ToolResult } from '../../types/index.js';
|
|
12
|
+
/**
|
|
13
|
+
* 로드맵 백로그 섹션을 파싱하여 유닛 목록을 반환한다
|
|
14
|
+
*
|
|
15
|
+
* 1. 백로그 섹션 탐색 (sectionHeader 기반)
|
|
16
|
+
* 2. 완료 섹션 분리 (completedSection 기반)
|
|
17
|
+
* 3. 활성 항목 파싱 (entryPattern + statusMap)
|
|
18
|
+
* 4. 의존성 충족 계산 (ready ↔ blocked 분류)
|
|
19
|
+
*
|
|
20
|
+
* @param content - 로드맵 파일 전체 내용
|
|
21
|
+
* @param config - 백로그 파싱 설정 (entryPattern, statusMap 등)
|
|
22
|
+
* @returns 파싱된 BacklogEntry 배열 (활성 + 완료)
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseBacklog(content: string, config: BacklogParsingConfig): ToolResult<BacklogEntry[]>;
|
|
25
|
+
//# sourceMappingURL=backlog-parser.d.ts.map
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 백로그(로드맵) 파서 — Layer 1 순수 함수
|
|
3
|
+
*
|
|
4
|
+
* 로드맵 파일의 작업 백로그 섹션을 파싱하여
|
|
5
|
+
* 유닛 목록, 상태, 의존성을 추출한다.
|
|
6
|
+
* `list-units` 커맨드의 데이터 소스.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { ok, fail } from '../../types/index.js';
|
|
11
|
+
import { normalizeLineEndings, safeRegex, getHeadingPrefix, HEADING_RE, isNoneDependency, } from './md-utils.js';
|
|
12
|
+
/**
|
|
13
|
+
* 완료 섹션의 마크다운 링크에서 ID/경로/제목을 추출하는 패턴
|
|
14
|
+
*
|
|
15
|
+
* 형식: - ✅ [ID](path) | 제목 (날짜)
|
|
16
|
+
* 비탐욕(.+?)으로 ID 캡처하여 [Phase] 접미사가 있는 ID 지원
|
|
17
|
+
*/
|
|
18
|
+
const COMPLETED_LINK_RE = /\[(?<id>.+?)\]\((?<path>[^)]+)\)(?:\s*\|\s*(?<title>.+))?/;
|
|
19
|
+
/**
|
|
20
|
+
* 로드맵 백로그 섹션을 파싱하여 유닛 목록을 반환한다
|
|
21
|
+
*
|
|
22
|
+
* 1. 백로그 섹션 탐색 (sectionHeader 기반)
|
|
23
|
+
* 2. 완료 섹션 분리 (completedSection 기반)
|
|
24
|
+
* 3. 활성 항목 파싱 (entryPattern + statusMap)
|
|
25
|
+
* 4. 의존성 충족 계산 (ready ↔ blocked 분류)
|
|
26
|
+
*
|
|
27
|
+
* @param content - 로드맵 파일 전체 내용
|
|
28
|
+
* @param config - 백로그 파싱 설정 (entryPattern, statusMap 등)
|
|
29
|
+
* @returns 파싱된 BacklogEntry 배열 (활성 + 완료)
|
|
30
|
+
*/
|
|
31
|
+
export function parseBacklog(content, config) {
|
|
32
|
+
const normalized = normalizeLineEndings(content);
|
|
33
|
+
// 1. entryPattern을 RegExp로 안전하게 변환
|
|
34
|
+
const entryRegex = safeRegex(config.entryPattern);
|
|
35
|
+
if (!entryRegex) {
|
|
36
|
+
return fail('INVALID_ENTRY_PATTERN', `백로그 항목 패턴이 유효하지 않습니다: ${config.entryPattern}`);
|
|
37
|
+
}
|
|
38
|
+
// 2. 백로그 섹션 탐색
|
|
39
|
+
const sectionLines = findBacklogSection(normalized, config.sectionHeader);
|
|
40
|
+
if (sectionLines === null) {
|
|
41
|
+
return fail('BACKLOG_SECTION_NOT_FOUND', `백로그 섹션을 찾을 수 없습니다: ${config.sectionHeader}`);
|
|
42
|
+
}
|
|
43
|
+
// 3. 완료 섹션 분리
|
|
44
|
+
const { activeLines, completedLines } = splitByCompletedSection(sectionLines, config.completedSection);
|
|
45
|
+
// 4. 완료 ID 세트 구축 (의존성 충족 판단용)
|
|
46
|
+
const completedIds = extractCompletedIds(completedLines);
|
|
47
|
+
// 5. 활성 항목 파싱
|
|
48
|
+
const activeEntries = parseActiveEntries(activeLines, entryRegex, config.statusMap);
|
|
49
|
+
// 6. 활성 항목 중 completed 상태인 것도 completedIds에 추가
|
|
50
|
+
for (const entry of activeEntries) {
|
|
51
|
+
if (entry.status === 'completed') {
|
|
52
|
+
completedIds.add(entry.unitId);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// 7. 완료 섹션 항목 파싱 (BacklogEntry로 변환)
|
|
56
|
+
const completedEntries = parseCompletedSectionEntries(completedLines);
|
|
57
|
+
// 8. completedIds 확장: shortId도 포함 (의존성 매칭용)
|
|
58
|
+
const expandedCompletedIds = expandWithShortIds(completedIds);
|
|
59
|
+
// 9. 의존성 충족 계산 (ready ↔ blocked 분류)
|
|
60
|
+
const resolvedEntries = resolveDependencies(activeEntries, expandedCompletedIds);
|
|
61
|
+
return ok([...resolvedEntries, ...completedEntries]);
|
|
62
|
+
}
|
|
63
|
+
// ── 내부 헬퍼 함수 ──────────────────────────────────────
|
|
64
|
+
/**
|
|
65
|
+
* 백로그 섹션의 라인들을 추출한다
|
|
66
|
+
*
|
|
67
|
+
* sectionHeader와 정확히 일치하는 헤딩을 찾고,
|
|
68
|
+
* 동일/상위 레벨의 다음 헤딩까지의 라인을 수집한다.
|
|
69
|
+
*
|
|
70
|
+
* @returns 섹션 라인 배열, 섹션 미발견 시 null
|
|
71
|
+
*/
|
|
72
|
+
function findBacklogSection(content, sectionHeader) {
|
|
73
|
+
const lines = content.split('\n');
|
|
74
|
+
const trimmedHeader = sectionHeader.trim();
|
|
75
|
+
const headingPrefix = getHeadingPrefix(trimmedHeader);
|
|
76
|
+
const sectionLevel = headingPrefix ? headingPrefix.length : 0;
|
|
77
|
+
let inSection = false;
|
|
78
|
+
const result = [];
|
|
79
|
+
for (const line of lines) {
|
|
80
|
+
const trimmed = line.trim();
|
|
81
|
+
if (!inSection) {
|
|
82
|
+
if (trimmed === trimmedHeader) {
|
|
83
|
+
inSection = true;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// 동일/상위 레벨 헤딩이면 섹션 종료
|
|
89
|
+
const headingMatch = trimmed.match(HEADING_RE);
|
|
90
|
+
if (headingMatch && sectionLevel > 0) {
|
|
91
|
+
const level = (headingMatch[1] ?? '').length;
|
|
92
|
+
if (level <= sectionLevel) {
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
result.push(line);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return inSection ? result : null;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 섹션 라인을 완료 섹션 기준으로 분리한다
|
|
103
|
+
*
|
|
104
|
+
* completedSectionHeader 이전 라인은 activeLines,
|
|
105
|
+
* 이후 라인은 completedLines로 분리.
|
|
106
|
+
*/
|
|
107
|
+
function splitByCompletedSection(sectionLines, completedSectionHeader) {
|
|
108
|
+
const trimmedHeader = completedSectionHeader.trim();
|
|
109
|
+
const completedIdx = sectionLines.findIndex((line) => line.trim() === trimmedHeader);
|
|
110
|
+
if (completedIdx === -1) {
|
|
111
|
+
return { activeLines: sectionLines, completedLines: [] };
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
activeLines: sectionLines.slice(0, completedIdx),
|
|
115
|
+
completedLines: sectionLines.slice(completedIdx + 1),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 완료 섹션 라인에서 유닛 ID를 추출한다
|
|
120
|
+
*
|
|
121
|
+
* 마크다운 링크 `[ID](path)` 형태에서 ID 추출.
|
|
122
|
+
*/
|
|
123
|
+
function extractCompletedIds(lines) {
|
|
124
|
+
const ids = new Set();
|
|
125
|
+
for (const line of lines) {
|
|
126
|
+
const match = line.match(COMPLETED_LINK_RE);
|
|
127
|
+
if (match?.groups?.id) {
|
|
128
|
+
ids.add(match.groups.id.trim());
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return ids;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 활성 백로그 항목을 entryPattern으로 파싱한다
|
|
135
|
+
*
|
|
136
|
+
* Named groups: id, path, title, deps, status
|
|
137
|
+
*/
|
|
138
|
+
function parseActiveEntries(lines, entryRegex, statusMap) {
|
|
139
|
+
const reverseMap = buildReverseStatusMap(statusMap);
|
|
140
|
+
const entries = [];
|
|
141
|
+
for (const line of lines) {
|
|
142
|
+
const match = line.match(entryRegex);
|
|
143
|
+
if (!match?.groups)
|
|
144
|
+
continue;
|
|
145
|
+
const { id, path, title, deps, status } = match.groups;
|
|
146
|
+
if (!id || !title || status === undefined)
|
|
147
|
+
continue;
|
|
148
|
+
entries.push({
|
|
149
|
+
unitId: id.trim(),
|
|
150
|
+
title: title.trim(),
|
|
151
|
+
depends: parseDependsList(deps ?? ''),
|
|
152
|
+
status: resolveStatus(status.trim(), reverseMap),
|
|
153
|
+
path: (path ?? '').trim(),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return entries;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* 완료 섹션의 항목을 BacklogEntry로 파싱한다
|
|
160
|
+
*
|
|
161
|
+
* 완료 섹션은 entryPattern과 다른 형식이므로 별도 파싱.
|
|
162
|
+
* 형식: - ✅ [ID](path) | 제목 (날짜)
|
|
163
|
+
*/
|
|
164
|
+
function parseCompletedSectionEntries(lines) {
|
|
165
|
+
const entries = [];
|
|
166
|
+
for (const line of lines) {
|
|
167
|
+
const match = line.match(COMPLETED_LINK_RE);
|
|
168
|
+
if (!match?.groups?.id)
|
|
169
|
+
continue;
|
|
170
|
+
entries.push({
|
|
171
|
+
unitId: match.groups.id.trim(),
|
|
172
|
+
title: (match.groups.title ?? '').trim(),
|
|
173
|
+
depends: [],
|
|
174
|
+
status: 'completed',
|
|
175
|
+
path: (match.groups.path ?? '').trim(),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return entries;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* 상태 이모지 → BacklogStatus 역방향 맵을 구축한다
|
|
182
|
+
*
|
|
183
|
+
* statusMap: { ready: '⏸️', inProgress: '🚧', completed: '✅', blocked: '❌' }
|
|
184
|
+
* → Map { '⏸️' → 'ready', '🚧' → 'inProgress', ... }
|
|
185
|
+
*/
|
|
186
|
+
function buildReverseStatusMap(statusMap) {
|
|
187
|
+
const map = new Map();
|
|
188
|
+
for (const [key, emoji] of Object.entries(statusMap)) {
|
|
189
|
+
map.set(emoji.trim(), key);
|
|
190
|
+
}
|
|
191
|
+
return map;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* 상태 문자열을 BacklogStatus로 변환한다
|
|
195
|
+
*
|
|
196
|
+
* 역방향 맵에 없는 경우 기본값 'ready' 반환.
|
|
197
|
+
*/
|
|
198
|
+
function resolveStatus(statusStr, reverseMap) {
|
|
199
|
+
return reverseMap.get(statusStr.trim()) ?? 'ready';
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* 쉼표로 구분된 의존성 문자열을 파싱한다
|
|
203
|
+
*
|
|
204
|
+
* "U-008,U-009,U-010" → ["U-008", "U-009", "U-010"]
|
|
205
|
+
* 빈 문자열 → []
|
|
206
|
+
*/
|
|
207
|
+
function parseDependsList(deps) {
|
|
208
|
+
return deps
|
|
209
|
+
.split(',')
|
|
210
|
+
.map((d) => d.trim())
|
|
211
|
+
.filter((d) => Boolean(d) && !isNoneDependency(d));
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* 완료 ID 세트에 shortId 변형을 추가한다
|
|
215
|
+
*
|
|
216
|
+
* 의존성 필드가 shortId(예: "U-002")로 기재되고
|
|
217
|
+
* 완료 ID가 fullId(예: "U-002[Mvp]")인 경우의 매칭을 위해
|
|
218
|
+
* [Phase] 접미사를 제거한 shortId도 세트에 추가한다.
|
|
219
|
+
*/
|
|
220
|
+
function expandWithShortIds(ids) {
|
|
221
|
+
const expanded = new Set(ids);
|
|
222
|
+
for (const id of ids) {
|
|
223
|
+
const shortId = id.replace(/\[.*?\]$/, '');
|
|
224
|
+
if (shortId !== id) {
|
|
225
|
+
expanded.add(shortId);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return expanded;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* 의존성 충족 여부를 계산하여 ready/blocked를 분류한다
|
|
232
|
+
*
|
|
233
|
+
* - completed, inProgress 상태는 변경하지 않음
|
|
234
|
+
* - 의존성이 없거나 모두 충족되면 ready
|
|
235
|
+
* - 미충족 의존성이 있으면 blocked
|
|
236
|
+
*/
|
|
237
|
+
function resolveDependencies(entries, completedIds) {
|
|
238
|
+
return entries.map((entry) => {
|
|
239
|
+
// completed, inProgress는 그대로 유지
|
|
240
|
+
if (entry.status === 'completed' || entry.status === 'inProgress') {
|
|
241
|
+
return entry;
|
|
242
|
+
}
|
|
243
|
+
// 의존성이 없으면 ready 유지
|
|
244
|
+
if (entry.depends.length === 0) {
|
|
245
|
+
return { ...entry, status: 'ready' };
|
|
246
|
+
}
|
|
247
|
+
// 모든 의존성이 완료되었는지 확인
|
|
248
|
+
const allDepsCompleted = entry.depends.every((dep) => completedIds.has(dep));
|
|
249
|
+
return {
|
|
250
|
+
...entry,
|
|
251
|
+
status: (allDepsCompleted ? 'ready' : 'blocked'),
|
|
252
|
+
};
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=backlog-parser.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 의존성 라인 파서 — Layer 1 순수 함수
|
|
3
|
+
*
|
|
4
|
+
* 마크다운 리스트 항목에서 의존 유닛 정보(ID, 설명, artifacts)를 파싱한다.
|
|
5
|
+
* dependency-extractor.ts에서 섹션 내 라인들을 받아 구조화된 DepInfo로 변환.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import type { DepInfo } from '../../types/index.js';
|
|
10
|
+
/**
|
|
11
|
+
* 의존성 라인 파싱 결과
|
|
12
|
+
*
|
|
13
|
+
* 유닛 기반 의존성(`deps`)과 비유닛 컨텍스트 항목(`contextNotes`)을 분리하여 반환.
|
|
14
|
+
*/
|
|
15
|
+
export interface ParsedDependencyResult {
|
|
16
|
+
/** 유닛 ID가 있는 의존성 항목 */
|
|
17
|
+
deps: DepInfo[];
|
|
18
|
+
/** 유닛 ID가 없는 비유닛 컨텍스트 항목 (SKILL 참조, 기존 코드 패턴 등) */
|
|
19
|
+
contextNotes: string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 섹션 내 리스트 항목에서 의존성 정보를 파싱한다
|
|
23
|
+
*
|
|
24
|
+
* 각 최상위 리스트 항목(`- `)에서:
|
|
25
|
+
* 1. `**ID**` 또는 `[ID](url)` 패턴으로 유닛 ID 추출
|
|
26
|
+
* 2. idPattern이 있으면 필터링
|
|
27
|
+
* 3. 설명 텍스트 추출
|
|
28
|
+
* 4. 하위 항목(` - `)에서 artifacts 추출
|
|
29
|
+
* 5. 유닛 ID가 없는 라인은 contextNotes로 수집
|
|
30
|
+
*
|
|
31
|
+
* @param lines - 섹션 내 라인 배열
|
|
32
|
+
* @param idPattern - 유닛 ID 필터링 패턴 (선택)
|
|
33
|
+
* @returns 유닛 의존성과 비유닛 컨텍스트 항목
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseDependencyLines(lines: string[], idPattern?: string): ParsedDependencyResult;
|
|
36
|
+
//# sourceMappingURL=dep-line-parser.d.ts.map
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 의존성 라인 파서 — Layer 1 순수 함수
|
|
3
|
+
*
|
|
4
|
+
* 마크다운 리스트 항목에서 의존 유닛 정보(ID, 설명, artifacts)를 파싱한다.
|
|
5
|
+
* dependency-extractor.ts에서 섹션 내 라인들을 받아 구조화된 DepInfo로 변환.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { escapeRegex, safeRegex, isNoneDependency } from './md-utils.js';
|
|
10
|
+
/**
|
|
11
|
+
* 섹션 내 리스트 항목에서 의존성 정보를 파싱한다
|
|
12
|
+
*
|
|
13
|
+
* 각 최상위 리스트 항목(`- `)에서:
|
|
14
|
+
* 1. `**ID**` 또는 `[ID](url)` 패턴으로 유닛 ID 추출
|
|
15
|
+
* 2. idPattern이 있으면 필터링
|
|
16
|
+
* 3. 설명 텍스트 추출
|
|
17
|
+
* 4. 하위 항목(` - `)에서 artifacts 추출
|
|
18
|
+
* 5. 유닛 ID가 없는 라인은 contextNotes로 수집
|
|
19
|
+
*
|
|
20
|
+
* @param lines - 섹션 내 라인 배열
|
|
21
|
+
* @param idPattern - 유닛 ID 필터링 패턴 (선택)
|
|
22
|
+
* @returns 유닛 의존성과 비유닛 컨텍스트 항목
|
|
23
|
+
*/
|
|
24
|
+
export function parseDependencyLines(lines, idPattern) {
|
|
25
|
+
const deps = [];
|
|
26
|
+
const contextNotes = [];
|
|
27
|
+
let currentUnitId = '';
|
|
28
|
+
let currentDescription = '';
|
|
29
|
+
let currentArtifacts;
|
|
30
|
+
let hasCurrent = false;
|
|
31
|
+
for (const line of lines) {
|
|
32
|
+
const trimmed = line.trim();
|
|
33
|
+
if (trimmed === '')
|
|
34
|
+
continue;
|
|
35
|
+
// 최상위 리스트 항목 판별: `- ` 또는 `* `로 시작하고, 들여쓰기 없음
|
|
36
|
+
const isTopLevelItem = /^[-*]\s/.test(trimmed) && !/^\s{2,}/.test(line);
|
|
37
|
+
if (isTopLevelItem) {
|
|
38
|
+
// 이전 dep 저장
|
|
39
|
+
if (hasCurrent) {
|
|
40
|
+
deps.push({
|
|
41
|
+
unitId: currentUnitId,
|
|
42
|
+
description: currentDescription,
|
|
43
|
+
artifacts: currentArtifacts,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
// 새 dep 파싱
|
|
47
|
+
const parsed = extractDepFromLine(trimmed, idPattern);
|
|
48
|
+
if (parsed) {
|
|
49
|
+
currentUnitId = parsed.unitId;
|
|
50
|
+
currentDescription = parsed.description;
|
|
51
|
+
currentArtifacts = parsed.artifacts;
|
|
52
|
+
hasCurrent = true;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// 비유닛 컨텍스트 항목: 리스트 마커 제거 후 수집
|
|
56
|
+
const noteText = trimmed.replace(/^[-*]\s+/, '').trim();
|
|
57
|
+
if (noteText) {
|
|
58
|
+
const firstToken = noteText.split(/[\s(,]/)[0] ?? '';
|
|
59
|
+
if (!isNoneDependency(firstToken)) {
|
|
60
|
+
contextNotes.push(noteText);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
hasCurrent = false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else if (hasCurrent && /^\s{2,}/.test(line)) {
|
|
67
|
+
// 하위 항목 → artifacts에 추가
|
|
68
|
+
const artifactText = extractArtifactText(trimmed);
|
|
69
|
+
if (artifactText) {
|
|
70
|
+
currentArtifacts =
|
|
71
|
+
currentArtifacts !== undefined ? `${currentArtifacts}; ${artifactText}` : artifactText;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// 마지막 dep 저장
|
|
76
|
+
if (hasCurrent) {
|
|
77
|
+
deps.push({
|
|
78
|
+
unitId: currentUnitId,
|
|
79
|
+
description: currentDescription,
|
|
80
|
+
artifacts: currentArtifacts,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return { deps, contextNotes };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 한 라인에서 의존 유닛 정보를 추출한다
|
|
87
|
+
*
|
|
88
|
+
* 추출 우선순위:
|
|
89
|
+
* 1. 링크 텍스트 `[ID](url)` — 더 구체적 (문서 링크 포함)
|
|
90
|
+
* 2. 볼드 텍스트 `**ID**` — 일반적인 의존성 표기
|
|
91
|
+
*
|
|
92
|
+
* idPattern이 제공되면 추출된 후보를 필터링하여 실제 유닛 ID만 선별.
|
|
93
|
+
*
|
|
94
|
+
* @param line - 리스트 항목 라인 (리스트 마커 제거 전)
|
|
95
|
+
* @param idPattern - 유닛 ID 필터링 패턴 (선택)
|
|
96
|
+
* @returns 추출된 DepInfo 또는 null
|
|
97
|
+
*/
|
|
98
|
+
function extractDepFromLine(line, idPattern) {
|
|
99
|
+
// 리스트 마커 제거
|
|
100
|
+
const content = line.replace(/^[-*]\s+/, '');
|
|
101
|
+
// 후보 ID 수집 (링크 우선, 볼드 후순위)
|
|
102
|
+
const candidates = [];
|
|
103
|
+
// 링크 텍스트: [ID](url) — 중첩 괄호 1레벨 지원 (예: [U-002[Mvp]])
|
|
104
|
+
const linkRegex = /\[((?:[^[\]]*(?:\[[^\]]*\])?[^[\]]*)*)\]\([^)]*\)/g;
|
|
105
|
+
let match;
|
|
106
|
+
while ((match = linkRegex.exec(content)) !== null) {
|
|
107
|
+
if (match[1])
|
|
108
|
+
candidates.push({ id: match[1].trim(), source: 'link' });
|
|
109
|
+
}
|
|
110
|
+
// 볼드 텍스트: **ID**
|
|
111
|
+
const boldRegex = /\*\*([^*]+)\*\*/g;
|
|
112
|
+
while ((match = boldRegex.exec(content)) !== null) {
|
|
113
|
+
if (match[1])
|
|
114
|
+
candidates.push({ id: match[1].trim(), source: 'bold' });
|
|
115
|
+
}
|
|
116
|
+
if (candidates.length === 0)
|
|
117
|
+
return null;
|
|
118
|
+
// idPattern으로 필터링
|
|
119
|
+
const idRegex = idPattern ? safeRegex(idPattern) : null;
|
|
120
|
+
const validCandidate = idRegex ? candidates.find((c) => idRegex.test(c.id)) : candidates[0];
|
|
121
|
+
if (!validCandidate)
|
|
122
|
+
return null;
|
|
123
|
+
const unitId = validCandidate.id;
|
|
124
|
+
if (isNoneDependency(unitId))
|
|
125
|
+
return null;
|
|
126
|
+
// 설명 추출: ID 참조 이후의 텍스트에서 구분자 제거
|
|
127
|
+
const description = extractDescription(content, unitId, validCandidate.source);
|
|
128
|
+
return { unitId, description };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* ID 참조 이후의 설명 텍스트를 추출한다
|
|
132
|
+
*
|
|
133
|
+
* 볼드 `**ID**` 또는 링크 `[ID](url)` 이후의 텍스트에서
|
|
134
|
+
* 구분자(`:`, `—`, `-`)를 제거하고 정리.
|
|
135
|
+
*
|
|
136
|
+
* @param content - 리스트 항목 내용 (마커 제거 후)
|
|
137
|
+
* @param unitId - 추출된 유닛 ID
|
|
138
|
+
* @param source - ID가 추출된 소스 ('link' | 'bold')
|
|
139
|
+
* @returns 설명 문자열 (없으면 빈 문자열)
|
|
140
|
+
*/
|
|
141
|
+
function extractDescription(content, unitId, source) {
|
|
142
|
+
const escapedId = escapeRegex(unitId);
|
|
143
|
+
let afterPattern;
|
|
144
|
+
if (source === 'link') {
|
|
145
|
+
afterPattern = new RegExp(`\\[${escapedId}\\]\\([^)]*\\)\\s*(.*)`);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
afterPattern = new RegExp(`\\*\\*${escapedId}\\*\\*\\s*(.*)`);
|
|
149
|
+
}
|
|
150
|
+
const afterMatch = content.match(afterPattern);
|
|
151
|
+
if (!afterMatch?.[1])
|
|
152
|
+
return '';
|
|
153
|
+
// 구분자 제거: 시작 부분의 :, —, -, | 등
|
|
154
|
+
return afterMatch[1].replace(/^[:\-—|]+\s*/, '').trim();
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 하위 항목에서 artifact 텍스트를 추출한다
|
|
158
|
+
*
|
|
159
|
+
* `- 결과물: ...` 형식의 하위 항목에서 결과물 설명을 추출.
|
|
160
|
+
* 결과물 접두사가 없으면 일반 하위 항목 텍스트를 반환.
|
|
161
|
+
*
|
|
162
|
+
* @param line - 하위 항목 라인 (트리밍 완료)
|
|
163
|
+
* @returns artifact 텍스트 또는 undefined
|
|
164
|
+
*/
|
|
165
|
+
function extractArtifactText(line) {
|
|
166
|
+
const content = line.replace(/^[-*]\s+/, '');
|
|
167
|
+
if (content === '')
|
|
168
|
+
return undefined;
|
|
169
|
+
// "결과물:" 접두사가 있으면 그 뒤의 텍스트
|
|
170
|
+
const artifactMatch = content.match(/^결과물\s*:\s*(.+)/);
|
|
171
|
+
if (artifactMatch?.[1])
|
|
172
|
+
return artifactMatch[1].trim();
|
|
173
|
+
// 일반 하위 항목 텍스트
|
|
174
|
+
return content;
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=dep-line-parser.js.map
|