@tienne/gestalt 0.95.0 → 0.97.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 (120) hide show
  1. package/CLAUDE.md +5 -3
  2. package/README.ko.md +1 -0
  3. package/README.md +1 -0
  4. package/dist/package.json +4 -3
  5. package/dist/plugin/skills/_shared/agent-model.md +5 -3
  6. package/dist/plugin/skills/_shared/proactive-routing.md +1 -0
  7. package/dist/plugin/skills/agent/SKILL.md +1 -1
  8. package/dist/plugin/skills/architecture/SKILL.md +31 -5
  9. package/dist/plugin/skills/architecture/references/domain-flow.md +1 -0
  10. package/dist/plugin/skills/architecture/references/projections.md +56 -0
  11. package/dist/plugin/skills/architecture/references/rerun.md +3 -1
  12. package/dist/plugin/skills/blast-radius/SKILL.md +1 -1
  13. package/dist/plugin/skills/build-graph/SKILL.md +1 -1
  14. package/dist/plugin/skills/doubt/SKILL.md +229 -0
  15. package/dist/plugin/skills/execute/SKILL.md +13 -1
  16. package/dist/plugin/skills/explain/SKILL.md +1 -1
  17. package/dist/plugin/skills/interview/SKILL.md +1 -1
  18. package/dist/plugin/skills/local-pr/SKILL.md +14 -1
  19. package/dist/plugin/skills/pr/SKILL.md +14 -1
  20. package/dist/plugin/skills/review/SKILL.md +14 -1
  21. package/dist/plugin/skills/review-loop/SKILL.md +13 -0
  22. package/dist/plugin/skills/review-reply/SKILL.md +13 -0
  23. package/dist/plugin/skills/ship/SKILL.md +14 -1
  24. package/dist/plugin/skills/solve/SKILL.md +1 -1
  25. package/dist/plugin/skills/spec/SKILL.md +1 -1
  26. package/dist/schemas/architecture-ir.schema.json +51 -3
  27. package/dist/schemas/gestalt.schema.json +12 -0
  28. package/dist/src/architecture/blueprint-layout.d.ts +105 -0
  29. package/dist/src/architecture/blueprint-layout.d.ts.map +1 -0
  30. package/dist/src/architecture/blueprint-layout.js +297 -0
  31. package/dist/src/architecture/blueprint-layout.js.map +1 -0
  32. package/dist/src/architecture/delta-renderer.d.ts +35 -0
  33. package/dist/src/architecture/delta-renderer.d.ts.map +1 -0
  34. package/dist/src/architecture/delta-renderer.js +329 -0
  35. package/dist/src/architecture/delta-renderer.js.map +1 -0
  36. package/dist/src/architecture/delta.d.ts +70 -0
  37. package/dist/src/architecture/delta.d.ts.map +1 -0
  38. package/dist/src/architecture/delta.js +342 -0
  39. package/dist/src/architecture/delta.js.map +1 -0
  40. package/dist/src/architecture/drilldown.d.ts +19 -1
  41. package/dist/src/architecture/drilldown.d.ts.map +1 -1
  42. package/dist/src/architecture/drilldown.js +5 -5
  43. package/dist/src/architecture/drilldown.js.map +1 -1
  44. package/dist/src/architecture/er-layout.d.ts +97 -0
  45. package/dist/src/architecture/er-layout.d.ts.map +1 -0
  46. package/dist/src/architecture/er-layout.js +344 -0
  47. package/dist/src/architecture/er-layout.js.map +1 -0
  48. package/dist/src/architecture/flow-layout.d.ts +22 -1
  49. package/dist/src/architecture/flow-layout.d.ts.map +1 -1
  50. package/dist/src/architecture/flow-layout.js +13 -9
  51. package/dist/src/architecture/flow-layout.js.map +1 -1
  52. package/dist/src/architecture/html-renderer.d.ts +4 -0
  53. package/dist/src/architecture/html-renderer.d.ts.map +1 -1
  54. package/dist/src/architecture/html-renderer.js +462 -20
  55. package/dist/src/architecture/html-renderer.js.map +1 -1
  56. package/dist/src/architecture/html-theme.d.ts +18 -0
  57. package/dist/src/architecture/html-theme.d.ts.map +1 -1
  58. package/dist/src/architecture/html-theme.js +101 -1
  59. package/dist/src/architecture/html-theme.js.map +1 -1
  60. package/dist/src/architecture/ir-schema.d.ts +358 -117
  61. package/dist/src/architecture/ir-schema.d.ts.map +1 -1
  62. package/dist/src/architecture/ir-schema.js +33 -1
  63. package/dist/src/architecture/ir-schema.js.map +1 -1
  64. package/dist/src/architecture/lifecycle-layout.d.ts +94 -0
  65. package/dist/src/architecture/lifecycle-layout.d.ts.map +1 -0
  66. package/dist/src/architecture/lifecycle-layout.js +471 -0
  67. package/dist/src/architecture/lifecycle-layout.js.map +1 -0
  68. package/dist/src/architecture/merge.d.ts.map +1 -1
  69. package/dist/src/architecture/merge.js +72 -2
  70. package/dist/src/architecture/merge.js.map +1 -1
  71. package/dist/src/architecture/store.d.ts +6 -1
  72. package/dist/src/architecture/store.d.ts.map +1 -1
  73. package/dist/src/architecture/store.js +75 -8
  74. package/dist/src/architecture/store.js.map +1 -1
  75. package/dist/src/architecture/types.d.ts +35 -3
  76. package/dist/src/architecture/types.d.ts.map +1 -1
  77. package/dist/src/architecture/types.js +13 -1
  78. package/dist/src/architecture/types.js.map +1 -1
  79. package/dist/src/architecture/validator.d.ts +27 -3
  80. package/dist/src/architecture/validator.d.ts.map +1 -1
  81. package/dist/src/architecture/validator.js +331 -8
  82. package/dist/src/architecture/validator.js.map +1 -1
  83. package/dist/src/core/config.d.ts +18 -0
  84. package/dist/src/core/config.d.ts.map +1 -1
  85. package/dist/src/core/config.js +14 -1
  86. package/dist/src/core/config.js.map +1 -1
  87. package/dist/src/core/hooks-env.d.ts +7 -2
  88. package/dist/src/core/hooks-env.d.ts.map +1 -1
  89. package/dist/src/core/hooks-env.js +7 -2
  90. package/dist/src/core/hooks-env.js.map +1 -1
  91. package/dist/src/mcp/tools/architecture-passthrough.d.ts.map +1 -1
  92. package/dist/src/mcp/tools/architecture-passthrough.js +91 -2
  93. package/dist/src/mcp/tools/architecture-passthrough.js.map +1 -1
  94. package/package.json +4 -3
  95. package/plugin/.codex-plugin/plugin.json +1 -1
  96. package/plugin/.mcp.json +1 -1
  97. package/plugin/mcp.json +1 -1
  98. package/plugin/skills/_shared/agent-model.md +5 -3
  99. package/plugin/skills/_shared/proactive-routing.md +1 -0
  100. package/plugin/skills/agent/SKILL.md +1 -1
  101. package/plugin/skills/architecture/SKILL.md +31 -5
  102. package/plugin/skills/architecture/references/domain-flow.md +1 -0
  103. package/plugin/skills/architecture/references/projections.md +56 -0
  104. package/plugin/skills/architecture/references/rerun.md +3 -1
  105. package/plugin/skills/blast-radius/SKILL.md +1 -1
  106. package/plugin/skills/build-graph/SKILL.md +1 -1
  107. package/plugin/skills/doubt/SKILL.md +229 -0
  108. package/plugin/skills/execute/SKILL.md +13 -1
  109. package/plugin/skills/explain/SKILL.md +1 -1
  110. package/plugin/skills/interview/SKILL.md +1 -1
  111. package/plugin/skills/local-pr/SKILL.md +14 -1
  112. package/plugin/skills/pr/SKILL.md +14 -1
  113. package/plugin/skills/review/SKILL.md +14 -1
  114. package/plugin/skills/review-loop/SKILL.md +13 -0
  115. package/plugin/skills/review-reply/SKILL.md +13 -0
  116. package/plugin/skills/ship/SKILL.md +14 -1
  117. package/plugin/skills/solve/SKILL.md +1 -1
  118. package/plugin/skills/spec/SKILL.md +1 -1
  119. package/schemas/architecture-ir.schema.json +51 -3
  120. package/schemas/gestalt.schema.json +12 -0
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/architecture/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/architecture/types.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { type ArchitectureIr, type Evidence, type LineStyle, type NodeKind, type UnresolvedQuestion } from './types.js';
2
- export type ArchitectureValidationErrorCode = 'SOLID_EDGE_WITHOUT_EVIDENCE' | 'CODE_EVIDENCE_NOT_FOUND' | 'PRIVATE_EXCERPT_PRESENT' | 'DANGLING_EDGE' | 'PARENT_NOT_FOUND' | 'PARENT_CYCLE' | 'INVALID_PARENT_KIND' | 'LIVE_COMMAND_NOT_READ_ONLY' | 'ACCOUNT_NOT_FOUND' | 'INVALID_ACCOUNT_KIND' | 'CLOUD_ID_IN_ID' | 'GROUP_MEMBER_NOT_FOUND' | 'DUPLICATE_GROUP_ID' | 'SERVES_SERVICE_WITH_APPS' | 'INVALID_LOADS_ENDS' | 'INVALID_HARNESS_EDGE_ENDS' | 'INVALID_CALL_ACTIONS' | 'UNKNOWN_MCP_ACTION' | 'MD_CODE_EVIDENCE' | 'FLOW_SERVICE_NOT_FOUND' | 'DUPLICATE_FLOW_ID' | 'FLOW_ACTOR_NOT_FOUND' | 'FLOW_STEP_NOT_FOUND' | 'FLOW_REF_NOT_FOUND' | 'INVALID_FLOW_REF_KIND' | 'SOLID_TRANSITION_WITHOUT_EVIDENCE' | 'DUPLICATE_STAGE_ID' | 'STAGE_NODE_NOT_FOUND' | 'STAGE_NODE_TWICE' | 'UNKNOWN_PACK' | 'KIND_NOT_IN_PACKS' | 'CODE_EVIDENCE_IN_DOC_REPO' | 'DUPLICATE_PROJECTION_ID' | 'DUPLICATE_MESSAGE_ID' | 'PROJECTION_NODE_NOT_FOUND' | 'PROJECTION_EDGE_NOT_FOUND' | 'PROJECTION_EDGE_MISMATCH' | 'PROJECTION_BLOCK_NOT_FOUND' | 'PROJECTION_BLOCK_SPLIT' | 'SOLID_MESSAGE_WITHOUT_EVIDENCE' | 'PROJECTION_EMPTY' | 'PROJECTION_SHAPE_FIELD' | 'DUPLICATE_PHASE_ID' | 'PROJECTION_PHASE_MESSAGE_NOT_FOUND' | 'PROJECTION_PHASE_REVERSED' | 'PROJECTION_PHASE_OVERLAP' | 'PROJECTION_PHASE_GAP' | 'INVALID_DESCRIBES_FROM' | 'SOLID_DESCRIBES';
1
+ import { type ArchitectureFlow, type ArchitectureProjection, type ArchitectureIr, type ArchitectureNode, type Evidence, type LineStyle, type NodeKind, type UnresolvedQuestion } from './types.js';
2
+ export type ArchitectureValidationErrorCode = 'SOLID_EDGE_WITHOUT_EVIDENCE' | 'CODE_EVIDENCE_NOT_FOUND' | 'PRIVATE_EXCERPT_PRESENT' | 'DANGLING_EDGE' | 'PARENT_NOT_FOUND' | 'PARENT_CYCLE' | 'INVALID_PARENT_KIND' | 'LIVE_COMMAND_NOT_READ_ONLY' | 'ACCOUNT_NOT_FOUND' | 'INVALID_ACCOUNT_KIND' | 'CLOUD_ID_IN_ID' | 'GROUP_MEMBER_NOT_FOUND' | 'DUPLICATE_GROUP_ID' | 'SERVES_SERVICE_WITH_APPS' | 'INVALID_LOADS_ENDS' | 'INVALID_HARNESS_EDGE_ENDS' | 'INVALID_CALL_ACTIONS' | 'UNKNOWN_MCP_ACTION' | 'MD_CODE_EVIDENCE' | 'FLOW_SERVICE_NOT_FOUND' | 'DUPLICATE_FLOW_ID' | 'FLOW_ACTOR_NOT_FOUND' | 'FLOW_STEP_NOT_FOUND' | 'FLOW_REF_NOT_FOUND' | 'INVALID_FLOW_REF_KIND' | 'SOLID_TRANSITION_WITHOUT_EVIDENCE' | 'DUPLICATE_STAGE_ID' | 'STAGE_NODE_NOT_FOUND' | 'STAGE_NODE_TWICE' | 'UNKNOWN_PACK' | 'KIND_NOT_IN_PACKS' | 'CODE_EVIDENCE_IN_DOC_REPO' | 'DUPLICATE_PROJECTION_ID' | 'DUPLICATE_MESSAGE_ID' | 'PROJECTION_NODE_NOT_FOUND' | 'PROJECTION_EDGE_NOT_FOUND' | 'PROJECTION_EDGE_MISMATCH' | 'PROJECTION_BLOCK_NOT_FOUND' | 'PROJECTION_BLOCK_SPLIT' | 'SOLID_MESSAGE_WITHOUT_EVIDENCE' | 'PROJECTION_EMPTY' | 'PROJECTION_SHAPE_FIELD' | 'PROJECTION_FLOW_NOT_FOUND' | 'DUPLICATE_PHASE_ID' | 'PROJECTION_PHASE_MESSAGE_NOT_FOUND' | 'PROJECTION_PHASE_REVERSED' | 'PROJECTION_PHASE_OVERLAP' | 'PROJECTION_PHASE_GAP' | 'INVALID_DESCRIBES_FROM' | 'SOLID_DESCRIBES' | 'COLUMN_REFERENCE_NOT_FOUND' | 'INVALID_COLUMN_REFERENCE_KIND';
3
3
  export interface ArchitectureValidationError {
4
4
  code: ArchitectureValidationErrorCode;
5
5
  message: string;
@@ -11,14 +11,26 @@ export interface ArchitectureValidationError {
11
11
  projectionId?: string;
12
12
  messageId?: string;
13
13
  phaseId?: string;
14
+ /** 노드 컬럼의 근거나 외래 키에서 난 오류. nodeId와 함께 온다 */
15
+ column?: string;
14
16
  evidenceIndex?: number;
15
17
  }
16
18
  /** 렌더를 막지 않는 알림. 저장하지 않고 validate와 render 응답에만 싣는다 */
17
- export interface ArchitectureValidationWarning {
19
+ export type ArchitectureValidationWarning = DescriptionWarning | BudgetWarning;
20
+ export interface DescriptionWarning {
18
21
  code: 'NODE_DESCRIPTION_MISSING';
19
22
  message: string;
20
23
  nodeIds: string[];
21
24
  }
25
+ export type BudgetWarningCode = 'LIFECYCLE_OVER_BUDGET' | 'BLUEPRINT_OVER_BUDGET' | 'ER_OVER_BUDGET' | 'DELTA_OVER_BUDGET';
26
+ /** 그림 한 장이 읽히는 양을 넘었다. 그리기는 하고 나누라고만 알린다 */
27
+ export interface BudgetWarning {
28
+ code: BudgetWarningCode;
29
+ message: string;
30
+ /** 예산을 넘은 그림. 투영이면 투영 id다 */
31
+ subjectId: string;
32
+ counts: Record<string, number>;
33
+ }
22
34
  export interface ValidatedIr {
23
35
  ir: ArchitectureIr;
24
36
  drawableNodeIds: Set<string>;
@@ -52,6 +64,16 @@ export declare const ACCOUNT_ID_RE: RegExp;
52
64
  export declare function deriveLineStyle(evidence: Evidence[]): LineStyle;
53
65
  /** 이 id가 공유본에서 못 가리는 클라우드 식별자를 담는지. kind를 모르면(엣지) 계정 ID만 본다 */
54
66
  export declare function idCarriesCloudId(id: string, kind?: NodeKind): boolean;
67
+ /**
68
+ * er 그림의 participants 가운데 db_table이 아닌 노드 id를 돌려준다. nodes에 없는 id는 PROJECTION_NODE_NOT_FOUND가 따로 잡으니 넣지 않는다.
69
+ * 재실행 이월(store.ts carryProjections)도 이 기준으로 걸러야 이어 실은 그림이 검증에서 render 전체를 막지 않는다
70
+ */
71
+ export declare function erNonTableParticipants(p: Pick<ArchitectureProjection, 'shape' | 'participants'>, kindById: ReadonlyMap<string, ArchitectureNode['kind']>): string[];
72
+ /**
73
+ * lifecycle과 blueprint 그림이 가리킨 흐름에서 그릴 단계를 못 찾으면 true다.
74
+ * 재실행 이월(store.ts carryProjections)도 이 기준으로 걸러야 이어 실은 그림이 검증에서 render 전체를 막지 않는다
75
+ */
76
+ export declare function flowHasNothingToDraw(p: Pick<ArchitectureProjection, 'shape'>, flow: ArchitectureFlow): boolean;
55
77
  /**
56
78
  * ArchitectureIr를 그리기 전에 검증한다.
57
79
  * 근거가 없는 노드나 엣지는 에러로 보지 않고 그리기 대상에서 빼며 미해결 질문을 만든다.
@@ -68,6 +90,8 @@ export declare function maskSharedText(text: string): string;
68
90
  * 리소스 ID가 담기므로 조회 시각만 남긴다. 노드 이름과 질문 글자의 계정 ID 꼴 숫자도 가린다
69
91
  */
70
92
  export declare function redactForSharing(ir: ArchitectureIr): ArchitectureIr;
93
+ /** 공유본의 질문. 저장된 질문과 그릴 때 만든 자동 질문이 같은 규칙으로 가려지게 한 곳에 둔다 */
94
+ export declare function maskSharedQuestion(q: UnresolvedQuestion): UnresolvedQuestion;
71
95
  /** 저장용 사본. private 근거의 excerpt만 지운다. 입력은 바꾸지 않는다 */
72
96
  export declare function stripPrivateExcerpts(ir: ArchitectureIr): ArchitectureIr;
73
97
  //# sourceMappingURL=validator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../src/architecture/validator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../src/architecture/validator.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,9 @@
1
1
  import { readFileSync, statSync } from 'node:fs';
2
2
  import { isAbsolute, relative, resolve } from 'node:path';
3
3
  import { classifyCliCommand } from '../utils/read-only-tools.js';
4
+ import { blueprintCounts, blueprintOverBudget, deriveBlueprint } from './blueprint-layout.js';
5
+ import { deriveEr, erCounts, erOverBudget, isUnconfirmedFk } from './er-layout.js';
6
+ import { deriveLifecycle, lifecycleCounts, lifecycleOverBudget } from './lifecycle-layout.js';
4
7
  import { indexMicroApps } from './micro-app.js';
5
8
  import { ALL_PACKS_VOCABULARY, irVocabulary, packById } from './packs/index.js';
6
9
  import { KNOWLEDGE_DOC_KINDS } from './packs/knowledge.js';
@@ -97,6 +100,51 @@ function checkEvidenceList(evidence, owner, ctx, errors) {
97
100
  }
98
101
  });
99
102
  }
103
+ /**
104
+ * 외래 키는 db_table 노드를 가리켜야 한다. 대상이 컬럼을 적었으면 가리킨 컬럼도 거기 있어야 한다.
105
+ * 대상이 컬럼을 하나도 안 적었으면 없다고 단정할 수 없어 거부하지 않는다
106
+ */
107
+ function checkColumnReferences(ir, errors) {
108
+ const byId = new Map(ir.nodes.map((n) => [n.id, n]));
109
+ for (const node of ir.nodes) {
110
+ for (const c of node.columns ?? []) {
111
+ const ref = c.references;
112
+ if (ref === undefined)
113
+ continue;
114
+ const where = `노드 "${node.id}"의 컬럼 "${c.name}"`;
115
+ const target = byId.get(ref.table);
116
+ if (target === undefined) {
117
+ errors.push({
118
+ code: 'COLUMN_REFERENCE_NOT_FOUND',
119
+ message: `${where}가 가리킨 테이블 "${ref.table}"가 nodes에 없다.`,
120
+ nodeId: node.id,
121
+ column: c.name,
122
+ });
123
+ continue;
124
+ }
125
+ if (target.kind !== 'db_table') {
126
+ errors.push({
127
+ code: 'INVALID_COLUMN_REFERENCE_KIND',
128
+ message: `${where}가 가리킨 "${ref.table}"는 ${target.kind} 노드다. 외래 키는 db_table을 가리킨다.`,
129
+ nodeId: node.id,
130
+ column: c.name,
131
+ });
132
+ continue;
133
+ }
134
+ if (ref.column !== undefined &&
135
+ target.columns !== undefined &&
136
+ target.columns.length > 0 &&
137
+ !target.columns.some((tc) => tc.name === ref.column)) {
138
+ errors.push({
139
+ code: 'COLUMN_REFERENCE_NOT_FOUND',
140
+ message: `${where}가 가리킨 컬럼 "${ref.table}.${ref.column}"가 그 테이블의 columns에 없다.`,
141
+ nodeId: node.id,
142
+ column: c.name,
143
+ });
144
+ }
145
+ }
146
+ }
147
+ }
100
148
  function checkGroups(ir, errors) {
101
149
  const nodeIds = new Set(ir.nodes.map((n) => n.id));
102
150
  const seen = new Set();
@@ -297,6 +345,8 @@ function checkDocRepos(ir, errors) {
297
345
  check(n.evidence, { nodeId: n.id });
298
346
  for (const list of Object.values(n.platformEvidence ?? {}))
299
347
  check(list, { nodeId: n.id });
348
+ for (const c of n.columns ?? [])
349
+ check(c.evidence ?? [], { nodeId: n.id, column: c.name });
300
350
  }
301
351
  for (const e of ir.edges)
302
352
  check(e.evidence, { edgeId: e.id });
@@ -526,6 +576,18 @@ function checkProjections(ir, ctx, errors) {
526
576
  for (const id of p.participants ?? [])
527
577
  missingNode(id, 'participants');
528
578
  checkProjectionShape(p, errors);
579
+ if (p.shape === 'er') {
580
+ const kindById = new Map(ir.nodes.map((n) => [n.id, n.kind]));
581
+ for (const id of erNonTableParticipants(p, kindById)) {
582
+ errors.push({
583
+ code: 'PROJECTION_SHAPE_FIELD',
584
+ message: `투영 "${p.id}"는 er인데 participants의 "${id}"가 ${kindById.get(id)} 노드다. er은 db_table만 그린다.`,
585
+ projectionId: p.id,
586
+ });
587
+ }
588
+ }
589
+ if (FLOW_SHAPES.has(p.shape) && p.flow !== undefined)
590
+ checkProjectionFlow(ir, p, errors);
529
591
  for (const side of p.sides ?? []) {
530
592
  for (const id of side.nodes)
531
593
  missingNode(id, `묶음 "${side.id}"의 nodes`);
@@ -693,6 +755,51 @@ function checkProjectionShape(p, errors) {
693
755
  projectionId: p.id,
694
756
  });
695
757
  };
758
+ if (p.shape === 'lifecycle') {
759
+ if (p.messages.length > 0)
760
+ misuse('messages가 있다. lifecycle은 flow 단계의 상태에서 선을 뽑는다.');
761
+ if (p.participants !== undefined)
762
+ misuse('participants가 있다. lifecycle은 지도 노드를 안 쓴다.');
763
+ }
764
+ if (p.shape === 'blueprint') {
765
+ if (p.messages.length > 0)
766
+ misuse('messages가 있다. blueprint는 선을 긋지 않는다.');
767
+ if (p.participants !== undefined)
768
+ misuse('participants가 있다. blueprint는 flow 단계가 가리킨 노드에서 칸을 채운다.');
769
+ }
770
+ if (FLOW_SHAPES.has(p.shape)) {
771
+ if (p.blocks !== undefined)
772
+ misuse('blocks가 있다. 묶음은 sequence만 쓴다.');
773
+ if (p.phases !== undefined)
774
+ misuse('phases가 있다. 구간은 sequence만 쓴다.');
775
+ if (p.sides !== undefined)
776
+ misuse('sides가 있다. 묶음 견주기는 compare만 쓴다.');
777
+ if (p.flow === undefined)
778
+ misuse(p.shape === 'lifecycle'
779
+ ? 'flow가 없다. 상태를 뽑을 도메인 흐름 id를 적는다.'
780
+ : 'flow가 없다. 단계 열을 뽑을 도메인 흐름 id를 적는다.');
781
+ return;
782
+ }
783
+ if (p.flow !== undefined)
784
+ misuse('flow가 있다. 흐름을 가리키는 건 lifecycle과 blueprint만 쓴다.');
785
+ if (p.shape === 'er') {
786
+ if (p.messages.length > 0)
787
+ misuse('messages가 있다. er은 컬럼의 references에서 외래 키 선을 뽑는다.');
788
+ if (p.blocks !== undefined)
789
+ misuse('blocks가 있다. 묶음은 sequence만 쓴다.');
790
+ if (p.phases !== undefined)
791
+ misuse('phases가 있다. 구간은 sequence만 쓴다.');
792
+ if (p.sides !== undefined)
793
+ misuse('sides가 있다. 묶음 견주기는 compare만 쓴다.');
794
+ if ((p.participants ?? []).length === 0) {
795
+ errors.push({
796
+ code: 'PROJECTION_EMPTY',
797
+ message: `투영 "${p.id}"에 participants가 없다. er은 그릴 db_table 노드 id를 participants에 적는다.`,
798
+ projectionId: p.id,
799
+ });
800
+ }
801
+ return;
802
+ }
696
803
  if (p.shape === 'compare') {
697
804
  if (p.messages.length > 0)
698
805
  misuse('messages가 있다. compare는 sides만 쓴다.');
@@ -726,11 +833,61 @@ function checkProjectionShape(p, errors) {
726
833
  misuse(`메시지 "${seqOnly.id}"에 reply, block, branch가 있다. 순서 표시는 sequence만 쓴다.`);
727
834
  }
728
835
  }
836
+ /** 흐름 하나를 가리켜 그리는 모양. 지도 노드 대신 흐름에서 그림을 뽑는다 */
837
+ const FLOW_SHAPES = new Set([
838
+ 'lifecycle',
839
+ 'blueprint',
840
+ ]);
841
+ /**
842
+ * 가리킨 흐름이 있어야 한다. lifecycle은 상태를 단 단계가, blueprint는 갈림길이 아닌 단계가 하나라도 있어야 그릴 것이 생긴다
843
+ */
844
+ function checkProjectionFlow(ir, p, errors) {
845
+ const flow = (ir.flows ?? []).find((f) => f.id === p.flow);
846
+ if (flow === undefined) {
847
+ errors.push({
848
+ code: 'PROJECTION_FLOW_NOT_FOUND',
849
+ message: `투영 "${p.id}"의 flow "${p.flow}"가 flows에 없다.`,
850
+ projectionId: p.id,
851
+ });
852
+ return;
853
+ }
854
+ if (!flowHasNothingToDraw(p, flow))
855
+ return;
856
+ errors.push({
857
+ code: 'PROJECTION_EMPTY',
858
+ message: p.shape === 'blueprint'
859
+ ? `투영 "${p.id}"가 가리킨 흐름 "${flow.id}"에 갈림길 말고 단계가 없다. blueprint는 단계를 열로 세운다.`
860
+ : `투영 "${p.id}"가 가리킨 흐름 "${flow.id}"에 state를 단 단계가 없다. lifecycle은 단계의 state로 상태를 그린다.`,
861
+ projectionId: p.id,
862
+ flowId: flow.id,
863
+ });
864
+ }
865
+ /**
866
+ * er 그림의 participants 가운데 db_table이 아닌 노드 id를 돌려준다. nodes에 없는 id는 PROJECTION_NODE_NOT_FOUND가 따로 잡으니 넣지 않는다.
867
+ * 재실행 이월(store.ts carryProjections)도 이 기준으로 걸러야 이어 실은 그림이 검증에서 render 전체를 막지 않는다
868
+ */
869
+ export function erNonTableParticipants(p, kindById) {
870
+ if (p.shape !== 'er')
871
+ return [];
872
+ return (p.participants ?? []).filter((id) => {
873
+ const kind = kindById.get(id);
874
+ return kind !== undefined && kind !== 'db_table';
875
+ });
876
+ }
877
+ /**
878
+ * lifecycle과 blueprint 그림이 가리킨 흐름에서 그릴 단계를 못 찾으면 true다.
879
+ * 재실행 이월(store.ts carryProjections)도 이 기준으로 걸러야 이어 실은 그림이 검증에서 render 전체를 막지 않는다
880
+ */
881
+ export function flowHasNothingToDraw(p, flow) {
882
+ if (p.shape === 'blueprint')
883
+ return !flow.steps.some((st) => st.kind !== 'decision');
884
+ return !flow.steps.some((st) => st.state !== undefined);
885
+ }
729
886
  /**
730
887
  * 투영에서 그릴 메시지를 고른다. 근거가 없으면 그리지 않고 질문으로 돌린다.
731
888
  * 양 끝 노드가 안 그려지는 메시지도 뺀다. 선은 근거로 다시 정한다
732
889
  */
733
- function settleProjections(projections, edgeById, drawableNodeIds, nameById, ask) {
890
+ function settleProjections(projections, edgeById, drawableNodeIds, nameById, ask, flows) {
734
891
  const messages = new Set();
735
892
  const drawable = new Set();
736
893
  const settled = projections.map((p) => ({
@@ -758,8 +915,98 @@ function settleProjections(projections, edgeById, drawableNodeIds, nameById, ask
758
915
  drawable.add(p.id);
759
916
  }
760
917
  }
918
+ for (const p of projections) {
919
+ if (p.shape === 'lifecycle' && lifecycleOf(p, flows) !== undefined)
920
+ drawable.add(p.id);
921
+ if (p.shape === 'blueprint' && blueprintOf(p, flows) !== undefined)
922
+ drawable.add(p.id);
923
+ if (p.shape === 'er' && erOf(p, flows) !== undefined)
924
+ drawable.add(p.id);
925
+ }
761
926
  return { projections: settled, messages, drawable };
762
927
  }
928
+ /** 그리는 흐름에서 뽑은 상태가 하나라도 있으면 그 모델. 흐름 레벨이 안 서는 흐름은 서랍을 못 열어 그리지 않는다 */
929
+ function lifecycleOf(p, flows) {
930
+ const flow = flows.flows.find((f) => f.id === p.flow);
931
+ if (flow === undefined || !flows.drawn.has(flow.id))
932
+ return undefined;
933
+ const model = deriveLifecycle(flow, flows.steps, flows.transitions);
934
+ return model.states.length > 0 ? model : undefined;
935
+ }
936
+ /** 그리는 흐름에서 정상 흐름 단계 열이 하나라도 서면 그 모델. 흐름 레벨이 안 서는 흐름은 단계 서랍을 못 열어 그리지 않는다 */
937
+ function blueprintOf(p, flows) {
938
+ const flow = flows.flows.find((f) => f.id === p.flow);
939
+ if (flow === undefined || !flows.drawn.has(flow.id))
940
+ return undefined;
941
+ const model = deriveBlueprint(flow, {
942
+ nodes: flows.nodes,
943
+ edges: flows.edges,
944
+ drawableNodeIds: flows.nodeIds,
945
+ drawableEdgeIds: flows.edgeIds,
946
+ drawableStepIds: flows.steps,
947
+ drawableTransitionIds: flows.transitions,
948
+ });
949
+ return model.columns.length > 0 ? model : undefined;
950
+ }
951
+ /** 그려지는 테이블이 하나라도 서면 그 모델. 근거 없는 테이블은 지도처럼 질문으로만 남는다 */
952
+ function erOf(p, flows) {
953
+ const model = deriveEr(p, { nodes: flows.nodes, drawableNodeIds: flows.nodeIds });
954
+ return model.tables.length > 0 ? model : undefined;
955
+ }
956
+ // 렌더러와 같은 모델에서 세야 경고 숫자와 그림이 어긋나지 않는다
957
+ function budgetWarnings(projections, drawable, flows) {
958
+ const out = [];
959
+ for (const p of projections) {
960
+ if (p.shape === 'er' && drawable.has(p.id)) {
961
+ const model = erOf(p, flows);
962
+ if (model === undefined)
963
+ continue;
964
+ const counts = erCounts(model);
965
+ const over = erOverBudget(counts);
966
+ if (over.length === 0)
967
+ continue;
968
+ out.push({
969
+ code: 'ER_OVER_BUDGET',
970
+ message: `테이블 그림 "${p.title}"이 한 장에 읽히는 양을 넘는다. ${over.join(', ')}. 질문에 걸린 테이블만 남기거나 그림을 나눈다.`,
971
+ subjectId: p.id,
972
+ counts: { ...counts },
973
+ });
974
+ continue;
975
+ }
976
+ if (p.shape === 'blueprint' && drawable.has(p.id)) {
977
+ const model = blueprintOf(p, flows);
978
+ if (model === undefined)
979
+ continue;
980
+ const counts = blueprintCounts(model);
981
+ const over = blueprintOverBudget(counts);
982
+ if (over.length === 0)
983
+ continue;
984
+ out.push({
985
+ code: 'BLUEPRINT_OVER_BUDGET',
986
+ message: `서비스 블루프린트 "${p.title}"이 한 장에 읽히는 양을 넘는다. ${over.join(', ')}. 단계를 합치거나 흐름을 나눈다.`,
987
+ subjectId: p.id,
988
+ counts: { ...counts },
989
+ });
990
+ continue;
991
+ }
992
+ if (p.shape !== 'lifecycle' || !drawable.has(p.id))
993
+ continue;
994
+ const model = lifecycleOf(p, flows);
995
+ if (model === undefined)
996
+ continue;
997
+ const counts = lifecycleCounts(model);
998
+ const over = lifecycleOverBudget(counts);
999
+ if (over.length === 0)
1000
+ continue;
1001
+ out.push({
1002
+ code: 'LIFECYCLE_OVER_BUDGET',
1003
+ message: `상태 흐름 "${p.title}"이 한 장에 읽히는 양을 넘는다. ${over.join(', ')}. 세부 상태를 합치거나 흐름을 나눈다.`,
1004
+ subjectId: p.id,
1005
+ counts: { ...counts },
1006
+ });
1007
+ }
1008
+ return out;
1009
+ }
763
1010
  /**
764
1011
  * 흐름 구조 검사. 단계와 전이 id는 흐름을 넘어 겹치면 안 된다. 질문의 stepId, transitionId가 흐름 id 없이 가리키기 때문이다.
765
1012
  * refs는 기술 그림으로 내려가는 입구라 없는 노드나 화면, API가 아닌 노드를 가리키면 거부한다
@@ -1017,8 +1264,12 @@ export function validateArchitectureIr(ir, opts = {}) {
1017
1264
  for (const platform of Object.keys(node.platformEvidence ?? {}).sort()) {
1018
1265
  checkEvidenceList(node.platformEvidence[platform], { nodeId: node.id }, ctx, errors);
1019
1266
  }
1267
+ for (const c of node.columns ?? []) {
1268
+ checkEvidenceList(c.evidence ?? [], { nodeId: node.id, column: c.name }, ctx, errors);
1269
+ }
1020
1270
  }
1021
1271
  checkPacks(ir, errors);
1272
+ checkColumnReferences(ir, errors);
1022
1273
  checkDocRepos(ir, errors);
1023
1274
  checkParents(ir, errors);
1024
1275
  checkAccounts(ir, errors);
@@ -1124,6 +1375,22 @@ export function validateArchitectureIr(ir, opts = {}) {
1124
1375
  });
1125
1376
  }
1126
1377
  }
1378
+ // 근거 없는 외래 키는 선으로 긋지 않는다. 테이블 하나에 컬럼마다 질문이 설 수 있어 id로 겹침을 본다
1379
+ for (const node of ir.nodes) {
1380
+ if (node.kind !== 'db_table' || !drawableNodeIds.has(node.id))
1381
+ continue;
1382
+ for (const c of node.columns ?? []) {
1383
+ if (!isUnconfirmedFk(c))
1384
+ continue;
1385
+ const ref = c.references;
1386
+ const target = ref.column !== undefined ? `${nameById(ref.table)}.${ref.column}` : nameById(ref.table);
1387
+ askById({
1388
+ id: `auto:fk:${node.id}:${c.name}`,
1389
+ subject: { nodeId: node.id },
1390
+ question: `"${nameOf(node)}"의 "${c.name}" 컬럼이 "${target}"를 가리킨다는 근거를 못 찾았어요. 마이그레이션이나 엔티티 정의가 어디 있나요?`,
1391
+ });
1392
+ }
1393
+ }
1127
1394
  // 진입 앱을 못 정하면 사용자가 받는 도메인이 어느 사슬인지 그림이 말하지 못한다
1128
1395
  const drawn = ir.nodes.filter((n) => drawableNodeIds.has(n.id));
1129
1396
  const apps = indexMicroApps(drawn, edges.filter((e) => drawableEdgeIds.has(e.id)));
@@ -1140,8 +1407,20 @@ export function validateArchitectureIr(ir, opts = {}) {
1140
1407
  });
1141
1408
  }
1142
1409
  const flowResult = settleFlows(ir.flows ?? [], ask, askById);
1410
+ const drawnFlows = {
1411
+ flows: flowResult.flows,
1412
+ drawn: new Set(flowResult.flows
1413
+ .filter((f) => f.service === undefined || drawableNodeIds.has(f.service))
1414
+ .map((f) => f.id)),
1415
+ steps: flowResult.steps,
1416
+ transitions: flowResult.transitions,
1417
+ nodes: ir.nodes,
1418
+ edges,
1419
+ nodeIds: drawableNodeIds,
1420
+ edgeIds: drawableEdgeIds,
1421
+ };
1143
1422
  const projectionResult = ir.projections !== undefined
1144
- ? settleProjections(ir.projections, new Map(ir.edges.map((e) => [e.id, e])), drawableNodeIds, nameById, ask)
1423
+ ? settleProjections(ir.projections, new Map(ir.edges.map((e) => [e.id, e])), drawableNodeIds, nameById, ask, drawnFlows)
1145
1424
  : undefined;
1146
1425
  return {
1147
1426
  ok: true,
@@ -1163,7 +1442,12 @@ export function validateArchitectureIr(ir, opts = {}) {
1163
1442
  }
1164
1443
  : {}),
1165
1444
  autoUnresolved,
1166
- warnings: descriptionWarnings(ir.nodes, drawableNodeIds),
1445
+ warnings: [
1446
+ ...descriptionWarnings(ir.nodes, drawableNodeIds),
1447
+ ...(projectionResult !== undefined
1448
+ ? budgetWarnings(projectionResult.projections, projectionResult.drawable, drawnFlows)
1449
+ : []),
1450
+ ],
1167
1451
  },
1168
1452
  };
1169
1453
  }
@@ -1192,6 +1476,11 @@ function mapEvidence(ir, fn) {
1192
1476
  platformEvidence: Object.fromEntries(Object.entries(n.platformEvidence).map(([p, list]) => [p, list.map(fn)])),
1193
1477
  }
1194
1478
  : {}),
1479
+ ...(n.columns !== undefined
1480
+ ? {
1481
+ columns: n.columns.map((c) => c.evidence !== undefined ? { ...c, evidence: c.evidence.map(fn) } : { ...c }),
1482
+ }
1483
+ : {}),
1195
1484
  });
1196
1485
  const mapEdge = (e) => ({
1197
1486
  ...e,
@@ -1338,6 +1627,28 @@ function maskNodeText(node) {
1338
1627
  ...(node.displayName !== undefined ? { displayName: mask(node.displayName) } : {}),
1339
1628
  ...(node.description !== undefined ? { description: mask(node.description) } : {}),
1340
1629
  ...(doc !== undefined ? { doc: maskDocInfo(doc) } : {}),
1630
+ ...(node.columns !== undefined
1631
+ ? {
1632
+ columns: node.columns.map((c) => ({
1633
+ ...c,
1634
+ name: mask(c.name),
1635
+ ...(c.type !== undefined ? { type: mask(c.type) } : {}),
1636
+ ...(c.references !== undefined
1637
+ ? {
1638
+ references: {
1639
+ ...c.references,
1640
+ ...(c.references.column !== undefined
1641
+ ? { column: mask(c.references.column) }
1642
+ : {}),
1643
+ ...(c.references.onDelete !== undefined
1644
+ ? { onDelete: mask(c.references.onDelete) }
1645
+ : {}),
1646
+ },
1647
+ }
1648
+ : {}),
1649
+ })),
1650
+ }
1651
+ : {}),
1341
1652
  };
1342
1653
  }
1343
1654
  /**
@@ -1366,11 +1677,23 @@ export function redactForSharing(ir) {
1366
1677
  ...(redacted.projections !== undefined
1367
1678
  ? { projections: redacted.projections.map(maskProjectionText) }
1368
1679
  : {}),
1369
- unresolved: redacted.unresolved.map((q) => ({
1370
- ...q,
1371
- question: maskSharedText(q.question),
1372
- ...(q.answer !== undefined ? { answer: maskSharedText(q.answer) } : {}),
1373
- })),
1680
+ ...(redacted.stages !== undefined
1681
+ ? { stages: redacted.stages.map((s) => ({ ...s, label: maskSharedText(s.label) })) }
1682
+ : {}),
1683
+ ...(redacted.groups !== undefined
1684
+ ? { groups: redacted.groups.map((g) => ({ ...g, name: maskSharedText(g.name) })) }
1685
+ : {}),
1686
+ unresolved: redacted.unresolved.map(maskSharedQuestion),
1687
+ };
1688
+ }
1689
+ /** 공유본의 질문. 저장된 질문과 그릴 때 만든 자동 질문이 같은 규칙으로 가려지게 한 곳에 둔다 */
1690
+ export function maskSharedQuestion(q) {
1691
+ return {
1692
+ ...q,
1693
+ // 다른 질문 id는 노드 id만 담지만 외래 키 질문 id는 컬럼 이름을 담는다
1694
+ ...(q.id.startsWith('auto:fk:') ? { id: maskSharedText(q.id) } : {}),
1695
+ question: maskSharedText(q.question),
1696
+ ...(q.answer !== undefined ? { answer: maskSharedText(q.answer) } : {}),
1374
1697
  };
1375
1698
  }
1376
1699
  /** 저장용 사본. private 근거의 excerpt만 지운다. 입력은 바꾸지 않는다 */