@ssobig/writer-cli 0.2.2 → 0.3.2

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 (39) hide show
  1. package/README.md +19 -0
  2. package/asset-repository.js +7 -2
  3. package/config.js +2 -1
  4. package/package.json +1 -1
  5. package/templates/mystery-v1/authoring-view-preference.js +22 -5
  6. package/templates/mystery-v1/character-perspective-preview.js +3 -3
  7. package/templates/mystery-v1/codemirror6-runtime.min.js +28 -0
  8. package/templates/mystery-v1/component-asset-operations.js +19 -6
  9. package/templates/mystery-v1/component-catalog-contract.js +26 -35
  10. package/templates/mystery-v1/component-draft-operations.js +19 -8
  11. package/templates/mystery-v1/component-field-contracts.js +127 -51
  12. package/templates/mystery-v1/component-id-policy.js +1 -1
  13. package/templates/mystery-v1/component-manager.js +21 -11
  14. package/templates/mystery-v1/component-navigation-counts.js +9 -9
  15. package/templates/mystery-v1/component-registry.js +11 -5
  16. package/templates/mystery-v1/component-renderers.js +12 -8
  17. package/templates/mystery-v1/component-storage-contract.js +28 -9
  18. package/templates/mystery-v1/markdown-document-model.js +444 -0
  19. package/templates/mystery-v1/markdown-image-editor.js +320 -0
  20. package/templates/mystery-v1/markdown-live-editor.js +1224 -0
  21. package/templates/mystery-v1/page-header.js +2 -1
  22. package/templates/mystery-v1/timeline-model.js +235 -0
  23. package/tools/writer-cli/package-lock.json +2 -2
  24. package/tools/writer-cli/package.json +1 -1
  25. package/tools/writer-cli/skills/ssobig-writer-cli/SKILL.md +13 -14
  26. package/tools/writer-cli/skills/ssobig-writer-cli/references/assets-checkpoints.md +5 -1
  27. package/tools/writer-cli/skills/ssobig-writer-cli/references/errors.md +4 -1
  28. package/tools/writer-cli/skills/ssobig-writer-cli/references/install-auth.md +3 -3
  29. package/tools/writer-cli/skills/ssobig-writer-cli/references/investigation-board.md +9 -0
  30. package/tools/writer-cli/skills/ssobig-writer-cli/references/layout-spec.md +130 -0
  31. package/tools/writer-cli/skills/ssobig-writer-cli/references/projects-components.md +8 -2
  32. package/tools/writer-cli/skills/ssobig-writer-cli/references/read-search.md +12 -2
  33. package/tools/writer-cli/src/agent-service.cjs +3 -1
  34. package/tools/writer-cli/src/command-registry.cjs +24 -20
  35. package/tools/writer-cli/src/commands.cjs +106 -3
  36. package/tools/writer-cli/src/domain.cjs +296 -1
  37. package/tools/writer-cli/src/gateway.cjs +14 -0
  38. package/tools/writer-cli/src/mutations.cjs +167 -1
  39. package/tools/writer-cli/src/project-import.cjs +27 -22
@@ -6,6 +6,7 @@ const path = require("node:path");
6
6
  const catalogContract = require("../../../templates/mystery-v1/component-catalog-contract.js");
7
7
  const componentContract = require("../../../templates/mystery-v1/component-storage-contract.js");
8
8
  const viewContract = require("../../../templates/mystery-v1/view-component-contract.js");
9
+ const timelineModel = require("../../../templates/mystery-v1/timeline-model.js");
9
10
  const { inspectAssetFile } = require("./asset-policy.cjs");
10
11
  const { clone, equalJson, sha256 } = require("./json.cjs");
11
12
  const { cliError } = require("./errors.cjs");
@@ -142,6 +143,15 @@ function parseTimeline(source) {
142
143
  return events;
143
144
  }
144
145
 
146
+ // 원본 표는 사건이 시간을 소유하는 legacy 행으로 읽고, 등록된 v2 모양으로만 Component data를 만든다.
147
+ function timelineComponentData(events) {
148
+ const legacy = { events };
149
+ const converted = timelineModel.convertLegacyTimeline(legacy);
150
+ const review = timelineModel.compareLegacyToV2(legacy, converted);
151
+ if (!review.ok) throw cliError("E_VALIDATION", `타임라인 v2 변환이 원본과 일치하지 않습니다: ${review.mismatches.join(" / ")}`);
152
+ return { data: converted, review };
153
+ }
154
+
145
155
  function characterComponent(characterSource) {
146
156
  const characters = {};
147
157
  const names = {};
@@ -164,13 +174,12 @@ function characterComponent(characterSource) {
164
174
  tag: "플레이어 캐릭터",
165
175
  color: CHARACTER_COLORS[characterId],
166
176
  customSections,
167
- containers: [{ id: crypto.randomUUID(), role: "primary", title: "기본 정보", sectionIds: customSections.map(section => section.id) }],
168
- authorNote: ""
177
+ containers: [{ id: crypto.randomUUID(), role: "primary", title: "기본 정보", sectionIds: customSections.map(section => section.id) }]
169
178
  };
170
179
  }
171
180
  return {
172
181
  characters, names, order, customCharacters: [], deletedColumns: [],
173
- enabledOptionalFields: ["image", "color", "tag", "authorNote"]
182
+ enabledOptionalFields: ["image", "color", "tag"]
174
183
  };
175
184
  }
176
185
 
@@ -195,7 +204,7 @@ function progressComponent(flowSource) {
195
204
  };
196
205
  }
197
206
 
198
- function cluesComponent(resourceSource, writerNote) {
207
+ function cluesComponent(resourceSource) {
199
208
  if (!isObject(resourceSource)) throw cliError("E_VALIDATION", "단서 resource 원본이 객체가 아닙니다.");
200
209
  const clues = [];
201
210
  Object.entries(resourceSource).forEach(([sourceId, item]) => {
@@ -207,7 +216,6 @@ function cluesComponent(resourceSource, writerNote) {
207
216
  title: name,
208
217
  location: source.verification === undefined ? "1차 조사" : "2차 조사",
209
218
  description: text(source.content),
210
- secret: text(source.verification),
211
219
  tags: [source.verification === undefined ? "자료" : "제보"],
212
220
  color: source.verification === undefined ? "#497fa8" : "#a86b49",
213
221
  confirmed: true,
@@ -216,8 +224,7 @@ function cluesComponent(resourceSource, writerNote) {
216
224
  });
217
225
  return {
218
226
  clues,
219
- enabledOptionalFields: ["confirmed", "image", "location", "tags", "color", "secret"],
220
- writerNote
227
+ enabledOptionalFields: ["confirmed", "image", "location", "tags", "color"]
221
228
  };
222
229
  }
223
230
 
@@ -357,6 +364,7 @@ function loadTrueWriterCase(input, options = {}) {
357
364
  const progress = progressComponent(jsonData.game_set.flow);
358
365
  const ending = endingComponent(jsonData.game_set.ending);
359
366
  const timelineEvents = parseTimeline(docs["03-timeline.md"]);
367
+ const timeline = timelineComponentData(timelineEvents);
360
368
  const resources = Object.keys(jsonData.resource).length;
361
369
  const components = normalizeImportComponents([
362
370
  component("basic", {
@@ -372,25 +380,19 @@ function loadTrueWriterCase(input, options = {}) {
372
380
  }),
373
381
  component("progress", progress),
374
382
  component("common", {
375
- sections: {
376
- overview: docs["00-overview.md"],
377
- prologue: text(jsonData.game_set.flow?.["00 - 프롤로그"]?.script)
378
- },
379
- customSections: [],
380
- sectionOrder: ["overview", "prologue"],
381
- baseTitles: { overview: "작품 개요", prologue: "프롤로그" },
382
- deletedBaseSections: []
383
+ documents: [
384
+ { id: crypto.randomUUID(), title: "작품 개요", body: docs["00-overview.md"], subdocuments: [] },
385
+ { id: crypto.randomUUID(), title: "프롤로그", body: text(jsonData.game_set.flow?.["00 - 프롤로그"]?.script), subdocuments: [] },
386
+ { id: crypto.randomUUID(), title: "사건의 전말", body: "디자이너 전용 정답 및 사건의 전말", subdocuments: [{ id: crypto.randomUUID(), title: "사건의 진실", body: docs["01-truth.md"] }] },
387
+ { id: crypto.randomUUID(), title: "에필로그", body: "게임 종료 후 공개 문서", subdocuments: [{ id: crypto.randomUUID(), title: "에필로그", body: text(jsonData.game_set.flow?.["14 - 에필로그"]?.script) }] }
388
+ ],
389
+ deletedDocumentIds: []
383
390
  }),
384
391
  component("characters", characterComponent(jsonData.game_set.character)),
385
392
  component("assets", { assets: {} }),
386
- component("clues", cluesComponent(jsonData.resource, docs["04-clues.md"])),
387
- component("timeline", { events: timelineEvents }),
393
+ component("clues", cluesComponent(jsonData.resource)),
394
+ component("timeline", timeline.data),
388
395
  component("ending", ending),
389
- component("postgame", {
390
- truth: [{ id: crypto.randomUUID(), title: "사건의 진실", body: docs["01-truth.md"] }],
391
- epilogue: [{ id: crypto.randomUUID(), title: "에필로그", body: text(jsonData.game_set.flow?.["14 - 에필로그"]?.script) }],
392
- intro: { truth: "디자이너 전용 정답 및 사건의 전말", epilogue: "게임 종료 후 공개 문서" }
393
- }),
394
396
  component("author-notes", {
395
397
  notes: [
396
398
  ["character-design", "캐릭터 설계 원문", docs["02-characters.md"]],
@@ -426,6 +428,9 @@ function loadTrueWriterCase(input, options = {}) {
426
428
  endingGroups: Object.keys(jsonData.game_set.ending || {}).length,
427
429
  endingOutcomes: ending.outcomes.length,
428
430
  timelineEvents: timelineEvents.length,
431
+ timelineLevels: timeline.data.timeLevels.length,
432
+ timelineEntries: timeline.data.entries.length,
433
+ timelineConversion: { shape: "v2", lossless: timeline.review.ok, ...timeline.review.counts },
429
434
  components: components.length,
430
435
  assets: assetFiles.length,
431
436
  unexplained: 0