@tienne/gestalt 0.99.0 → 0.101.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 (157) hide show
  1. package/CLAUDE.md +6 -1
  2. package/dist/package.json +5 -3
  3. package/dist/plugin/research-sources/amplitude/SOURCE.md +28 -0
  4. package/dist/plugin/research-sources/app-store-reviews/SOURCE.md +25 -0
  5. package/dist/plugin/research-sources/app-store-search/SOURCE.md +25 -0
  6. package/dist/plugin/research-sources/tech-blog-search/SOURCE.md +27 -0
  7. package/dist/plugin/research-sources/uibowl/SOURCE.md +26 -0
  8. package/dist/plugin/research-sources/web-search/SOURCE.md +24 -0
  9. package/dist/plugin/skills/_shared/agent-delegation.md +30 -7
  10. package/dist/plugin/skills/_shared/agent-model.md +3 -1
  11. package/dist/plugin/skills/_shared/proactive-routing.md +5 -0
  12. package/dist/plugin/skills/brief/SKILL.md +5 -7
  13. package/dist/plugin/skills/explain/SKILL.md +4 -5
  14. package/dist/plugin/skills/jira-create/SKILL.md +3 -4
  15. package/dist/plugin/skills/market-scan/SKILL.md +61 -0
  16. package/dist/plugin/skills/pr/SKILL.md +4 -6
  17. package/dist/plugin/skills/presentation/SKILL.md +18 -16
  18. package/dist/plugin/skills/research/SKILL.md +438 -0
  19. package/dist/plugin/skills/research/references/design.md +938 -0
  20. package/dist/plugin/skills/research/references/output-template.md +366 -0
  21. package/dist/plugin/skills/research/scripts/pii-vision.swift +327 -0
  22. package/dist/plugin/skills/research/scripts/slide-extract.mjs +223 -0
  23. package/dist/plugin/skills/reverse-plan/SKILL.md +52 -0
  24. package/dist/plugin/skills/review/SKILL.md +20 -25
  25. package/dist/plugin/skills/review-reply/SKILL.md +4 -5
  26. package/dist/plugin/skills/screen-ref/SKILL.md +63 -0
  27. package/dist/plugin/skills/slack-send/SKILL.md +3 -4
  28. package/dist/plugin/skills/ux-research/SKILL.md +51 -0
  29. package/dist/src/core/config.d.ts +3 -0
  30. package/dist/src/core/config.d.ts.map +1 -1
  31. package/dist/src/core/config.js +4 -0
  32. package/dist/src/core/config.js.map +1 -1
  33. package/dist/src/mcp/schemas.d.ts +93 -0
  34. package/dist/src/mcp/schemas.d.ts.map +1 -1
  35. package/dist/src/mcp/schemas.js +110 -0
  36. package/dist/src/mcp/schemas.js.map +1 -1
  37. package/dist/src/mcp/server.d.ts.map +1 -1
  38. package/dist/src/mcp/server.js +11 -1
  39. package/dist/src/mcp/server.js.map +1 -1
  40. package/dist/src/mcp/tools/research-passthrough.d.ts +21 -0
  41. package/dist/src/mcp/tools/research-passthrough.d.ts.map +1 -0
  42. package/dist/src/mcp/tools/research-passthrough.js +566 -0
  43. package/dist/src/mcp/tools/research-passthrough.js.map +1 -0
  44. package/dist/src/research/canvas-view.d.ts +5 -0
  45. package/dist/src/research/canvas-view.d.ts.map +1 -0
  46. package/dist/src/research/canvas-view.js +274 -0
  47. package/dist/src/research/canvas-view.js.map +1 -0
  48. package/dist/src/research/evidence.d.ts +145 -0
  49. package/dist/src/research/evidence.d.ts.map +1 -0
  50. package/dist/src/research/evidence.js +50 -0
  51. package/dist/src/research/evidence.js.map +1 -0
  52. package/dist/src/research/figma-plan.d.ts +80 -0
  53. package/dist/src/research/figma-plan.d.ts.map +1 -0
  54. package/dist/src/research/figma-plan.js +527 -0
  55. package/dist/src/research/figma-plan.js.map +1 -0
  56. package/dist/src/research/figma-script.d.ts +19 -0
  57. package/dist/src/research/figma-script.d.ts.map +1 -0
  58. package/dist/src/research/figma-script.js +363 -0
  59. package/dist/src/research/figma-script.js.map +1 -0
  60. package/dist/src/research/index.d.ts +8 -0
  61. package/dist/src/research/index.d.ts.map +1 -0
  62. package/dist/src/research/index.js +8 -0
  63. package/dist/src/research/index.js.map +1 -0
  64. package/dist/src/research/pii-core.d.ts +16 -0
  65. package/dist/src/research/pii-core.d.ts.map +1 -0
  66. package/dist/src/research/pii-core.js +345 -0
  67. package/dist/src/research/pii-core.js.map +1 -0
  68. package/dist/src/research/pii-dom.d.ts +13 -0
  69. package/dist/src/research/pii-dom.d.ts.map +1 -0
  70. package/dist/src/research/pii-dom.js +208 -0
  71. package/dist/src/research/pii-dom.js.map +1 -0
  72. package/dist/src/research/pii-image.d.ts +71 -0
  73. package/dist/src/research/pii-image.d.ts.map +1 -0
  74. package/dist/src/research/pii-image.js +163 -0
  75. package/dist/src/research/pii-image.js.map +1 -0
  76. package/dist/src/research/pii.d.ts +21 -0
  77. package/dist/src/research/pii.d.ts.map +1 -0
  78. package/dist/src/research/pii.js +47 -0
  79. package/dist/src/research/pii.js.map +1 -0
  80. package/dist/src/research/product-profile.d.ts +108 -0
  81. package/dist/src/research/product-profile.d.ts.map +1 -0
  82. package/dist/src/research/product-profile.js +104 -0
  83. package/dist/src/research/product-profile.js.map +1 -0
  84. package/dist/src/research/render-html.d.ts +203 -0
  85. package/dist/src/research/render-html.d.ts.map +1 -0
  86. package/dist/src/research/render-html.js +901 -0
  87. package/dist/src/research/render-html.js.map +1 -0
  88. package/dist/src/research/schema-base.d.ts +5 -0
  89. package/dist/src/research/schema-base.d.ts.map +1 -0
  90. package/dist/src/research/schema-base.js +10 -0
  91. package/dist/src/research/schema-base.js.map +1 -0
  92. package/dist/src/research/schema.d.ts +5142 -0
  93. package/dist/src/research/schema.d.ts.map +1 -0
  94. package/dist/src/research/schema.js +991 -0
  95. package/dist/src/research/schema.js.map +1 -0
  96. package/dist/src/research/source-create.d.ts +79 -0
  97. package/dist/src/research/source-create.d.ts.map +1 -0
  98. package/dist/src/research/source-create.js +226 -0
  99. package/dist/src/research/source-create.js.map +1 -0
  100. package/dist/src/research/source-registry.d.ts +29 -0
  101. package/dist/src/research/source-registry.d.ts.map +1 -0
  102. package/dist/src/research/source-registry.js +74 -0
  103. package/dist/src/research/source-registry.js.map +1 -0
  104. package/dist/src/research/source-schema.d.ts +158 -0
  105. package/dist/src/research/source-schema.d.ts.map +1 -0
  106. package/dist/src/research/source-schema.js +215 -0
  107. package/dist/src/research/source-schema.js.map +1 -0
  108. package/dist/src/research/source-status.d.ts +40 -0
  109. package/dist/src/research/source-status.d.ts.map +1 -0
  110. package/dist/src/research/source-status.js +109 -0
  111. package/dist/src/research/source-status.js.map +1 -0
  112. package/dist/src/research/store.d.ts +24 -0
  113. package/dist/src/research/store.d.ts.map +1 -0
  114. package/dist/src/research/store.js +60 -0
  115. package/dist/src/research/store.js.map +1 -0
  116. package/dist/src/research/target-view.d.ts +33 -0
  117. package/dist/src/research/target-view.d.ts.map +1 -0
  118. package/dist/src/research/target-view.js +133 -0
  119. package/dist/src/research/target-view.js.map +1 -0
  120. package/dist/src/research/target.d.ts +883 -0
  121. package/dist/src/research/target.d.ts.map +1 -0
  122. package/dist/src/research/target.js +323 -0
  123. package/dist/src/research/target.js.map +1 -0
  124. package/dist/src/research/video-transfer.d.ts +28 -0
  125. package/dist/src/research/video-transfer.d.ts.map +1 -0
  126. package/dist/src/research/video-transfer.js +100 -0
  127. package/dist/src/research/video-transfer.js.map +1 -0
  128. package/package.json +5 -3
  129. package/plugin/.codex-plugin/plugin.json +1 -1
  130. package/plugin/.mcp.json +1 -1
  131. package/plugin/mcp.json +1 -1
  132. package/plugin/research-sources/amplitude/SOURCE.md +28 -0
  133. package/plugin/research-sources/app-store-reviews/SOURCE.md +25 -0
  134. package/plugin/research-sources/app-store-search/SOURCE.md +25 -0
  135. package/plugin/research-sources/tech-blog-search/SOURCE.md +27 -0
  136. package/plugin/research-sources/uibowl/SOURCE.md +26 -0
  137. package/plugin/research-sources/web-search/SOURCE.md +24 -0
  138. package/plugin/skills/_shared/agent-delegation.md +30 -7
  139. package/plugin/skills/_shared/agent-model.md +3 -1
  140. package/plugin/skills/_shared/proactive-routing.md +5 -0
  141. package/plugin/skills/brief/SKILL.md +5 -7
  142. package/plugin/skills/explain/SKILL.md +4 -5
  143. package/plugin/skills/jira-create/SKILL.md +3 -4
  144. package/plugin/skills/market-scan/SKILL.md +61 -0
  145. package/plugin/skills/pr/SKILL.md +4 -6
  146. package/plugin/skills/presentation/SKILL.md +18 -16
  147. package/plugin/skills/research/SKILL.md +438 -0
  148. package/plugin/skills/research/references/design.md +938 -0
  149. package/plugin/skills/research/references/output-template.md +366 -0
  150. package/plugin/skills/research/scripts/pii-vision.swift +327 -0
  151. package/plugin/skills/research/scripts/slide-extract.mjs +223 -0
  152. package/plugin/skills/reverse-plan/SKILL.md +52 -0
  153. package/plugin/skills/review/SKILL.md +20 -25
  154. package/plugin/skills/review-reply/SKILL.md +4 -5
  155. package/plugin/skills/screen-ref/SKILL.md +63 -0
  156. package/plugin/skills/slack-send/SKILL.md +3 -4
  157. package/plugin/skills/ux-research/SKILL.md +51 -0
@@ -0,0 +1,158 @@
1
+ import { z } from 'zod';
2
+ import { ValidationError } from '../core/errors.js';
3
+ import { type Result } from '../core/result.js';
4
+ export declare const SOURCE_STAGES: readonly ["product-discovery", "scale", "screen-map", "evidence", "baseline"];
5
+ export declare const SOURCE_ACCESS_KINDS: readonly ["mcp", "browser", "http", "env"];
6
+ export declare const SOURCE_EVIDENCE: readonly ["observation", "company-statement", "interpretation", "estimate"];
7
+ export declare const SOURCE_AXES: readonly ["same-market", "same-problem"];
8
+ export type SourceStage = (typeof SOURCE_STAGES)[number];
9
+ export type SourceAccessKind = (typeof SOURCE_ACCESS_KINDS)[number];
10
+ /** 파일 하나의 크기 상한. 개인 디렉토리라도 통째로 읽으므로 막아둔다 */
11
+ export declare const SOURCE_MAX_BYTES: number;
12
+ /** 사설 주소로 가는 길을 막는다. 숫자 주소와 점 없는 이름은 정규식에서 이미 걸러진다 */
13
+ export declare function isPublicHostname(host: string): boolean;
14
+ /** 비밀번호나 키가 SOURCE.md에 들어가는 걸 막는다. 환경변수 이름만 적는다 */
15
+ export declare function findSecret(text: string): string | undefined;
16
+ export declare const sourceFrontmatterSchema: z.ZodEffects<z.ZodObject<{
17
+ name: z.ZodString;
18
+ description: z.ZodString;
19
+ stage: z.ZodEffects<z.ZodArray<z.ZodEnum<["product-discovery", "scale", "screen-map", "evidence", "baseline"]>, "many">, ("evidence" | "product-discovery" | "scale" | "screen-map" | "baseline")[], unknown>;
20
+ axis: z.ZodEffects<z.ZodDefault<z.ZodArray<z.ZodEnum<["same-market", "same-problem"]>, "many">>, ("same-market" | "same-problem")[], unknown>;
21
+ access: z.ZodArray<z.ZodEffects<z.ZodUnknown, {
22
+ server?: string | undefined;
23
+ tools?: string[] | undefined;
24
+ kind: "mcp";
25
+ name?: undefined;
26
+ } | {
27
+ site: string;
28
+ login?: "none" | "human" | undefined;
29
+ kind: "browser";
30
+ name?: undefined;
31
+ } | {
32
+ host: string;
33
+ probe?: string | undefined;
34
+ kind: "http";
35
+ name?: undefined;
36
+ } | {
37
+ kind: "env";
38
+ name: string;
39
+ }, unknown>, "many">;
40
+ requires: z.ZodDefault<z.ZodObject<{
41
+ mcpServer: z.ZodOptional<z.ZodString>;
42
+ env: z.ZodEffects<z.ZodDefault<z.ZodArray<z.ZodString, "many">>, string[], unknown>;
43
+ browserLogin: z.ZodOptional<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>>;
44
+ }, "strict", z.ZodTypeAny, {
45
+ env: string[];
46
+ mcpServer?: string | undefined;
47
+ browserLogin?: string | undefined;
48
+ }, {
49
+ env?: unknown;
50
+ mcpServer?: string | undefined;
51
+ browserLogin?: string | undefined;
52
+ }>>;
53
+ /** estimate는 제3자 추정치라 근거 상태에서는 해석으로 받는다 */
54
+ evidence: z.ZodEffects<z.ZodDefault<z.ZodEnum<["observation", "company-statement", "interpretation", "estimate"]>>, "observation" | "company-statement" | "interpretation", "observation" | "company-statement" | "interpretation" | "estimate" | undefined>;
55
+ limits: z.ZodOptional<z.ZodString>;
56
+ /** 이 출처를 못 쓸 때 대신 쓰는 방법. 0번 전제의 근거 한계 문장에 들어간다 */
57
+ fallback: z.ZodOptional<z.ZodString>;
58
+ }, "strip", z.ZodTypeAny, {
59
+ name: string;
60
+ description: string;
61
+ requires: {
62
+ env: string[];
63
+ mcpServer?: string | undefined;
64
+ browserLogin?: string | undefined;
65
+ };
66
+ stage: ("evidence" | "product-discovery" | "scale" | "screen-map" | "baseline")[];
67
+ evidence: "observation" | "company-statement" | "interpretation";
68
+ axis: ("same-market" | "same-problem")[];
69
+ access: ({
70
+ server?: string | undefined;
71
+ tools?: string[] | undefined;
72
+ kind: "mcp";
73
+ name?: undefined;
74
+ } | {
75
+ site: string;
76
+ login?: "none" | "human" | undefined;
77
+ kind: "browser";
78
+ name?: undefined;
79
+ } | {
80
+ host: string;
81
+ probe?: string | undefined;
82
+ kind: "http";
83
+ name?: undefined;
84
+ } | {
85
+ kind: "env";
86
+ name: string;
87
+ })[];
88
+ limits?: string | undefined;
89
+ fallback?: string | undefined;
90
+ }, {
91
+ name: string;
92
+ description: string;
93
+ access: unknown[];
94
+ requires?: {
95
+ env?: unknown;
96
+ mcpServer?: string | undefined;
97
+ browserLogin?: string | undefined;
98
+ } | undefined;
99
+ stage?: unknown;
100
+ evidence?: "observation" | "company-statement" | "interpretation" | "estimate" | undefined;
101
+ axis?: unknown;
102
+ limits?: string | undefined;
103
+ fallback?: string | undefined;
104
+ }>, {
105
+ name: string;
106
+ description: string;
107
+ requires: {
108
+ env: string[];
109
+ mcpServer?: string | undefined;
110
+ browserLogin?: string | undefined;
111
+ };
112
+ stage: ("evidence" | "product-discovery" | "scale" | "screen-map" | "baseline")[];
113
+ evidence: "observation" | "company-statement" | "interpretation";
114
+ axis: ("same-market" | "same-problem")[];
115
+ access: ({
116
+ server?: string | undefined;
117
+ tools?: string[] | undefined;
118
+ kind: "mcp";
119
+ name?: undefined;
120
+ } | {
121
+ site: string;
122
+ login?: "none" | "human" | undefined;
123
+ kind: "browser";
124
+ name?: undefined;
125
+ } | {
126
+ host: string;
127
+ probe?: string | undefined;
128
+ kind: "http";
129
+ name?: undefined;
130
+ } | {
131
+ kind: "env";
132
+ name: string;
133
+ })[];
134
+ limits?: string | undefined;
135
+ fallback?: string | undefined;
136
+ }, {
137
+ name: string;
138
+ description: string;
139
+ access: unknown[];
140
+ requires?: {
141
+ env?: unknown;
142
+ mcpServer?: string | undefined;
143
+ browserLogin?: string | undefined;
144
+ } | undefined;
145
+ stage?: unknown;
146
+ evidence?: "observation" | "company-statement" | "interpretation" | "estimate" | undefined;
147
+ axis?: unknown;
148
+ limits?: string | undefined;
149
+ fallback?: string | undefined;
150
+ }>;
151
+ export type SourceFrontmatter = z.infer<typeof sourceFrontmatterSchema>;
152
+ export type SourceAccess = SourceFrontmatter['access'][number];
153
+ export interface SourceDefinition {
154
+ frontmatter: SourceFrontmatter;
155
+ body: string;
156
+ }
157
+ export declare function parseSourceMd(content: string): Result<SourceDefinition, ValidationError>;
158
+ //# sourceMappingURL=source-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-schema.d.ts","sourceRoot":"","sources":["../../../src/research/source-schema.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEzD,eAAO,MAAM,aAAa,+EAMhB,CAAC;AACX,eAAO,MAAM,mBAAmB,4CAA6C,CAAC;AAC9E,eAAO,MAAM,eAAe,6EAKlB,CAAC;AACX,eAAO,MAAM,WAAW,0CAA2C,CAAC;AAEpE,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AACzD,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE,4CAA4C;AAC5C,eAAO,MAAM,gBAAgB,QAAY,CAAC;AAkB1C,sDAAsD;AACtD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGtD;AAgBD,mDAAmD;AACnD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK3D;AA4ED,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAehC,2CAA2C;;;IAM3C,kDAAkD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqBlD,CAAC;AAEL,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AACxE,MAAM,MAAM,YAAY,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/D,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,iBAAiB,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;CACd;AAgBD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,gBAAgB,EAAE,eAAe,CAAC,CA0CxF"}
@@ -0,0 +1,215 @@
1
+ import matter from 'gray-matter';
2
+ import { z } from 'zod';
3
+ import { ValidationError } from '../core/errors.js';
4
+ import { err, ok } from '../core/result.js';
5
+ export const SOURCE_STAGES = [
6
+ 'product-discovery',
7
+ 'scale',
8
+ 'screen-map',
9
+ 'evidence',
10
+ 'baseline',
11
+ ];
12
+ export const SOURCE_ACCESS_KINDS = ['mcp', 'browser', 'http', 'env'];
13
+ export const SOURCE_EVIDENCE = [
14
+ 'observation',
15
+ 'company-statement',
16
+ 'interpretation',
17
+ 'estimate',
18
+ ];
19
+ export const SOURCE_AXES = ['same-market', 'same-problem'];
20
+ /** 파일 하나의 크기 상한. 개인 디렉토리라도 통째로 읽으므로 막아둔다 */
21
+ export const SOURCE_MAX_BYTES = 64 * 1024;
22
+ const NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,39}$/;
23
+ const ENV_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/;
24
+ /** `claude.ai Amplitude`처럼 커넥터 이름에 공백이 들어간다. 연속 공백과 끝 공백은 막는다 */
25
+ const MCP_SERVER_PATTERN = /^(?!.* {2})(?!.* $)[A-Za-z0-9][A-Za-z0-9_.: -]{0,63}$/;
26
+ const MCP_TOOL_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
27
+ const HOST_PATTERN = /^(?=.{1,253}$)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/;
28
+ const PRIVATE_HOST_SUFFIXES = [
29
+ '.local',
30
+ '.internal',
31
+ '.localhost',
32
+ '.lan',
33
+ '.corp',
34
+ '.home',
35
+ '.intranet',
36
+ ];
37
+ /** 사설 주소로 가는 길을 막는다. 숫자 주소와 점 없는 이름은 정규식에서 이미 걸러진다 */
38
+ export function isPublicHostname(host) {
39
+ const h = host.toLowerCase();
40
+ return HOST_PATTERN.test(h) && !PRIVATE_HOST_SUFFIXES.some((s) => h.endsWith(s));
41
+ }
42
+ const SECRET_PATTERNS = [
43
+ [/\b(?:sk|pk|rk)-[A-Za-z0-9_-]{16,}/, 'API 키로 보이는 문자열'],
44
+ [/\b(?:ghp|gho|ghu|ghs|github_pat)_[A-Za-z0-9_]{16,}/, 'GitHub 토큰으로 보이는 문자열'],
45
+ [/\bxox[abprs]-[A-Za-z0-9-]{10,}/, 'Slack 토큰으로 보이는 문자열'],
46
+ [/\bAKIA[0-9A-Z]{12,}/, 'AWS 키로 보이는 문자열'],
47
+ [/\bAIza[0-9A-Za-z_-]{20,}/, 'Google API 키로 보이는 문자열'],
48
+ [/\bBearer\s+[A-Za-z0-9._~+/-]{16,}/i, 'Bearer 토큰'],
49
+ [/https?:\/\/[^\s/:@]+:[^\s/@]+@/, '주소에 박힌 계정과 비밀번호'],
50
+ [
51
+ /(?:password|passwd|secret|token|api[_-]?key)\s*[:=]\s*["']?[A-Za-z0-9+/=._-]{12,}/i,
52
+ '비밀번호나 키 값',
53
+ ],
54
+ ];
55
+ /** 비밀번호나 키가 SOURCE.md에 들어가는 걸 막는다. 환경변수 이름만 적는다 */
56
+ export function findSecret(text) {
57
+ for (const [pattern, label] of SECRET_PATTERNS) {
58
+ if (pattern.test(text))
59
+ return label;
60
+ }
61
+ return undefined;
62
+ }
63
+ const hostSchema = z
64
+ .string()
65
+ .transform((s) => s.toLowerCase())
66
+ .refine(isPublicHostname, '공개 호스트 이름만 쓴다 (IP 주소, localhost, 사내 이름 불가)');
67
+ const accessEntrySchema = z.unknown().transform((raw, ctx) => {
68
+ const fail = (message) => {
69
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message });
70
+ return z.NEVER;
71
+ };
72
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
73
+ return fail('access 항목은 `mcp: {...}` 같은 한 키짜리 객체다');
74
+ }
75
+ const keys = Object.keys(raw);
76
+ const kind = keys[0];
77
+ if (keys.length !== 1 || !kind || !SOURCE_ACCESS_KINDS.includes(kind)) {
78
+ return fail(`access 방법은 ${SOURCE_ACCESS_KINDS.join(', ')}만 쓴다 (받은 키: ${keys.join(', ') || '없음'})`);
79
+ }
80
+ const value = raw[kind];
81
+ const parse = (schema) => {
82
+ const r = schema.safeParse(value);
83
+ if (r.success)
84
+ return r.data;
85
+ for (const issue of r.error.issues) {
86
+ ctx.addIssue({ ...issue, path: [kind, ...issue.path] });
87
+ }
88
+ return z.NEVER;
89
+ };
90
+ switch (kind) {
91
+ case 'mcp':
92
+ return {
93
+ kind: 'mcp',
94
+ ...parse(z
95
+ .object({
96
+ server: z
97
+ .string()
98
+ .regex(MCP_SERVER_PATTERN, 'MCP 서버 이름 형식이 아니다')
99
+ .optional(),
100
+ /** 서버 이름은 사람마다 달라서 도구 이름으로 알아본다. 하나라도 세션에 있으면 연결된 걸로 본다 */
101
+ tools: z
102
+ .array(z.string().regex(MCP_TOOL_PATTERN, 'MCP 도구 이름 형식이 아니다'))
103
+ .min(1)
104
+ .optional(),
105
+ })
106
+ .strict()
107
+ .refine((m) => m.server || m.tools, 'MCP는 server나 tools 중 하나는 적는다')),
108
+ };
109
+ case 'browser':
110
+ return {
111
+ kind: 'browser',
112
+ ...parse(z
113
+ .object({ site: hostSchema, login: z.enum(['human', 'none']).default('human') })
114
+ .strict()),
115
+ };
116
+ case 'http':
117
+ return {
118
+ kind: 'http',
119
+ ...parse(z.object({ host: hostSchema, probe: z.string().url().optional() }).strict()),
120
+ };
121
+ default:
122
+ return {
123
+ kind: 'env',
124
+ name: parse(z.string().regex(ENV_NAME_PATTERN, '환경변수 이름은 대문자와 _만 쓴다')),
125
+ };
126
+ }
127
+ });
128
+ const toArray = (v) => (v === undefined || Array.isArray(v) ? v : [v]);
129
+ export const sourceFrontmatterSchema = z
130
+ .object({
131
+ name: z.string().regex(NAME_PATTERN, '이름은 소문자, 숫자, -만 쓴다 (40자까지)'),
132
+ description: z.string().min(1),
133
+ stage: z.preprocess(toArray, z.array(z.enum(SOURCE_STAGES)).min(1)),
134
+ axis: z.preprocess(toArray, z.array(z.enum(SOURCE_AXES)).default([])),
135
+ access: z.array(accessEntrySchema).min(1),
136
+ requires: z
137
+ .object({
138
+ mcpServer: z.string().regex(MCP_SERVER_PATTERN).optional(),
139
+ env: z.preprocess(toArray, z.array(z.string().regex(ENV_NAME_PATTERN)).default([])),
140
+ browserLogin: hostSchema.optional(),
141
+ })
142
+ .strict()
143
+ .default({}),
144
+ /** estimate는 제3자 추정치라 근거 상태에서는 해석으로 받는다 */
145
+ evidence: z
146
+ .enum(SOURCE_EVIDENCE)
147
+ .default('observation')
148
+ .transform((e) => (e === 'estimate' ? 'interpretation' : e)),
149
+ limits: z.string().optional(),
150
+ /** 이 출처를 못 쓸 때 대신 쓰는 방법. 0번 전제의 근거 한계 문장에 들어간다 */
151
+ fallback: z.string().optional(),
152
+ })
153
+ .superRefine((fm, ctx) => {
154
+ fm.access.forEach((a, i) => {
155
+ if (a.kind !== 'http' || !a.probe)
156
+ return;
157
+ let url;
158
+ try {
159
+ url = new URL(a.probe);
160
+ }
161
+ catch {
162
+ return;
163
+ }
164
+ const hostOk = url.hostname === a.host || url.hostname.endsWith(`.${a.host}`);
165
+ if (url.protocol !== 'https:' || url.username || url.password || !hostOk) {
166
+ ctx.addIssue({
167
+ code: z.ZodIssueCode.custom,
168
+ path: ['access', i, 'http', 'probe'],
169
+ message: 'probe는 같은 호스트의 https 주소만 쓴다 (계정 정보 불가)',
170
+ });
171
+ }
172
+ });
173
+ });
174
+ const REQUIRED_SECTIONS = ['찾는 법', '멈추는 곳'];
175
+ /** gray-matter의 js 엔진은 frontmatter를 코드로 실행한다. 개인 디렉토리 파일이라도 막는다 */
176
+ const SAFE_MATTER_OPTIONS = {
177
+ engines: {
178
+ js: () => {
179
+ throw new Error('js frontmatter는 쓰지 않는다');
180
+ },
181
+ javascript: () => {
182
+ throw new Error('js frontmatter는 쓰지 않는다');
183
+ },
184
+ },
185
+ };
186
+ export function parseSourceMd(content) {
187
+ if (Buffer.byteLength(content, 'utf-8') > SOURCE_MAX_BYTES) {
188
+ return err(new ValidationError('SOURCE.md가 너무 크다 (64KB 상한)', []));
189
+ }
190
+ const secret = findSecret(content);
191
+ if (secret) {
192
+ return err(new ValidationError(`SOURCE.md에 ${secret}이 들어 있다. 비밀번호와 키는 적지 않고 환경변수 이름이나 MCP 서버 이름만 적는다`, [secret]));
193
+ }
194
+ let data;
195
+ let body;
196
+ try {
197
+ const parsed = matter(content, SAFE_MATTER_OPTIONS);
198
+ data = parsed.data;
199
+ body = parsed.content.trim();
200
+ }
201
+ catch (e) {
202
+ return err(new ValidationError(`frontmatter를 읽지 못했다: ${e instanceof Error ? e.message : String(e)}`, []));
203
+ }
204
+ const fm = sourceFrontmatterSchema.safeParse(data);
205
+ if (!fm.success) {
206
+ const issues = fm.error.issues.map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`);
207
+ return err(new ValidationError(`SOURCE.md frontmatter 오류: ${issues.join('; ')}`, issues));
208
+ }
209
+ const missing = REQUIRED_SECTIONS.filter((s) => !new RegExp(`^##\\s+${s}\\s*$`, 'm').test(body));
210
+ if (missing.length > 0) {
211
+ return err(new ValidationError(`본문에 절이 빠졌다: ${missing.map((s) => `## ${s}`).join(', ')}`, missing));
212
+ }
213
+ return ok({ frontmatter: fm.data, body });
214
+ }
215
+ //# sourceMappingURL=source-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-schema.js","sourceRoot":"","sources":["../../../src/research/source-schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type { LoadedSource } from './source-registry.js';
2
+ import type { SourceAccess, SourceStage } from './source-schema.js';
3
+ /** 같은 세션의 다른 MCP 연결과 브라우저 로그인은 ges 서버가 못 본다. 세션이 알려준다 */
4
+ export interface SessionAvailability {
5
+ mcpServers?: string[];
6
+ /** 세션의 MCP 도구 이름(`mcp__<서버>__<도구>`). 서버 이름이 사람마다 달라서 도구 이름으로 출처를 알아본다 */
7
+ mcpTools?: string[];
8
+ browserLogins?: string[];
9
+ }
10
+ export type SourceStatus = 'usable' | 'fallback' | 'skipped';
11
+ export interface SourceEvaluation {
12
+ status: SourceStatus;
13
+ /** 실제로 쓰게 될 접근 방법. 건너뛰면 없다 */
14
+ via?: SourceAccess['kind'];
15
+ /** MCP로 쓸 때 이 세션에서 부를 서버. 도구 이름으로 찾았으면 그 도구의 접두어에서 뽑는다 */
16
+ mcpServer?: string;
17
+ note: string;
18
+ }
19
+ /** access를 위에서부터 시도해 첫 번째로 되는 방법을 고른다 */
20
+ export declare function evaluateSource(source: LoadedSource, avail: SessionAvailability, env?: NodeJS.ProcessEnv): SourceEvaluation;
21
+ export interface SourceReportRow {
22
+ name: string;
23
+ description: string;
24
+ stage: SourceStage[];
25
+ origin: LoadedSource['origin'];
26
+ overridden: boolean;
27
+ evidence: LoadedSource['frontmatter']['evidence'];
28
+ status: SourceStatus;
29
+ via?: SourceAccess['kind'];
30
+ mcpServer?: string;
31
+ note: string;
32
+ limits?: string;
33
+ }
34
+ export interface SourceReport {
35
+ sources: SourceReportRow[];
36
+ /** 0번 전제의 근거 한계에 그대로 옮겨 적는 문장 */
37
+ evidenceLimits: string[];
38
+ }
39
+ export declare function buildSourceReport(sources: LoadedSource[], avail: SessionAvailability, env?: NodeJS.ProcessEnv, stageFilter?: SourceStage): SourceReport;
40
+ //# sourceMappingURL=source-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-status.d.ts","sourceRoot":"","sources":["../../../src/research/source-status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEpE,yDAAyD;AACzD,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;AAE7D,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,YAAY,CAAC;IACrB,8BAA8B;IAC9B,GAAG,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IAC3B,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AA6DD,yCAAyC;AACzC,wBAAgB,cAAc,CAC5B,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,mBAAmB,EAC1B,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,gBAAgB,CAwBlB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;IAC/B,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,YAAY,CAAC,aAAa,CAAC,CAAC,UAAU,CAAC,CAAC;IAClD,MAAM,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,iCAAiC;IACjC,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,YAAY,EAAE,EACvB,KAAK,EAAE,mBAAmB,EAC1B,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,WAAW,CAAC,EAAE,WAAW,GACxB,YAAY,CA+Bd"}
@@ -0,0 +1,109 @@
1
+ /** 세션에서 이 MCP 출처를 맡는 서버. 없으면 undefined */
2
+ function findMcpServer(access, avail) {
3
+ if (access.server && avail.mcpServers?.includes(access.server))
4
+ return access.server;
5
+ for (const full of avail.mcpTools ?? []) {
6
+ const m = /^mcp__(.+)__([^_].*)$/.exec(full);
7
+ const [server, tool] = m ? [m[1], m[2]] : [undefined, full];
8
+ if (access.tools?.includes(tool))
9
+ return server ?? access.server ?? '';
10
+ }
11
+ return undefined;
12
+ }
13
+ const mcpLabel = (access) => access.server ?? access.tools?.join(', ') ?? '';
14
+ const VIA_LABEL = {
15
+ mcp: 'MCP',
16
+ browser: '브라우저',
17
+ http: '공개 API',
18
+ env: 'API 키',
19
+ };
20
+ function unmetReason(access, avail, env) {
21
+ switch (access.kind) {
22
+ case 'mcp':
23
+ return findMcpServer(access, avail) === undefined
24
+ ? `MCP(${mcpLabel(access)}) 미연결`
25
+ : undefined;
26
+ case 'browser':
27
+ if (access.login === 'none')
28
+ return undefined;
29
+ return avail.browserLogins?.includes(access.site) ? undefined : `${access.site} 로그인 안 됨`;
30
+ case 'http':
31
+ return undefined;
32
+ case 'env':
33
+ return env[access.name] ? undefined : `환경변수 ${access.name} 없음`;
34
+ }
35
+ }
36
+ function unmetRequirement(source, avail, env) {
37
+ const req = source.frontmatter.requires;
38
+ if (req.mcpServer && !avail.mcpServers?.includes(req.mcpServer)) {
39
+ return `MCP(${req.mcpServer}) 미연결`;
40
+ }
41
+ const missingEnv = req.env.find((name) => !env[name]);
42
+ if (missingEnv)
43
+ return `환경변수 ${missingEnv} 없음`;
44
+ if (req.browserLogin && !avail.browserLogins?.includes(req.browserLogin)) {
45
+ return `${req.browserLogin} 로그인 안 됨`;
46
+ }
47
+ return undefined;
48
+ }
49
+ /** access를 위에서부터 시도해 첫 번째로 되는 방법을 고른다 */
50
+ export function evaluateSource(source, avail, env = process.env) {
51
+ const requirement = unmetRequirement(source, avail, env);
52
+ if (requirement)
53
+ return { status: 'skipped', note: `${requirement}이라 건너뜀` };
54
+ // 같은 종류끼리는 연결 이름만 다른 같은 경로라(예: 커넥터 MCP와 직접 띄운 MCP) 대체로 치지 않는다
55
+ const reasons = [];
56
+ for (const access of source.frontmatter.access) {
57
+ const reason = unmetReason(access, avail, env);
58
+ if (!reason) {
59
+ const other = reasons.find((r) => r.kind !== access.kind);
60
+ const server = access.kind === 'mcp' ? findMcpServer(access, avail) : undefined;
61
+ const mcp = server ? { mcpServer: server } : {};
62
+ return !other
63
+ ? { status: 'usable', via: access.kind, ...mcp, note: '사용 가능' }
64
+ : {
65
+ status: 'fallback',
66
+ via: access.kind,
67
+ ...mcp,
68
+ note: `${other.reason}이라 ${VIA_LABEL[access.kind]}로`,
69
+ };
70
+ }
71
+ reasons.push({ kind: access.kind, reason });
72
+ }
73
+ return { status: 'skipped', note: `${reasons[0].reason}이라 건너뜀` };
74
+ }
75
+ export function buildSourceReport(sources, avail, env = process.env, stageFilter) {
76
+ const rows = [];
77
+ const evidenceLimits = [];
78
+ for (const s of sources) {
79
+ const fm = s.frontmatter;
80
+ if (stageFilter && !fm.stage.includes(stageFilter))
81
+ continue;
82
+ const ev = evaluateSource(s, avail, env);
83
+ rows.push({
84
+ name: fm.name,
85
+ description: fm.description,
86
+ stage: fm.stage,
87
+ origin: s.origin,
88
+ overridden: s.overridden,
89
+ evidence: fm.evidence,
90
+ status: ev.status,
91
+ ...(ev.via ? { via: ev.via } : {}),
92
+ ...(ev.mcpServer ? { mcpServer: ev.mcpServer } : {}),
93
+ note: ev.note,
94
+ ...(fm.limits ? { limits: fm.limits } : {}),
95
+ });
96
+ if (ev.status === 'skipped') {
97
+ const tail = fm.fallback ? `${fm.fallback}` : '이 출처가 맡던 부분은 못 채웠다';
98
+ evidenceLimits.push(`${fm.name}: ${ev.note.replace(/이라 건너뜀$/, '')}이라 쓰지 못했다. ${tail}`);
99
+ }
100
+ else {
101
+ if (ev.status === 'fallback')
102
+ evidenceLimits.push(`${fm.name}: ${ev.note}`);
103
+ if (fm.limits)
104
+ evidenceLimits.push(`${fm.name}: ${fm.limits}`);
105
+ }
106
+ }
107
+ return { sources: rows, evidenceLimits };
108
+ }
109
+ //# sourceMappingURL=source-status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-status.js","sourceRoot":"","sources":["../../../src/research/source-status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { type Research } from './schema.js';
2
+ export type ResearchAudience = 'private' | 'shared';
3
+ export declare const SLUG_PATTERN: RegExp;
4
+ export declare function isValidSlug(slug: string): boolean;
5
+ /**
6
+ * 리서치 결과 저장소: `<repoRoot>/.gestalt/research/<slug>/`.
7
+ * `.gestalt/`는 gitignore 대상이라 캡처와 근거 자료가 커밋에 딸려가지 않는다.
8
+ */
9
+ export declare class ResearchStore {
10
+ private readonly root;
11
+ constructor(repoRoot: string);
12
+ /** slug는 경로 조각이라 패턴을 통과한 것만 받는다 */
13
+ dir(slug: string): string;
14
+ capturesDir(slug: string): string;
15
+ jsonPath(slug: string): string;
16
+ htmlPath(slug: string, audience: ResearchAudience): string;
17
+ /** 없으면 null. 깨졌거나 스키마에 안 맞으면 `.corrupt-*`로 옮기고 null */
18
+ load(slug: string): Research | null;
19
+ save(research: Research): string;
20
+ saveHtml(slug: string, audience: ResearchAudience, html: string): string;
21
+ /** research 폴더 바로 아래에 파생 파일(figma-plan.json 등)을 쓴다 */
22
+ saveFile(slug: string, name: string, content: string): string;
23
+ }
24
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/research/store.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE3D,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEpD,eAAO,MAAM,YAAY,QAA8B,CAAC;AAExD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED;;;GAGG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;gBAElB,QAAQ,EAAE,MAAM;IAI5B,mCAAmC;IACnC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAKzB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAIjC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAI9B,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,MAAM;IAI1D,uDAAuD;IACvD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAOnC,IAAI,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM;IAMhC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAOxE,sDAAsD;IACtD,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;CAM9D"}
@@ -0,0 +1,60 @@
1
+ import { mkdirSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { readJsonOrQuarantine, writeJsonAtomic } from '../core/json-file.js';
4
+ import { parseResearch } from './schema.js';
5
+ export const SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
6
+ export function isValidSlug(slug) {
7
+ return SLUG_PATTERN.test(slug);
8
+ }
9
+ /**
10
+ * 리서치 결과 저장소: `<repoRoot>/.gestalt/research/<slug>/`.
11
+ * `.gestalt/`는 gitignore 대상이라 캡처와 근거 자료가 커밋에 딸려가지 않는다.
12
+ */
13
+ export class ResearchStore {
14
+ root;
15
+ constructor(repoRoot) {
16
+ this.root = join(repoRoot, '.gestalt', 'research');
17
+ }
18
+ /** slug는 경로 조각이라 패턴을 통과한 것만 받는다 */
19
+ dir(slug) {
20
+ if (!isValidSlug(slug))
21
+ throw new Error(`slug가 올바르지 않다: ${slug}`);
22
+ return join(this.root, slug);
23
+ }
24
+ capturesDir(slug) {
25
+ return join(this.dir(slug), 'captures');
26
+ }
27
+ jsonPath(slug) {
28
+ return join(this.dir(slug), 'research.json');
29
+ }
30
+ htmlPath(slug, audience) {
31
+ return join(this.dir(slug), audience === 'shared' ? 'research.shared.html' : 'research.html');
32
+ }
33
+ /** 없으면 null. 깨졌거나 스키마에 안 맞으면 `.corrupt-*`로 옮기고 null */
34
+ load(slug) {
35
+ const raw = readJsonOrQuarantine(this.jsonPath(slug), (v) => parseResearch(v).ok);
36
+ if (raw === undefined)
37
+ return null;
38
+ const parsed = parseResearch(raw);
39
+ return parsed.ok ? parsed.value : null;
40
+ }
41
+ save(research) {
42
+ const path = this.jsonPath(research.meta.slug);
43
+ writeJsonAtomic(path, research);
44
+ return path;
45
+ }
46
+ saveHtml(slug, audience, html) {
47
+ const path = this.htmlPath(slug, audience);
48
+ mkdirSync(this.dir(slug), { recursive: true });
49
+ writeFileSync(path, html, 'utf-8');
50
+ return path;
51
+ }
52
+ /** research 폴더 바로 아래에 파생 파일(figma-plan.json 등)을 쓴다 */
53
+ saveFile(slug, name, content) {
54
+ const path = join(this.dir(slug), name);
55
+ mkdirSync(this.dir(slug), { recursive: true });
56
+ writeFileSync(path, content, 'utf-8');
57
+ return path;
58
+ }
59
+ }
60
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/research/store.ts"],"names":[],"mappings":"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"}