pubuilder 0.6.0 → 0.8.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.
@@ -0,0 +1,28 @@
1
+ import type { AgentEvent } from './api';
2
+ import type { BlockSelection } from './types';
3
+ export type PublishStatus = 'idle' | 'running' | 'done' | 'error';
4
+ /**
5
+ * 한 블록의 퍼블리싱 job 상태. selection에 종속된 로컬 state가 아니라
6
+ * store에 블록별로 보관되어, 다른 블록을 선택해도 실행 중인 job이 끊기지 않는다.
7
+ */
8
+ export interface PublishJobState {
9
+ jobId: string | null;
10
+ status: PublishStatus;
11
+ events: AgentEvent[];
12
+ errorMessage: string | null;
13
+ errorCode: string | null;
14
+ serverDown: boolean;
15
+ /** isAlive/startPublish 왕복 중 로딩 플래그 */
16
+ checking: boolean;
17
+ cancelling: boolean;
18
+ /** 입력값도 블록별로 유지 — 전환 후 돌아와도 그대로 */
19
+ figmaUrl: string;
20
+ reply: string;
21
+ }
22
+ export declare const IDLE_PUBLISH_JOB: PublishJobState;
23
+ /**
24
+ * 블록을 페이지 간에도 고유하게 식별하는 키.
25
+ * 서로 다른 페이지가 같은 selector를 가질 수 있으므로 pagePath까지 합친다.
26
+ * '\n'은 selector/pagePath에 나타나지 않아 구분자로 안전하다.
27
+ */
28
+ export declare function blockKey(sel: BlockSelection): string;
@@ -1,6 +1,31 @@
1
+ /** 충돌 파일 정보: 사용자가 파일 단위로 revert/keep/edit 결정한다 */
2
+ export interface SuspendConflict {
3
+ file: string;
4
+ baseline: string;
5
+ current: string;
6
+ }
7
+ export interface SuspendResult {
8
+ autoReverted: string[];
9
+ conflicts: SuspendConflict[];
10
+ }
11
+ export type SuspendResolution = {
12
+ file: string;
13
+ action: 'revert' | 'keep' | 'edit';
14
+ content?: string;
15
+ };
16
+ /** 테스트에서 fs/git 없이 로직만 검증할 수 있도록 주입 가능한 파일 연산 */
17
+ interface SnapshotOps {
18
+ captureBaseline?: (cwd: string) => string | null;
19
+ readBaseline?: (cwd: string, ref: string, file: string) => string | null;
20
+ restoreFile?: (cwd: string, ref: string, file: string) => void;
21
+ readCurrent?: (cwd: string, file: string) => string;
22
+ writeFile?: (cwd: string, file: string, content: string) => void;
23
+ }
1
24
  export interface AgentEvent {
2
25
  type: 'log' | 'tool' | 'error' | 'done';
3
26
  text: string;
27
+ /** tool 이벤트에서 Edit/Write 대상 파일 경로 (touchedFiles 추적용) */
28
+ file?: string;
4
29
  }
5
30
  export interface AgentRunOptions {
6
31
  prompt: string;
@@ -18,13 +43,20 @@ export interface AgentAdapter {
18
43
  */
19
44
  export declare function parseAgentLine(line: string): AgentEvent[];
20
45
  export declare const claudeAdapter: AgentAdapter;
21
- /** 동시 실행 1개 제한 (스펙). 이벤트는 저장 후 브로드캐스트 — 늦은 SSE 구독자는 replay */
46
+ /** 이벤트는 저장 후 브로드캐스트 — 늦은 SSE 구독자는 replay */
22
47
  export declare class PublishJobManager {
23
48
  private readonly adapter;
24
49
  private jobs;
25
- private active;
26
50
  private seq;
27
- constructor(adapter: AgentAdapter);
51
+ private readonly capture;
52
+ private readonly readBaseline;
53
+ private readonly restoreFile;
54
+ private readonly readCurrent;
55
+ private readonly writeFile;
56
+ /** suspend 시 파일별 baseline ref를 보관 → resolve의 revert에서 사용 */
57
+ private pendingBaseline;
58
+ constructor(adapter: AgentAdapter, opts?: SnapshotOps);
59
+ private activeCount;
28
60
  start(opts: {
29
61
  prompt: string;
30
62
  cwd: string;
@@ -33,7 +65,24 @@ export declare class PublishJobManager {
33
65
  private evictFinishedJobs;
34
66
  /** SSE 핸들러가 헤더 전송 전에 job 존재를 확인할 때 사용 (subscribe는 replay 부작용이 있어 분리) */
35
67
  has(id: string): boolean;
68
+ /** job이 편집한 파일 목록 (상대경로, 정렬) */
69
+ touchedFiles(id: string): string[];
70
+ /** job 시작 시점 baseline ref */
71
+ baselineRef(id: string): string | null;
72
+ /**
73
+ * 진행 중(running)인 job을 전부 abort하고, 그들이 편집한 파일을 분류한다.
74
+ * - 자동 롤백: running job만 건드린 파일 → baseline으로 즉시 복원
75
+ * - 충돌: 완료(finished)된 job도 건드린 파일 → 사용자 해결 대상으로 반환
76
+ * 파일별 baseline은 그 파일을 건드린 running job 중 가장 이른(seq 최소) job의 것.
77
+ */
78
+ suspendRunning(cwd: string): SuspendResult;
79
+ /**
80
+ * 충돌 파일에 대한 사용자 결정을 적용한다.
81
+ * revert → baseline 복원, edit → content로 덮어쓰기, keep → 그대로 둔다.
82
+ */
83
+ applyResolutions(cwd: string, resolutions: SuspendResolution[]): void;
36
84
  subscribe(id: string, listener: (e: AgentEvent) => void): () => void;
37
85
  cancel(id: string): void;
38
86
  waitFor(id: string): Promise<void>;
39
87
  }
88
+ export {};
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Figma REST /nodes document 트리 → 컴팩트 Design IR.
3
+ *
4
+ * 규칙 기반 순수 변환(LLM 토큰 0). raw Figma 노드는 노드마다 수십 개 필드
5
+ * (blendMode/constraints/absoluteRenderBounds/exportSettings 등)를 담아 프롬프트에
6
+ * 그대로 실으면 토큰을 크게 낭비한다. 퍼블리싱에 실제로 필요한 스타일 필드만 뽑고
7
+ * 숨김 노드를 제거해 60~80% 축소한다. LLM은 이 IR로 판단(토큰 매핑·코드 작성)만 한다.
8
+ */
9
+ export interface DesignIRNode {
10
+ name: string;
11
+ type: string;
12
+ size?: {
13
+ w: number;
14
+ h: number;
15
+ };
16
+ layout?: {
17
+ mode: 'row' | 'col';
18
+ gap?: number;
19
+ padding?: [number, number, number, number];
20
+ align?: string;
21
+ justify?: string;
22
+ };
23
+ radius?: number | number[];
24
+ fills?: string[];
25
+ strokes?: string[];
26
+ effects?: string[];
27
+ text?: {
28
+ chars: string;
29
+ font?: string;
30
+ size?: number;
31
+ weight?: number;
32
+ lineHeight?: number;
33
+ letterSpacing?: number;
34
+ };
35
+ component?: Record<string, string>;
36
+ children?: DesignIRNode[];
37
+ }
38
+ export interface DesignIR {
39
+ root: DesignIRNode;
40
+ /** 색상 사용 빈도 (hex→token 매핑 결정용, 빈도 내림차순) */
41
+ colors: Array<{
42
+ rgba: string;
43
+ count: number;
44
+ }>;
45
+ }
46
+ export declare function extractDesignIR(document: unknown): DesignIR;
@@ -7,6 +7,8 @@ export interface PubuilderServerOptions {
7
7
  port?: number;
8
8
  adapter?: AgentAdapter;
9
9
  figmaFetch?: typeof fetch;
10
+ /** 패키지 번들 기본 스킬 디렉토리 (CLI가 dist 기준으로 해석해 전달) */
11
+ builtinDir?: string;
10
12
  }
11
13
  export interface PubuilderServerHandle {
12
14
  port: number;
@@ -1,4 +1,4 @@
1
- export type SkillSource = 'project' | 'global' | 'uploaded';
1
+ export type SkillSource = 'builtin' | 'project' | 'global' | 'uploaded';
2
2
  export interface SkillInfo {
3
3
  id: string;
4
4
  name: string;
@@ -9,16 +9,19 @@ export interface SkillInfo {
9
9
  export declare class SkillRegistry {
10
10
  private readonly projectRoot;
11
11
  private readonly homeDir;
12
+ /** 패키지에 번들되어 배포되는 기본 스킬 디렉토리 (없으면 스캔 안 함) */
13
+ private readonly builtinDir?;
12
14
  constructor(opts: {
13
15
  projectRoot: string;
14
16
  homeDir: string;
17
+ builtinDir?: string;
15
18
  });
16
19
  private settingsPath;
17
20
  private loadSettings;
18
21
  private saveSettings;
19
22
  private scanDir;
20
23
  private scanAll;
21
- /** 기본값: project·uploaded on, global off (스펙 결정) */
24
+ /** 기본값: builtin·project·uploaded on, global off (스펙 결정) */
22
25
  list(): SkillInfo[];
23
26
  setEnabled(id: string, enabled: boolean): void;
24
27
  upload(filename: string, content: string): SkillInfo;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 현재 작업 트리 상태를 커밋 오브젝트로 스냅샷하고 그 SHA를 반환한다.
3
+ * 워킹트리·stash 목록을 건드리지 않는다(`git stash create`).
4
+ * 트리가 clean이면 'HEAD'(동일 상태), 비-git이거나 실패하면 null(롤백 기능 degrade).
5
+ */
6
+ export declare function captureBaseline(cwd: string): string | null;
7
+ /** ref 시점의 file 내용. ref에 해당 파일이 없으면(스냅샷 이후 생성된 신규 파일) null. */
8
+ export declare function readBaseline(cwd: string, ref: string, file: string): string | null;
9
+ /** file을 baseline으로 복원한다. baseline에 없던(신규 생성) 파일이면 삭제한다. */
10
+ export declare function restoreFile(cwd: string, ref: string, file: string): void;
package/dist/store.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { AgentEvent, SuspendConflict, SuspendResolution } from './api';
2
+ import { type PublishJobState } from './publish-jobs';
1
3
  import type { BlockSelection, Granularity } from './types';
2
4
  interface PubuilderStore {
3
5
  isMapOpen: boolean;
@@ -9,6 +11,11 @@ interface PubuilderStore {
9
11
  /** 선택된 블록의 outerHTML 스냅샷 (Phase 3 퍼블리싱 프롬프트용, 20K 상한) */
10
12
  selectedOuterHTML: string | null;
11
13
  skillDrawerOpen: boolean;
14
+ /**
15
+ * 블록별 퍼블리싱 job 상태 (key: blockKey). selection에서 분리되어 있어
16
+ * 다른 블록을 선택하거나 맵을 닫아도 실행 중인 job이 끊기지 않는다.
17
+ */
18
+ publishJobs: Record<string, PublishJobState>;
12
19
  openMap: () => void;
13
20
  closeMap: () => void;
14
21
  openViewer: (path: string) => void;
@@ -17,6 +24,17 @@ interface PubuilderStore {
17
24
  setInspectEnabled: (v: boolean) => void;
18
25
  setSelectionWithHTML: (s: BlockSelection | null, html: string | null) => void;
19
26
  toggleSkillDrawer: () => void;
27
+ /** 블록 job의 일부 필드를 갱신 (없으면 IDLE에서 시작) */
28
+ patchPublishJob: (key: string, partial: Partial<PublishJobState>) => void;
29
+ /** 블록 job의 이벤트 로그에 한 건 추가 */
30
+ appendPublishEvent: (key: string, event: AgentEvent) => void;
31
+ /** 일시 종료 후 사용자가 해결해야 하는 충돌 파일들 (없으면 패널 미표시) */
32
+ suspendConflicts: SuspendConflict[];
33
+ /** 충돌 파일별 사용자 결정 (file → resolution) */
34
+ suspendResolutions: Record<string, SuspendResolution>;
35
+ setSuspendConflicts: (conflicts: SuspendConflict[]) => void;
36
+ setConflictResolution: (file: string, action: SuspendResolution['action'], content?: string) => void;
37
+ clearSuspend: () => void;
20
38
  }
21
39
  export declare const usePubuilderStore: import("zustand").UseBoundStore<import("zustand").StoreApi<PubuilderStore>>;
22
40
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pubuilder",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "In-app IA minimap & block inspector for React — live iframe thumbnails of every page, plus a Next.js route scanner that drafts your ia.config (dev-only)",
5
5
  "license": "MIT",
6
6
  "author": "taehoon",
@@ -39,6 +39,7 @@
39
39
  },
40
40
  "files": [
41
41
  "dist",
42
+ "skills",
42
43
  "CHANGELOG.md"
43
44
  ],
44
45
  "sideEffects": false,
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: publish
3
+ description: Use when implementing a UI block from a Figma node — turning a Figma design into code inside an existing project, using that project's own style tokens and components.
4
+ ---
5
+
6
+ # Figma 퍼블리싱 방법
7
+
8
+ Figma 노드를 이 프로젝트의 코드로 구현할 때 따르는 방법이다. **프레임워크 무관** — 프로젝트가 이미 쓰는 스타일 시스템(Chakra / Tailwind / MUI / styled-components / CSS Modules 등)과 기존 컴포넌트를 그대로 활용한다.
9
+
10
+ pubuilder가 이미 Figma를 **컴팩트 Design IR**(규칙 기반으로 스타일 필드만 추출, 숨김 노드 제외)과 렌더 이미지로 프롬프트에 전달한다. 이 스킬은 그것을 **어떤 태도와 순서로 코드에 옮길지**를 정의한다.
11
+
12
+ ## 핵심 원칙 (모든 단계에 우선)
13
+
14
+ - **애매하거나 모르면 추측하지 말고 사용자에게 질문한다.** 추측으로 진행 금지.
15
+ - **문제가 생기면 우회하지 말고 멈추고 보고한다.** 노드 누락 / 스펙 불일치 / 토큰 매칭 실패 / 컴포넌트 매핑 부재 — "비슷해 보이는 값"으로 대체하거나 "아마 이 뜻일 것"으로 넘어가지 않는다.
16
+ - **기존 코드에서 UI 요소(컴포넌트·필드·컬럼 등)를 제거하기 전 반드시 확인한다.** 전달된 노드 JSON은 일부 노드만 담을 수 있으므로, 시안에 안 보인다고 없는 것으로 판단하지 않는다. 제거가 필요해 보이면 → 멈추고 이유와 함께 보고 → 확인 후 진행.
17
+ - **letter를 어기는 것은 spirit을 어기는 것이다.** 위 규칙은 다른 어떤 편의보다 우선한다.
18
+
19
+ ## 1. 시안을 눈으로 먼저 본다
20
+
21
+ - 전달된 Figma 렌더 이미지를 **Read 도구로 직접 연다.**
22
+ - 노드 JSON의 수치를 1차 근거로 쓰되, **JSON에만 있고 이미지엔 안 보이는 요소**(hidden / opacity 0 / placeholder)는 구현하지 않는다.
23
+
24
+ ## 2. 스펙을 raw 값으로 읽는다 (토큰명 추론 금지)
25
+
26
+ Design IR에서 아래를 **raw 값 그대로** 파악한다:
27
+
28
+ - size / layout(mode·align·sizing·gap·padding) / cornerRadius
29
+ - fills·strokes (rgba) / effects(shadow)
30
+ - text: 내용 + typography 5필드(font family / size / weight / lineHeight / letterSpacing)
31
+ - 컴포넌트 variant / INSTANCE_SWAP / BOOLEAN / TEXT 값
32
+ - 아이콘 내부 vector·알림 dot 같은 **decorative 요소도 누락 금지**
33
+
34
+ 반복 요소(칩·카드·행)는 공통 스펙 + 인스턴스별 차이(width/text)를 **한 개도 빠짐없이** 확인한다.
35
+
36
+ ## 3. 프로젝트 규약으로 변환한다
37
+
38
+ - raw 값을 **프로젝트가 이미 쓰는 토큰/유틸**로 매핑한다. 하드코딩보다 기존 토큰 우선.
39
+ - 토큰이 불확실하면 **토큰 정의 파일을 열어 대조**한다 — 이름을 추측하지 않는다.
40
+ - fill opacity < 1이면 시맨틱 토큰 대신 `rgba()`를 직접 쓴다.
41
+ - 컴포넌트는 **기존 컴포넌트를 재사용**한다. 같은 패턴이 이미 있으면 새로 만들지 않는다.
42
+ - 대상 소스 파일은 전달된 outerHTML 스냅샷과 라우트를 단서로 찾는다.
43
+
44
+ ## 4. 구현
45
+
46
+ - 스타일·마크업만 시안에 맞추고, **기존 동작·이벤트 핸들러·데이터 로직은 유지**한다.
47
+
48
+ ## 5. 완료 게이트 (선언 전 필수)
49
+
50
+ 아래를 모두 확인한 뒤에만 "완료"라 한다. 한 칸이라도 미충족이면 완료 대신 사용자에게 보고한다:
51
+
52
+ - [ ] hidden/placeholder 노드를 잘못 구현하지 않았는가
53
+ - [ ] variant / INSTANCE_SWAP / 텍스트 값이 시안과 일치하는가
54
+ - [ ] 반복 요소의 개수·내용이 맞는가
55
+ - [ ] **색상·간격·타이포를 시안 이미지와 나란히 비교했는가** (코드만 보고 "맞다" 가정 금지)
56
+ - [ ] 프로젝트의 타입체크가 통과하는가
57
+
58
+ ## 하지 않는 것
59
+
60
+ - Figma 스펙 추측 코딩 / sublayer 건너뛰기
61
+ - 시안 이미지에 없는 요소 추가 (비활성·숨김·placeholder 포함)
62
+ - 확인 없이 기존 UI 제거
63
+ - 이름을 추측한 토큰 사용