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,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 제목 추출기 — Layer 1 순수 함수
|
|
3
|
+
*
|
|
4
|
+
* 계획서 마크다운에서 제목을 추출한다.
|
|
5
|
+
* `titleSource` 설정에 따라 H1 헤더 또는 YAML frontmatter에서 가져온다.
|
|
6
|
+
*
|
|
7
|
+
* PRD §8.1 계획서 파싱:
|
|
8
|
+
* titleSource: "h1" → 첫 번째 H1 라인에서 추출
|
|
9
|
+
* titleSource: "frontmatter:title" → YAML frontmatter의 title 필드
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import { ok } from '../../types/index.js';
|
|
14
|
+
import { FRONTMATTER_RE, normalizeLineEndings } from './md-utils.js';
|
|
15
|
+
/**
|
|
16
|
+
* 계획서에서 제목을 추출한다
|
|
17
|
+
*
|
|
18
|
+
* `titleSource` 설정값에 따라 추출 전략을 선택:
|
|
19
|
+
* - `"h1"`: 첫 번째 H1 헤더에서 추출
|
|
20
|
+
* - `"frontmatter:필드명"`: YAML frontmatter의 해당 필드에서 추출
|
|
21
|
+
*
|
|
22
|
+
* 못 찾으면 `undefined`를 포함한 성공 결과 반환 (에러가 아닌 부분 실패 — Graceful Degradation).
|
|
23
|
+
*
|
|
24
|
+
* @param content - 계획서 마크다운 전체 내용
|
|
25
|
+
* @param titleSource - 제목 추출 소스 설정 ("h1" | "frontmatter:title" 등)
|
|
26
|
+
* @returns 제목 문자열(또는 undefined)을 포함한 ToolResult
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* const result = extractTitle('# U-005: 제목 추출기\n\n본문', 'h1');
|
|
31
|
+
* if (result.success) {
|
|
32
|
+
* // result.data → 'U-005: 제목 추출기'
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export function extractTitle(content, titleSource) {
|
|
37
|
+
const normalized = normalizeLineEndings(content);
|
|
38
|
+
if (titleSource === 'h1') {
|
|
39
|
+
return ok(extractH1Title(normalized));
|
|
40
|
+
}
|
|
41
|
+
if (titleSource.startsWith('frontmatter:')) {
|
|
42
|
+
const field = titleSource.slice('frontmatter:'.length);
|
|
43
|
+
return ok(extractFrontmatterField(normalized, field));
|
|
44
|
+
}
|
|
45
|
+
// 알 수 없는 소스 — undefined 반환 (Graceful Degradation)
|
|
46
|
+
return ok(undefined);
|
|
47
|
+
}
|
|
48
|
+
// ── 내부 헬퍼 ──
|
|
49
|
+
/**
|
|
50
|
+
* 첫 번째 H1 헤더에서 제목을 추출한다
|
|
51
|
+
*
|
|
52
|
+
* 멀티라인 모드(`m` 플래그)로 `# ` 시작 라인을 탐색.
|
|
53
|
+
* H2 이상(## )은 무시하며, `#` 뒤 공백 필수.
|
|
54
|
+
*
|
|
55
|
+
* @param content - 마크다운 내용
|
|
56
|
+
* @returns H1 제목 또는 undefined
|
|
57
|
+
*/
|
|
58
|
+
function extractH1Title(content) {
|
|
59
|
+
const match = content.match(/^#\s+(.+)$/m);
|
|
60
|
+
return match?.[1]?.trim() ?? undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* YAML frontmatter에서 특정 필드 값을 추출한다
|
|
64
|
+
*
|
|
65
|
+
* YAML 라이브러리 없이 `key: value` 라인 기반으로 파싱.
|
|
66
|
+
* 따옴표(`"`, `'`)로 감싼 값은 따옴표를 제거하여 반환.
|
|
67
|
+
* 멀티라인 YAML 값은 지원하지 않음 (단일 라인만).
|
|
68
|
+
*
|
|
69
|
+
* @param content - 마크다운 내용 (frontmatter 포함)
|
|
70
|
+
* @param field - 추출할 필드명 (예: "title")
|
|
71
|
+
* @returns 필드 값 또는 undefined
|
|
72
|
+
*/
|
|
73
|
+
function extractFrontmatterField(content, field) {
|
|
74
|
+
// frontmatter는 문서 시작 ---로 시작하고 ---로 끝남
|
|
75
|
+
const fmMatch = content.match(FRONTMATTER_RE);
|
|
76
|
+
if (!fmMatch?.[1])
|
|
77
|
+
return undefined;
|
|
78
|
+
const fmContent = fmMatch[1];
|
|
79
|
+
const lines = fmContent.split('\n');
|
|
80
|
+
for (const line of lines) {
|
|
81
|
+
const trimmedLine = line.trim();
|
|
82
|
+
if (trimmedLine === '' || trimmedLine.startsWith('#'))
|
|
83
|
+
continue;
|
|
84
|
+
// key: value 패턴 매칭 (첫 번째 콜론 기준으로 분리)
|
|
85
|
+
const kvMatch = trimmedLine.match(/^([^:]+):\s*(.+)$/);
|
|
86
|
+
if (!kvMatch?.[1] || !kvMatch[2])
|
|
87
|
+
continue;
|
|
88
|
+
const key = kvMatch[1].trim();
|
|
89
|
+
if (key !== field)
|
|
90
|
+
continue;
|
|
91
|
+
let value = kvMatch[2].trim();
|
|
92
|
+
// 따옴표 제거
|
|
93
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
94
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
95
|
+
value = value.slice(1, -1);
|
|
96
|
+
}
|
|
97
|
+
return value || undefined;
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=title-extractor.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core 렌더러 — 공개 API barrel export
|
|
3
|
+
*
|
|
4
|
+
* 커맨드 파일 섹션 렌더링/업데이트 함수의 진입점.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
export { toShortId, toBareId, toSlug, buildTemplateVars, interpolatePattern, } from './interpolate.js';
|
|
9
|
+
export { extractTitleOnly, buildSectionVars, interpolateTemplate } from './template-engine.js';
|
|
10
|
+
export { renderSection } from './section-renderer.js';
|
|
11
|
+
export { updateSection, updateField } from './section-updater.js';
|
|
12
|
+
export type { UpdateResult, MarkerOptions } from './section-updater.js';
|
|
13
|
+
export { createBeginMarker, createEndMarker, findMarkerRange, isValidSectionKey, wrapWithMarkers, stripMarkers, } from './marker-utils.js';
|
|
14
|
+
export type { MarkerRange } from './marker-utils.js';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core 렌더러 — 공개 API barrel export
|
|
3
|
+
*
|
|
4
|
+
* 커맨드 파일 섹션 렌더링/업데이트 함수의 진입점.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
// 템플릿 보간
|
|
9
|
+
export { toShortId, toBareId, toSlug, buildTemplateVars, interpolatePattern, } from './interpolate.js';
|
|
10
|
+
// 템플릿 엔진
|
|
11
|
+
export { extractTitleOnly, buildSectionVars, interpolateTemplate } from './template-engine.js';
|
|
12
|
+
// 섹션 렌더링
|
|
13
|
+
export { renderSection } from './section-renderer.js';
|
|
14
|
+
// 섹션 업데이트
|
|
15
|
+
export { updateSection, updateField } from './section-updater.js';
|
|
16
|
+
// 마커 유틸리티
|
|
17
|
+
export { createBeginMarker, createEndMarker, findMarkerRange, isValidSectionKey, wrapWithMarkers, stripMarkers, } from './marker-utils.js';
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 템플릿 변수 보간 유틸리티 — Layer 1 순수 함수
|
|
3
|
+
*
|
|
4
|
+
* 유닛 ID에서 다양한 형태의 식별자를 추출하고,
|
|
5
|
+
* {{변수}} 플레이스홀더를 치환하는 기능 제공.
|
|
6
|
+
*
|
|
7
|
+
* PRD §8.2 문서 탐색 변수:
|
|
8
|
+
* {{unitId}} — 원본 ID ("U-117[Mmp]")
|
|
9
|
+
* {{shortId}} — phase 제거 ("U-117")
|
|
10
|
+
* {{bareId}} — 숫자만 ("117")
|
|
11
|
+
* {{slug}} — kebab-case ("u-117-mmp")
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* 유닛 ID에서 phase 태그를 제거한다
|
|
17
|
+
*
|
|
18
|
+
* 대괄호([...])로 둘러싸인 phase 정보를 ID 끝에서 제거.
|
|
19
|
+
* phase 태그가 없으면 원본 그대로 반환.
|
|
20
|
+
*
|
|
21
|
+
* @param unitId - 원본 유닛 ID
|
|
22
|
+
* @returns phase 제거된 유닛 ID
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* toShortId('U-118[Mmp]') // → 'U-118'
|
|
27
|
+
* toShortId('RU-010-S3') // → 'RU-010-S3' (변화 없음)
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function toShortId(unitId: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* 유닛 ID에서 첫 번째 숫자 시퀀스만 추출한다
|
|
33
|
+
*
|
|
34
|
+
* prefix와 phase를 제거하고 순수 숫자 부분만 반환.
|
|
35
|
+
* 숫자가 없으면 shortId 그대로 반환 (Graceful Degradation).
|
|
36
|
+
*
|
|
37
|
+
* @param unitId - 원본 유닛 ID
|
|
38
|
+
* @returns 숫자 문자열
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* toBareId('U-118[Mmp]') // → '118'
|
|
43
|
+
* toBareId('RU-010-S3') // → '010'
|
|
44
|
+
* toBareId('CP-MMP-03') // → '03'
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function toBareId(unitId: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* 유닛 ID를 kebab-case 슬러그로 변환한다
|
|
50
|
+
*
|
|
51
|
+
* 대괄호/특수문자를 하이픈으로 치환하고 소문자로 변환.
|
|
52
|
+
*
|
|
53
|
+
* @param unitId - 원본 유닛 ID
|
|
54
|
+
* @returns kebab-case 슬러그
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* toSlug('U-118[Mmp]') // → 'u-118-mmp'
|
|
59
|
+
* toSlug('RU-010-S3') // → 'ru-010-s3'
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare function toSlug(unitId: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* 유닛 ID에서 템플릿 변수 맵을 생성한다
|
|
65
|
+
*
|
|
66
|
+
* 패턴 보간에 사용되는 4개 변수를 한 번에 생성.
|
|
67
|
+
*
|
|
68
|
+
* @param unitId - 원본 유닛 ID
|
|
69
|
+
* @returns 변수명 → 값 맵
|
|
70
|
+
*/
|
|
71
|
+
export declare function buildTemplateVars(unitId: string): Record<string, string>;
|
|
72
|
+
/**
|
|
73
|
+
* 패턴 내 {{변수}} 플레이스홀더를 치환한다
|
|
74
|
+
*
|
|
75
|
+
* `eval()` 사용 금지 — `string.replace()` 기반 구현 (RULE-006 준수).
|
|
76
|
+
* 미등록 변수는 원본 플레이스홀더를 유지 (Graceful Degradation).
|
|
77
|
+
*
|
|
78
|
+
* @param pattern - {{변수}} 플레이스홀더가 포함된 패턴 문자열
|
|
79
|
+
* @param vars - 변수명 → 값 맵
|
|
80
|
+
* @returns 치환된 문자열
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* interpolatePattern('{{unitId}}.md', { unitId: 'U-118[Mmp]' })
|
|
85
|
+
* // → 'U-118[Mmp].md'
|
|
86
|
+
*
|
|
87
|
+
* interpolatePattern('{{shortId}}-*-runbook.md', { shortId: 'U-118' })
|
|
88
|
+
* // → 'U-118-*-runbook.md'
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export declare function interpolatePattern(pattern: string, vars: Record<string, string>): string;
|
|
92
|
+
//# sourceMappingURL=interpolate.d.ts.map
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 템플릿 변수 보간 유틸리티 — Layer 1 순수 함수
|
|
3
|
+
*
|
|
4
|
+
* 유닛 ID에서 다양한 형태의 식별자를 추출하고,
|
|
5
|
+
* {{변수}} 플레이스홀더를 치환하는 기능 제공.
|
|
6
|
+
*
|
|
7
|
+
* PRD §8.2 문서 탐색 변수:
|
|
8
|
+
* {{unitId}} — 원본 ID ("U-117[Mmp]")
|
|
9
|
+
* {{shortId}} — phase 제거 ("U-117")
|
|
10
|
+
* {{bareId}} — 숫자만 ("117")
|
|
11
|
+
* {{slug}} — kebab-case ("u-117-mmp")
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* 유닛 ID에서 phase 태그를 제거한다
|
|
17
|
+
*
|
|
18
|
+
* 대괄호([...])로 둘러싸인 phase 정보를 ID 끝에서 제거.
|
|
19
|
+
* phase 태그가 없으면 원본 그대로 반환.
|
|
20
|
+
*
|
|
21
|
+
* @param unitId - 원본 유닛 ID
|
|
22
|
+
* @returns phase 제거된 유닛 ID
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* toShortId('U-118[Mmp]') // → 'U-118'
|
|
27
|
+
* toShortId('RU-010-S3') // → 'RU-010-S3' (변화 없음)
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function toShortId(unitId) {
|
|
31
|
+
return unitId.replace(/\[.*?\]$/, '');
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 유닛 ID에서 첫 번째 숫자 시퀀스만 추출한다
|
|
35
|
+
*
|
|
36
|
+
* prefix와 phase를 제거하고 순수 숫자 부분만 반환.
|
|
37
|
+
* 숫자가 없으면 shortId 그대로 반환 (Graceful Degradation).
|
|
38
|
+
*
|
|
39
|
+
* @param unitId - 원본 유닛 ID
|
|
40
|
+
* @returns 숫자 문자열
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* toBareId('U-118[Mmp]') // → '118'
|
|
45
|
+
* toBareId('RU-010-S3') // → '010'
|
|
46
|
+
* toBareId('CP-MMP-03') // → '03'
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export function toBareId(unitId) {
|
|
50
|
+
const short = toShortId(unitId);
|
|
51
|
+
const match = short.match(/\d+/);
|
|
52
|
+
return match !== null ? match[0] : short;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 유닛 ID를 kebab-case 슬러그로 변환한다
|
|
56
|
+
*
|
|
57
|
+
* 대괄호/특수문자를 하이픈으로 치환하고 소문자로 변환.
|
|
58
|
+
*
|
|
59
|
+
* @param unitId - 원본 유닛 ID
|
|
60
|
+
* @returns kebab-case 슬러그
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* toSlug('U-118[Mmp]') // → 'u-118-mmp'
|
|
65
|
+
* toSlug('RU-010-S3') // → 'ru-010-s3'
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export function toSlug(unitId) {
|
|
69
|
+
return unitId
|
|
70
|
+
.replace(/[[\](){}]/g, '-')
|
|
71
|
+
.replace(/[^a-zA-Z0-9-]/g, '-')
|
|
72
|
+
.replace(/-+/g, '-')
|
|
73
|
+
.replace(/^-|-$/g, '')
|
|
74
|
+
.toLowerCase();
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 유닛 ID에서 템플릿 변수 맵을 생성한다
|
|
78
|
+
*
|
|
79
|
+
* 패턴 보간에 사용되는 4개 변수를 한 번에 생성.
|
|
80
|
+
*
|
|
81
|
+
* @param unitId - 원본 유닛 ID
|
|
82
|
+
* @returns 변수명 → 값 맵
|
|
83
|
+
*/
|
|
84
|
+
export function buildTemplateVars(unitId) {
|
|
85
|
+
return {
|
|
86
|
+
unitId,
|
|
87
|
+
shortId: toShortId(unitId),
|
|
88
|
+
bareId: toBareId(unitId),
|
|
89
|
+
slug: toSlug(unitId),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 패턴 내 {{변수}} 플레이스홀더를 치환한다
|
|
94
|
+
*
|
|
95
|
+
* `eval()` 사용 금지 — `string.replace()` 기반 구현 (RULE-006 준수).
|
|
96
|
+
* 미등록 변수는 원본 플레이스홀더를 유지 (Graceful Degradation).
|
|
97
|
+
*
|
|
98
|
+
* @param pattern - {{변수}} 플레이스홀더가 포함된 패턴 문자열
|
|
99
|
+
* @param vars - 변수명 → 값 맵
|
|
100
|
+
* @returns 치환된 문자열
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* interpolatePattern('{{unitId}}.md', { unitId: 'U-118[Mmp]' })
|
|
105
|
+
* // → 'U-118[Mmp].md'
|
|
106
|
+
*
|
|
107
|
+
* interpolatePattern('{{shortId}}-*-runbook.md', { shortId: 'U-118' })
|
|
108
|
+
* // → 'U-118-*-runbook.md'
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export function interpolatePattern(pattern, vars) {
|
|
112
|
+
return pattern.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
113
|
+
const value = vars[key];
|
|
114
|
+
return value !== undefined ? value : match;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=interpolate.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML 주석 마커 유틸리티 — Layer 1 순수 함수
|
|
3
|
+
*
|
|
4
|
+
* 커맨드 파일 섹션 경계를 안전하게 식별하기 위한
|
|
5
|
+
* `<!-- vc:begin:{key} -->` / `<!-- vc:end:{key} -->` 마커를 생성/탐색한다.
|
|
6
|
+
*
|
|
7
|
+
* sectionKey는 unitType.key 값(예: "implement", "refactor")을 사용하며,
|
|
8
|
+
* 영문 소문자 + 숫자 + 하이픈만 허용한다.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
/** 마커 범위 (라인 인덱스 기반, inclusive) */
|
|
13
|
+
export interface MarkerRange {
|
|
14
|
+
/** begin 마커의 라인 인덱스 */
|
|
15
|
+
start: number;
|
|
16
|
+
/** end 마커의 라인 인덱스 */
|
|
17
|
+
end: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* sectionKey가 유효한지 검증한다
|
|
21
|
+
*
|
|
22
|
+
* 허용: 영문 소문자로 시작, 소문자+숫자+하이픈 조합
|
|
23
|
+
*/
|
|
24
|
+
export declare function isValidSectionKey(key: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* begin 마커 문자열을 생성한다
|
|
27
|
+
*
|
|
28
|
+
* @example createBeginMarker('implement') → '<!-- vc:begin:implement -->'
|
|
29
|
+
*/
|
|
30
|
+
export declare function createBeginMarker(sectionKey: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* end 마커 문자열을 생성한다
|
|
33
|
+
*
|
|
34
|
+
* @example createEndMarker('implement') → '<!-- vc:end:implement -->'
|
|
35
|
+
*/
|
|
36
|
+
export declare function createEndMarker(sectionKey: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* 라인 배열에서 지정된 sectionKey의 마커 범위를 찾는다
|
|
39
|
+
*
|
|
40
|
+
* begin 마커를 먼저 찾고, 이후 나타나는 첫 번째 end 마커를 매칭한다.
|
|
41
|
+
* 마커가 없거나 begin만 있고 end가 없으면 null을 반환한다.
|
|
42
|
+
*
|
|
43
|
+
* @param lines - 파일 콘텐츠를 줄 단위로 분할한 배열
|
|
44
|
+
* @param sectionKey - 탐색할 섹션 키 (예: "implement")
|
|
45
|
+
* @returns 마커 범위 (라인 인덱스) 또는 null
|
|
46
|
+
*/
|
|
47
|
+
export declare function findMarkerRange(lines: readonly string[], sectionKey: string): MarkerRange | null;
|
|
48
|
+
/**
|
|
49
|
+
* 내부 콘텐츠 라인들을 마커로 래핑한다
|
|
50
|
+
*
|
|
51
|
+
* @param sectionKey - 섹션 키
|
|
52
|
+
* @param innerLines - 마커로 감쌀 라인 배열
|
|
53
|
+
* @returns 마커를 포함한 라인 배열
|
|
54
|
+
*/
|
|
55
|
+
export declare function wrapWithMarkers(sectionKey: string, innerLines: readonly string[]): string[];
|
|
56
|
+
/**
|
|
57
|
+
* 콘텐츠에서 모든 마커 라인을 제거한다 (테스트/디버그용)
|
|
58
|
+
*/
|
|
59
|
+
export declare function stripMarkers(content: string): string;
|
|
60
|
+
//# sourceMappingURL=marker-utils.d.ts.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML 주석 마커 유틸리티 — Layer 1 순수 함수
|
|
3
|
+
*
|
|
4
|
+
* 커맨드 파일 섹션 경계를 안전하게 식별하기 위한
|
|
5
|
+
* `<!-- vc:begin:{key} -->` / `<!-- vc:end:{key} -->` 마커를 생성/탐색한다.
|
|
6
|
+
*
|
|
7
|
+
* sectionKey는 unitType.key 값(예: "implement", "refactor")을 사용하며,
|
|
8
|
+
* 영문 소문자 + 숫자 + 하이픈만 허용한다.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
import { stripCR } from '../parsers/md-utils.js';
|
|
13
|
+
/** sectionKey 허용 패턴: 영문 소문자 시작, 소문자+숫자+하이픈 */
|
|
14
|
+
const SECTION_KEY_RE = /^[a-z][a-z0-9-]*$/;
|
|
15
|
+
/** 마커 라인 매칭 정규식 */
|
|
16
|
+
const MARKER_LINE_RE = /^<!--\s*vc:(begin|end):([a-z][a-z0-9-]*)\s*-->$/;
|
|
17
|
+
/**
|
|
18
|
+
* sectionKey가 유효한지 검증한다
|
|
19
|
+
*
|
|
20
|
+
* 허용: 영문 소문자로 시작, 소문자+숫자+하이픈 조합
|
|
21
|
+
*/
|
|
22
|
+
export function isValidSectionKey(key) {
|
|
23
|
+
return SECTION_KEY_RE.test(key);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* begin 마커 문자열을 생성한다
|
|
27
|
+
*
|
|
28
|
+
* @example createBeginMarker('implement') → '<!-- vc:begin:implement -->'
|
|
29
|
+
*/
|
|
30
|
+
export function createBeginMarker(sectionKey) {
|
|
31
|
+
return `<!-- vc:begin:${sectionKey} -->`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* end 마커 문자열을 생성한다
|
|
35
|
+
*
|
|
36
|
+
* @example createEndMarker('implement') → '<!-- vc:end:implement -->'
|
|
37
|
+
*/
|
|
38
|
+
export function createEndMarker(sectionKey) {
|
|
39
|
+
return `<!-- vc:end:${sectionKey} -->`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 라인 배열에서 지정된 sectionKey의 마커 범위를 찾는다
|
|
43
|
+
*
|
|
44
|
+
* begin 마커를 먼저 찾고, 이후 나타나는 첫 번째 end 마커를 매칭한다.
|
|
45
|
+
* 마커가 없거나 begin만 있고 end가 없으면 null을 반환한다.
|
|
46
|
+
*
|
|
47
|
+
* @param lines - 파일 콘텐츠를 줄 단위로 분할한 배열
|
|
48
|
+
* @param sectionKey - 탐색할 섹션 키 (예: "implement")
|
|
49
|
+
* @returns 마커 범위 (라인 인덱스) 또는 null
|
|
50
|
+
*/
|
|
51
|
+
export function findMarkerRange(lines, sectionKey) {
|
|
52
|
+
const beginMarker = createBeginMarker(sectionKey);
|
|
53
|
+
const endMarker = createEndMarker(sectionKey);
|
|
54
|
+
let startIdx = -1;
|
|
55
|
+
for (let i = 0; i < lines.length; i++) {
|
|
56
|
+
const trimmed = stripCR(lines[i] ?? '').trim();
|
|
57
|
+
if (trimmed === beginMarker && startIdx === -1) {
|
|
58
|
+
startIdx = i;
|
|
59
|
+
}
|
|
60
|
+
else if (trimmed === endMarker && startIdx !== -1) {
|
|
61
|
+
return { start: startIdx, end: i };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 내부 콘텐츠 라인들을 마커로 래핑한다
|
|
68
|
+
*
|
|
69
|
+
* @param sectionKey - 섹션 키
|
|
70
|
+
* @param innerLines - 마커로 감쌀 라인 배열
|
|
71
|
+
* @returns 마커를 포함한 라인 배열
|
|
72
|
+
*/
|
|
73
|
+
export function wrapWithMarkers(sectionKey, innerLines) {
|
|
74
|
+
return [createBeginMarker(sectionKey), ...innerLines, createEndMarker(sectionKey)];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 콘텐츠에서 모든 마커 라인을 제거한다 (테스트/디버그용)
|
|
78
|
+
*/
|
|
79
|
+
export function stripMarkers(content) {
|
|
80
|
+
return content
|
|
81
|
+
.split('\n')
|
|
82
|
+
.filter((line) => !MARKER_LINE_RE.test(stripCR(line).trim()))
|
|
83
|
+
.join('\n');
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=marker-utils.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 커맨드 파일 섹션 렌더러 — Layer 1 순수 함수
|
|
3
|
+
*
|
|
4
|
+
* CommandContext와 UnitTypeConfig를 받아
|
|
5
|
+
* 커맨드 파일에 삽입할 섹션 본문을 생성한다.
|
|
6
|
+
*
|
|
7
|
+
* 렌더링 결과는 separator를 포함하지 않으며,
|
|
8
|
+
* 섹션 헤더(예: "# 유닛 구현")도 포함하지 않는다.
|
|
9
|
+
*
|
|
10
|
+
* 형식 레퍼런스: vibe/commands.md
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import type { CommandContext, UnitTypeConfig } from '../../types/index.js';
|
|
15
|
+
/**
|
|
16
|
+
* 커맨드 파일 섹션을 렌더링한다
|
|
17
|
+
*
|
|
18
|
+
* 렌더링 순서:
|
|
19
|
+
* 1. headerTemplate 보간 → 헤더 블록
|
|
20
|
+
* 2. 의존 유닛 목록 (collectDeps=true일 때)
|
|
21
|
+
* 3. 의존성 문서 경로 (collectDeps=true일 때)
|
|
22
|
+
* 4. 의존성 커밋 SHA (collectDeps=true일 때)
|
|
23
|
+
* 5. 특별 요청사항 (항상)
|
|
24
|
+
*
|
|
25
|
+
* @param context - 렌더링할 전체 컨텍스트 (유닛 메타 + 의존성 + 특별 요청)
|
|
26
|
+
* @param unitTypeConfig - 유닛 유형 설정 (headerTemplate, collectDeps 등)
|
|
27
|
+
* @param docPrefix - 문서 경로 앞에 붙일 프리픽스 (기본: "@")
|
|
28
|
+
* @returns 렌더링된 섹션 본문 문자열
|
|
29
|
+
*/
|
|
30
|
+
export declare function renderSection(context: CommandContext, unitTypeConfig: UnitTypeConfig, docPrefix?: string): string;
|
|
31
|
+
//# sourceMappingURL=section-renderer.d.ts.map
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 커맨드 파일 섹션 렌더러 — Layer 1 순수 함수
|
|
3
|
+
*
|
|
4
|
+
* CommandContext와 UnitTypeConfig를 받아
|
|
5
|
+
* 커맨드 파일에 삽입할 섹션 본문을 생성한다.
|
|
6
|
+
*
|
|
7
|
+
* 렌더링 결과는 separator를 포함하지 않으며,
|
|
8
|
+
* 섹션 헤더(예: "# 유닛 구현")도 포함하지 않는다.
|
|
9
|
+
*
|
|
10
|
+
* 형식 레퍼런스: vibe/commands.md
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { interpolateTemplate } from './template-engine.js';
|
|
15
|
+
/** 문서 경로 앞에 붙일 기본 프리픽스 */
|
|
16
|
+
const DEFAULT_DOC_PREFIX = '@';
|
|
17
|
+
/** 빈 항목 표시 마커 */
|
|
18
|
+
const EMPTY_MARKER = '-';
|
|
19
|
+
/**
|
|
20
|
+
* 커맨드 파일 섹션을 렌더링한다
|
|
21
|
+
*
|
|
22
|
+
* 렌더링 순서:
|
|
23
|
+
* 1. headerTemplate 보간 → 헤더 블록
|
|
24
|
+
* 2. 의존 유닛 목록 (collectDeps=true일 때)
|
|
25
|
+
* 3. 의존성 문서 경로 (collectDeps=true일 때)
|
|
26
|
+
* 4. 의존성 커밋 SHA (collectDeps=true일 때)
|
|
27
|
+
* 5. 특별 요청사항 (항상)
|
|
28
|
+
*
|
|
29
|
+
* @param context - 렌더링할 전체 컨텍스트 (유닛 메타 + 의존성 + 특별 요청)
|
|
30
|
+
* @param unitTypeConfig - 유닛 유형 설정 (headerTemplate, collectDeps 등)
|
|
31
|
+
* @param docPrefix - 문서 경로 앞에 붙일 프리픽스 (기본: "@")
|
|
32
|
+
* @returns 렌더링된 섹션 본문 문자열
|
|
33
|
+
*/
|
|
34
|
+
export function renderSection(context, unitTypeConfig, docPrefix = DEFAULT_DOC_PREFIX) {
|
|
35
|
+
const lines = [];
|
|
36
|
+
// 1. Header block (headerTemplate 변수 보간)
|
|
37
|
+
const header = interpolateTemplate(unitTypeConfig.headerTemplate, context.unit);
|
|
38
|
+
lines.push(header);
|
|
39
|
+
// 2. 의존성 섹션 (collectDeps=true일 때만)
|
|
40
|
+
if (unitTypeConfig.collectDeps) {
|
|
41
|
+
renderDepUnits(lines, context);
|
|
42
|
+
renderDepDocs(lines, context, docPrefix);
|
|
43
|
+
renderDepCommits(lines, context);
|
|
44
|
+
}
|
|
45
|
+
// 3. 특별 요청사항 (항상 렌더링)
|
|
46
|
+
renderSpecialRequests(lines, context);
|
|
47
|
+
return lines.join('\n');
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 의존 유닛 목록과 비유닛 컨텍스트 항목을 렌더링한다
|
|
51
|
+
*
|
|
52
|
+
* 유닛 항목: `- **{unitId}**: {description}`
|
|
53
|
+
* 비유닛 항목: `- {description}` (볼드 유닛 ID 없이)
|
|
54
|
+
*/
|
|
55
|
+
function renderDepUnits(lines, context) {
|
|
56
|
+
lines.push('');
|
|
57
|
+
lines.push('### 의존 유닛 및 기능:');
|
|
58
|
+
const contextNotes = context.unit.contextNotes ?? [];
|
|
59
|
+
const hasContent = context.unit.depends.length > 0 || contextNotes.length > 0;
|
|
60
|
+
if (hasContent) {
|
|
61
|
+
for (const dep of context.unit.depends) {
|
|
62
|
+
lines.push(`- **${dep.unitId}**: ${dep.description}`);
|
|
63
|
+
}
|
|
64
|
+
for (const note of contextNotes) {
|
|
65
|
+
lines.push(`- ${note}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
lines.push(EMPTY_MARKER);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 의존성 문서 경로를 렌더링한다
|
|
74
|
+
*
|
|
75
|
+
* 형식: `- @{path}` (docPrefix 제어 가능)
|
|
76
|
+
*/
|
|
77
|
+
function renderDepDocs(lines, context, docPrefix) {
|
|
78
|
+
lines.push('');
|
|
79
|
+
lines.push('### 의존성 문서:');
|
|
80
|
+
const allDocs = context.depDocs.flatMap((d) => d.found);
|
|
81
|
+
if (allDocs.length > 0) {
|
|
82
|
+
for (const doc of allDocs) {
|
|
83
|
+
lines.push(`- ${docPrefix}${doc.path}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
lines.push(EMPTY_MARKER);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 의존성 커밋 SHA를 렌더링한다
|
|
92
|
+
*
|
|
93
|
+
* 형식: `- {sha}`
|
|
94
|
+
*/
|
|
95
|
+
function renderDepCommits(lines, context) {
|
|
96
|
+
lines.push('');
|
|
97
|
+
lines.push('### 의존성 Commits:');
|
|
98
|
+
if (context.depCommits.length > 0) {
|
|
99
|
+
for (const sha of context.depCommits) {
|
|
100
|
+
lines.push(`- ${sha}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
lines.push(EMPTY_MARKER);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 특별 요청사항을 렌더링한다
|
|
109
|
+
*
|
|
110
|
+
* 형식: `- {request}`
|
|
111
|
+
*
|
|
112
|
+
* 페어링 질문 확인 항목("페어링 질문의 결정을 확인합니다")이 있을 때,
|
|
113
|
+
* 모든 미결정 질문이 답변되었으면 자동으로 ✅를 추가한다.
|
|
114
|
+
* 답변된 페어링 질문은 특별 요청사항 뒤에 체크리스트 형태로 렌더링.
|
|
115
|
+
*/
|
|
116
|
+
function renderSpecialRequests(lines, context) {
|
|
117
|
+
lines.push('');
|
|
118
|
+
lines.push('### 특별 요청사항');
|
|
119
|
+
const allAnswered = areAllQuestionsAnswered(context);
|
|
120
|
+
if (context.specialRequests.length > 0) {
|
|
121
|
+
for (const req of context.specialRequests) {
|
|
122
|
+
if (allAnswered && isPairingConfirmItem(req) && !req.includes('✅')) {
|
|
123
|
+
lines.push(`- ${req}✅`);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
lines.push(`- ${req}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
lines.push(EMPTY_MARKER);
|
|
132
|
+
}
|
|
133
|
+
renderPairingAnswers(lines, context);
|
|
134
|
+
}
|
|
135
|
+
/** "페어링 질문의 결정을 확인합니다" 항목인지 판별 */
|
|
136
|
+
function isPairingConfirmItem(text) {
|
|
137
|
+
return text.includes('페어링 질문의 결정을 확인합니다');
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 모든 미결정 페어링 질문이 답변되었는지 확인
|
|
141
|
+
*
|
|
142
|
+
* 미결정 질문이 0개이면 false (확인할 필요 없음),
|
|
143
|
+
* pairingAnswers 수가 미결정 질문 수 이상이면 true.
|
|
144
|
+
*/
|
|
145
|
+
function areAllQuestionsAnswered(context) {
|
|
146
|
+
const unresolvedCount = context.pairingQuestions.filter((q) => !q.resolved).length;
|
|
147
|
+
if (unresolvedCount === 0)
|
|
148
|
+
return false;
|
|
149
|
+
const answerCount = context.pairingAnswers?.length ?? 0;
|
|
150
|
+
return answerCount >= unresolvedCount;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 페어링 질문과 답변을 렌더링한다
|
|
154
|
+
*
|
|
155
|
+
* 미결정 질문: `- [ ] **Q1**: 질문`
|
|
156
|
+
* 답변된 질문: `- [x] **Q1**: 질문 → **답변: {선택/입력값}**`
|
|
157
|
+
*/
|
|
158
|
+
function renderPairingAnswers(lines, context) {
|
|
159
|
+
const unresolvedQuestions = context.pairingQuestions.filter((q) => !q.resolved);
|
|
160
|
+
if (unresolvedQuestions.length === 0)
|
|
161
|
+
return;
|
|
162
|
+
const answers = context.pairingAnswers ?? [];
|
|
163
|
+
for (const question of unresolvedQuestions) {
|
|
164
|
+
const answer = answers.find((a) => a.questionId === question.id);
|
|
165
|
+
if (answer) {
|
|
166
|
+
const answerText = answer.selectedOption ?? answer.freeText ?? '';
|
|
167
|
+
lines.push(`- [x] **${question.id}**: ${question.text} → **답변: ${answerText}**`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=section-renderer.js.map
|