@penpair/core 0.3.7 → 0.4.1

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 (142) hide show
  1. package/dist/adapters.d.ts +1 -1
  2. package/dist/adapters.js +4 -0
  3. package/dist/approvals.d.ts +4 -1
  4. package/dist/approvals.js +7 -4
  5. package/dist/baseline-coverage.js +3 -3
  6. package/dist/baselines-config.d.ts +3 -0
  7. package/dist/baselines-config.js +3 -0
  8. package/dist/baselines.d.ts +7 -0
  9. package/dist/baselines.js +90 -21
  10. package/dist/bounded-file.d.ts +2 -0
  11. package/dist/bounded-file.js +38 -0
  12. package/dist/browser-wake.d.ts +22 -0
  13. package/dist/browser-wake.js +241 -0
  14. package/dist/cli-main.js +193 -18
  15. package/dist/design-commands/context.d.ts +9 -0
  16. package/dist/design-commands/context.js +1 -0
  17. package/dist/design-commands/index.d.ts +3 -0
  18. package/dist/design-commands/index.js +7 -0
  19. package/dist/design-commands/map-command.d.ts +4 -0
  20. package/dist/design-commands/map-command.js +685 -0
  21. package/dist/design-commands/release-command.d.ts +3 -0
  22. package/dist/design-commands/release-command.js +239 -0
  23. package/dist/design-releases/artifact-paths.d.ts +6 -0
  24. package/dist/design-releases/artifact-paths.js +82 -0
  25. package/dist/design-releases/assemble.d.ts +3 -0
  26. package/dist/design-releases/assemble.js +176 -0
  27. package/dist/design-releases/check.d.ts +16 -0
  28. package/dist/design-releases/check.js +323 -0
  29. package/dist/design-releases/context.d.ts +2 -0
  30. package/dist/design-releases/context.js +119 -0
  31. package/dist/design-releases/contract-graph.d.ts +13 -0
  32. package/dist/design-releases/contract-graph.js +75 -0
  33. package/dist/design-releases/fidelity.d.ts +23 -0
  34. package/dist/design-releases/fidelity.js +328 -0
  35. package/dist/design-releases/finalize.d.ts +4 -0
  36. package/dist/design-releases/finalize.js +201 -0
  37. package/dist/design-releases/index.d.ts +8 -0
  38. package/dist/design-releases/index.js +7 -0
  39. package/dist/design-releases/live.d.ts +22 -0
  40. package/dist/design-releases/live.js +97 -0
  41. package/dist/design-releases/snippets.d.ts +5 -0
  42. package/dist/design-releases/snippets.js +89 -0
  43. package/dist/design-releases/store.d.ts +10 -0
  44. package/dist/design-releases/store.js +205 -0
  45. package/dist/design-releases/types.d.ts +161 -0
  46. package/dist/design-releases/types.js +1 -0
  47. package/dist/design-releases/validation.d.ts +6 -0
  48. package/dist/design-releases/validation.js +384 -0
  49. package/dist/design-releases/verify.d.ts +3 -0
  50. package/dist/design-releases/verify.js +148 -0
  51. package/dist/errors.js +1 -1
  52. package/dist/image-codec.d.ts +3 -0
  53. package/dist/image-codec.js +78 -0
  54. package/dist/image-evidence.d.ts +15 -0
  55. package/dist/image-evidence.js +121 -0
  56. package/dist/index.d.ts +5 -0
  57. package/dist/index.js +5 -0
  58. package/dist/inspection.js +13 -5
  59. package/dist/instance/config-schema.d.ts +4 -0
  60. package/dist/instance/config-schema.js +7 -0
  61. package/dist/instance/connection.d.ts +2 -0
  62. package/dist/instance/connection.js +15 -3
  63. package/dist/kit-source.js +1 -1
  64. package/dist/layout-policy.js +21 -11
  65. package/dist/layout-settling.d.ts +1 -0
  66. package/dist/layout-settling.js +51 -17
  67. package/dist/layout.d.ts +7 -1
  68. package/dist/layout.js +19 -3
  69. package/dist/map-penpot/content-validation.d.ts +3 -0
  70. package/dist/map-penpot/content-validation.js +57 -0
  71. package/dist/map-penpot/content.d.ts +22 -0
  72. package/dist/map-penpot/content.js +173 -0
  73. package/dist/map-penpot/details.d.ts +4 -0
  74. package/dist/map-penpot/details.js +84 -0
  75. package/dist/map-penpot/evidence-error.d.ts +5 -0
  76. package/dist/map-penpot/evidence-error.js +9 -0
  77. package/dist/map-penpot/icons.d.ts +5 -0
  78. package/dist/map-penpot/icons.js +79 -0
  79. package/dist/map-penpot/import-content.d.ts +2 -0
  80. package/dist/map-penpot/import-content.js +150 -0
  81. package/dist/map-penpot/import.d.ts +5 -0
  82. package/dist/map-penpot/import.js +255 -0
  83. package/dist/map-penpot/index.d.ts +11 -0
  84. package/dist/map-penpot/index.js +10 -0
  85. package/dist/map-penpot/path-geometry.d.ts +5 -0
  86. package/dist/map-penpot/path-geometry.js +51 -0
  87. package/dist/map-penpot/presentation.d.ts +20 -0
  88. package/dist/map-penpot/presentation.js +205 -0
  89. package/dist/map-penpot/projection.d.ts +8 -0
  90. package/dist/map-penpot/projection.js +176 -0
  91. package/dist/map-penpot/publish.d.ts +4 -0
  92. package/dist/map-penpot/publish.js +301 -0
  93. package/dist/map-penpot/release-maps.d.ts +21 -0
  94. package/dist/map-penpot/release-maps.js +104 -0
  95. package/dist/map-penpot/snippets.d.ts +4 -0
  96. package/dist/map-penpot/snippets.js +164 -0
  97. package/dist/map-penpot/status-labels.d.ts +7 -0
  98. package/dist/map-penpot/status-labels.js +7 -0
  99. package/dist/map-penpot/types.d.ts +161 -0
  100. package/dist/map-penpot/types.js +1 -0
  101. package/dist/maps/annotation-layout.d.ts +24 -0
  102. package/dist/maps/annotation-layout.js +85 -0
  103. package/dist/maps/context.d.ts +13 -0
  104. package/dist/maps/context.js +207 -0
  105. package/dist/maps/index.d.ts +9 -0
  106. package/dist/maps/index.js +9 -0
  107. package/dist/maps/layout.d.ts +35 -0
  108. package/dist/maps/layout.js +364 -0
  109. package/dist/maps/proposal.d.ts +5 -0
  110. package/dist/maps/proposal.js +67 -0
  111. package/dist/maps/render.d.ts +4 -0
  112. package/dist/maps/render.js +331 -0
  113. package/dist/maps/review.d.ts +16 -0
  114. package/dist/maps/review.js +90 -0
  115. package/dist/maps/store.d.ts +21 -0
  116. package/dist/maps/store.js +222 -0
  117. package/dist/maps/types.d.ts +206 -0
  118. package/dist/maps/types.js +1 -0
  119. package/dist/maps/validation.d.ts +10 -0
  120. package/dist/maps/validation.js +375 -0
  121. package/dist/plugin-health.d.ts +13 -0
  122. package/dist/plugin-health.js +39 -0
  123. package/dist/skill-routing.js +4 -0
  124. package/dist/version.d.ts +1 -1
  125. package/dist/version.js +1 -1
  126. package/package.json +2 -1
  127. package/skills/penpair-design-release/SKILL.md +55 -0
  128. package/skills/penpair-design-release/agents/openai.yaml +6 -0
  129. package/skills/penpair-design-release/references/workflow.md +55 -0
  130. package/skills/penpair-diagrams/SKILL.md +52 -0
  131. package/skills/penpair-diagrams/agents/openai.yaml +6 -0
  132. package/skills/penpair-diagrams/references/workflow.md +84 -0
  133. package/skills/penpair-handoff/SKILL.md +7 -0
  134. package/skills/penpair-handoff/references/handoff-flow.md +7 -1
  135. package/skills/penpair-integration-map/SKILL.md +44 -0
  136. package/skills/penpair-integration-map/agents/openai.yaml +6 -0
  137. package/skills/penpair-integration-map/references/workflow.md +33 -0
  138. package/skills/penpair-journey-map/SKILL.md +42 -0
  139. package/skills/penpair-journey-map/agents/openai.yaml +6 -0
  140. package/skills/penpair-journey-map/references/workflow.md +35 -0
  141. package/skills/penpair-verify/SKILL.md +7 -0
  142. package/skills/penpair-verify/references/browser-evidence.md +10 -0
@@ -0,0 +1,173 @@
1
+ import { mapAnnotationLayout } from "../maps/annotation-layout.js";
2
+ import { mapLabelHeight } from "../maps/layout.js";
3
+ import { mapOriginLabels, mapOriginLegend } from "./status-labels.js";
4
+ export const nativeMapRoles = [
5
+ "node",
6
+ "label",
7
+ "edge",
8
+ "edge-label",
9
+ "title",
10
+ "badge",
11
+ "legend",
12
+ "map-summary",
13
+ "map-full-summary",
14
+ "map-goal",
15
+ "map-description",
16
+ "summary",
17
+ "full-label",
18
+ "full-summary",
19
+ "description",
20
+ "annotation-panel",
21
+ "annotation-id",
22
+ "annotation-heading",
23
+ "icon-well",
24
+ "icon",
25
+ "port-in",
26
+ "port-out",
27
+ ];
28
+ export function nativeMapTextEntries(drawing) {
29
+ const result = [];
30
+ const add = (role, logicalId, text, x, y, width, height, fontSize = 14, color = "#101828", semantic, strong = false) => {
31
+ result.push({
32
+ role,
33
+ logicalId,
34
+ text,
35
+ x,
36
+ y,
37
+ width,
38
+ height,
39
+ fontSize,
40
+ color,
41
+ ...(semantic
42
+ ? {
43
+ semantic,
44
+ }
45
+ : {}),
46
+ ...(strong
47
+ ? {
48
+ strong,
49
+ }
50
+ : {}),
51
+ });
52
+ };
53
+ const titleBox = drawing.header?.titleBox ?? {
54
+ x: 32,
55
+ y: 24,
56
+ width: 900,
57
+ height: 40,
58
+ };
59
+ add("title", drawing.mapId, drawing.title, titleBox.x, titleBox.y, titleBox.width, titleBox.height, 24, "#101828", {
60
+ scope: "map",
61
+ field: "title",
62
+ }, true);
63
+ const headerText = drawing.headerSummary ??
64
+ drawing.summary ??
65
+ (drawing.goal && drawing.goal.length <= 240 ? drawing.goal : undefined);
66
+ if (headerText) {
67
+ const box = drawing.header?.summaryBox ?? {
68
+ x: 32,
69
+ y: 72,
70
+ width: 900,
71
+ height: mapLabelHeight(headerText, 900),
72
+ };
73
+ add("map-summary", drawing.mapId, headerText, box.x, box.y, box.width, box.height, 14, "#667085", {
74
+ scope: "map",
75
+ field: drawing.summary !== undefined ? "summary" : "goal",
76
+ });
77
+ }
78
+ if (drawing.nodes.some((node) => node.origin)) {
79
+ add("legend", drawing.mapId, mapOriginLegend, 32, drawing.header?.legendY ?? (headerText ? 132 : 72), 1000, 20, 10, "#667085");
80
+ }
81
+ for (const node of drawing.nodes) {
82
+ const modern = node.cardTitle !== undefined;
83
+ const title = modern
84
+ ? node.cardTitle
85
+ : (node.kind === "decision" ? "Decision: " : node.kind === "detail-link" ? "Details: " : "") +
86
+ node.label;
87
+ add("label", node.id, title, node.x + 64, node.y + 18, node.width - 80, 32, 14, "#101828", {
88
+ scope: "node",
89
+ field: "label",
90
+ nodeId: node.id,
91
+ compact: modern && title !== node.label,
92
+ }, true);
93
+ const summary = node.cardSummary ?? node.summary;
94
+ if (summary) {
95
+ add("summary", node.id, summary, node.x + 64, node.y + 50, node.width - 80, 48, 12, "#667085", {
96
+ scope: "node",
97
+ field: "summary",
98
+ nodeId: node.id,
99
+ compact: summary !== node.summary,
100
+ });
101
+ }
102
+ if (node.origin) {
103
+ add("badge", node.id, mapOriginLabels[node.origin], node.x + 16, node.y + node.height - 24, node.width - 32, 16, 10, "#667085");
104
+ }
105
+ }
106
+ for (const edge of drawing.edges) {
107
+ if (edge.label && edge.labelBox) {
108
+ add("edge-label", edge.id, edge.label, edge.labelBox.x, edge.labelBox.y, edge.labelBox.width, edge.labelBox.height, 12, "#667085");
109
+ }
110
+ }
111
+ for (const annotation of drawing.annotations ?? []) {
112
+ const node = annotation.nodeId
113
+ ? drawing.nodes.find((candidate) => candidate.id === annotation.nodeId)
114
+ : undefined;
115
+ const id = node?.id ?? drawing.mapId;
116
+ const layout = mapAnnotationLayout(annotation);
117
+ const x = annotation.x + 20;
118
+ const width = annotation.width - 40;
119
+ add("annotation-id", annotation.id, node ? `Node · ${node.id}` : "Map context", x, annotation.y + layout.idBox.y, width, layout.idBox.height, 10, "#667085");
120
+ if (node && layout.titleBox) {
121
+ add("full-label", node.id, node.label, x, annotation.y + layout.titleBox.y, width, layout.titleBox.height, 16, "#101828", {
122
+ scope: "node",
123
+ field: "label",
124
+ nodeId: node.id,
125
+ }, true);
126
+ }
127
+ for (const fieldLayout of layout.fields) {
128
+ const field = fieldLayout.field;
129
+ const value = annotation[field];
130
+ add("annotation-heading", `${annotation.id}:${field}`, field[0].toUpperCase() + field.slice(1), x, annotation.y + fieldLayout.headingY, width, fieldLayout.headingHeight, 11, "#667085", undefined, true);
131
+ const role = node
132
+ ? field === "summary"
133
+ ? "full-summary"
134
+ : "description"
135
+ : field === "goal"
136
+ ? "map-goal"
137
+ : field === "summary"
138
+ ? "map-full-summary"
139
+ : "map-description";
140
+ add(role, id, value, x, annotation.y + fieldLayout.y, width, fieldLayout.height, 14, "#344054", {
141
+ scope: node ? "node" : "map",
142
+ field,
143
+ ...(node
144
+ ? {
145
+ nodeId: node.id,
146
+ }
147
+ : {}),
148
+ });
149
+ }
150
+ }
151
+ return result;
152
+ }
153
+ export function nativeMapShapeKeys(drawing) {
154
+ return new Set([
155
+ ...nativeMapTextEntries(drawing).map((entry) => `${entry.role}:${entry.logicalId}`),
156
+ ...drawing.nodes.flatMap((node) => [
157
+ "node",
158
+ "icon-well",
159
+ "icon",
160
+ "port-in",
161
+ "port-out",
162
+ ].map((role) => `${role}:${node.id}`)),
163
+ ...drawing.edges.flatMap((edge) => [
164
+ `edge:${edge.id}`,
165
+ ...(edge.label
166
+ ? [
167
+ `edge-label:${edge.id}`,
168
+ ]
169
+ : []),
170
+ ]),
171
+ ...(drawing.annotations ?? []).map((annotation) => `annotation-panel:${annotation.id}`),
172
+ ]);
173
+ }
@@ -0,0 +1,4 @@
1
+ import type { MapDrawing } from "./types.js";
2
+ /** Resolve only explicitly pinned direct contracts; never recurse into another map's sources. */
3
+ export declare function resolveMapDetails(root: string, drawing: MapDrawing): Promise<MapDrawing>;
4
+ export declare function requireResolvedMapDetails(drawing: MapDrawing): void;
@@ -0,0 +1,84 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readProjectBytes } from "../bounded-file.js";
3
+ import { hashMapContract, maxMapBytes, validateMapContract, validateMapDetail, } from "../maps/validation.js";
4
+ /** Resolve only explicitly pinned direct contracts; never recurse into another map's sources. */
5
+ export async function resolveMapDetails(root, drawing) {
6
+ const references = drawing.nodes.filter((node) => node.detail);
7
+ if (references.length > 64) {
8
+ throw new Error("Native map supports at most 64 direct detail references");
9
+ }
10
+ const identity = drawing.mapId.match(/^(journey-map|integration-map):(.+)$/);
11
+ if (references.length && !identity) {
12
+ throw new Error("Native detail maps require a namespaced canonical parent identity");
13
+ }
14
+ const parent = {
15
+ kind: identity?.[1],
16
+ id: identity?.[2] ?? "",
17
+ sources: references.map((node) => ({
18
+ ...node.detail,
19
+ id: node.detail.sourceId,
20
+ kind: "contract",
21
+ })),
22
+ };
23
+ let totalBytes = 0;
24
+ const cache = new Map();
25
+ const resolved = new Map();
26
+ for (const node of references) {
27
+ const detail = node.detail;
28
+ const key = `${detail.path}:${detail.sha256}`;
29
+ let contract = cache.get(key);
30
+ if (!contract) {
31
+ const bytes = await readProjectBytes(root, detail.path, maxMapBytes);
32
+ totalBytes += bytes.length;
33
+ if (totalBytes > 128 * 1024 * 1024) {
34
+ throw new Error("Direct detail contracts exceed the 128 MiB aggregate budget");
35
+ }
36
+ if (createHash("sha256").update(bytes).digest("hex") !== detail.sha256) {
37
+ throw new Error(`Pinned detail contract changed: ${detail.sourceId}`);
38
+ }
39
+ let raw;
40
+ try {
41
+ raw = JSON.parse(bytes.toString("utf8"));
42
+ }
43
+ catch {
44
+ throw new Error(`Invalid detail contract JSON: ${detail.sourceId}`);
45
+ }
46
+ contract = validateMapContract(raw);
47
+ cache.set(key, contract);
48
+ }
49
+ validateMapDetail(parent, {
50
+ id: node.id,
51
+ sourceIds: [
52
+ detail.sourceId,
53
+ ],
54
+ requirementIds: node.requirementIds,
55
+ }, contract);
56
+ const identity = {
57
+ mapId: `${contract.kind}:${contract.id}`,
58
+ contractHash: hashMapContract(contract),
59
+ };
60
+ if ((detail.mapId !== undefined && detail.mapId !== identity.mapId) ||
61
+ (detail.contractHash !== undefined && detail.contractHash !== identity.contractHash)) {
62
+ throw new Error(`Resolved detail contract identity differs: ${detail.sourceId}`);
63
+ }
64
+ resolved.set(key, identity);
65
+ }
66
+ return {
67
+ ...drawing,
68
+ nodes: drawing.nodes.map((node) => node.detail
69
+ ? {
70
+ ...node,
71
+ detail: {
72
+ ...node.detail,
73
+ ...resolved.get(`${node.detail.path}:${node.detail.sha256}`),
74
+ },
75
+ }
76
+ : node),
77
+ };
78
+ }
79
+ export function requireResolvedMapDetails(drawing) {
80
+ if (drawing.nodes.some((node) => node.detail &&
81
+ (!node.detail.mapId || !/^[a-f0-9]{64}$/.test(node.detail.contractHash ?? "")))) {
82
+ throw new Error("Resolve pinned detail contract identities before using native map evidence");
83
+ }
84
+ }
@@ -0,0 +1,5 @@
1
+ import { PenpairError } from "../errors.js";
2
+ export declare class MapExportEvidenceError extends PenpairError {
3
+ readonly runId: string;
4
+ constructor(runId: string, detail: string);
5
+ }
@@ -0,0 +1,9 @@
1
+ import { PenpairError } from "../errors.js";
2
+ export class MapExportEvidenceError extends PenpairError {
3
+ runId;
4
+ constructor(runId, detail) {
5
+ super("incomplete-output", `Native map write is verified, but export evidence failed for run ${runId}: ${detail}. Inspect the run and rerun the original publish command with --apply --resume ${runId}.`);
6
+ this.runId = runId;
7
+ this.name = "MapExportEvidenceError";
8
+ }
9
+ }
@@ -0,0 +1,5 @@
1
+ import type { MapIcon } from "../maps/types.js";
2
+ import type { ProjectionShape } from "./types.js";
3
+ export declare const nativeMapIcons: Record<MapIcon, string>;
4
+ export declare function defaultMapIcon(kind: string): MapIcon;
5
+ export declare function identifyMapIcon(shape: Pick<ProjectionShape, "content">): MapIcon | undefined;
@@ -0,0 +1,79 @@
1
+ export const nativeMapIcons = {
2
+ screen: "M0 0 L24 0 L24 18 L0 18 L0 0 M6 24 L18 24 M12 18 L12 24",
3
+ action: "M14 0 L2 14 L11 14 L10 24 L22 9 L13 9 L14 0",
4
+ decision: "M12 0 L24 12 L12 24 L0 12 L12 0",
5
+ data: "M0 0 L24 0 L24 24 L0 24 L0 0 M0 8 L24 8 M0 16 L24 16",
6
+ service: "M0 0 L24 0 L24 10 L0 10 L0 0 M0 14 L24 14 L24 24 L0 24 L0 14 M5 5 L8 5 M5 19 L8 19",
7
+ code: "M8 0 L0 12 L8 24 M16 0 L24 12 L16 24",
8
+ check: "M0 12 L8 22 L24 0",
9
+ link: "M0 8 L0 0 L16 0 L16 16 L8 16 M8 8 L24 8 L24 24 L8 24 L8 8",
10
+ };
11
+ export function defaultMapIcon(kind) {
12
+ if (kind === "decision") {
13
+ return "decision";
14
+ }
15
+ if (kind === "detail-link") {
16
+ return "link";
17
+ }
18
+ if (/screen|view|page|route/.test(kind)) {
19
+ return "screen";
20
+ }
21
+ if (/data|store|state/.test(kind)) {
22
+ return "data";
23
+ }
24
+ if (/service|api|request/.test(kind)) {
25
+ return "service";
26
+ }
27
+ if (/component|hook|code/.test(kind)) {
28
+ return "code";
29
+ }
30
+ if (/success|outcome|complete/.test(kind)) {
31
+ return "check";
32
+ }
33
+ return "action";
34
+ }
35
+ function normalizedIcon(content) {
36
+ if (typeof content !== "string" || content.length > 8192) {
37
+ return undefined;
38
+ }
39
+ const tokens = content.match(/[ML]|[-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?/g);
40
+ if (!tokens || tokens.join("") !== content.replace(/[\s,]/g, "")) {
41
+ return undefined;
42
+ }
43
+ const points = [];
44
+ for (let index = 0; index < tokens.length; index += 3) {
45
+ const command = tokens[index];
46
+ const x = Number(tokens[index + 1]);
47
+ const y = Number(tokens[index + 2]);
48
+ if (![
49
+ "M",
50
+ "L",
51
+ ].includes(command ?? "") ||
52
+ !Number.isFinite(x) ||
53
+ !Number.isFinite(y)) {
54
+ return undefined;
55
+ }
56
+ points.push({
57
+ command: command,
58
+ x,
59
+ y,
60
+ });
61
+ }
62
+ const minX = Math.min(...points.map((point) => point.x));
63
+ const minY = Math.min(...points.map((point) => point.y));
64
+ const width = Math.max(...points.map((point) => point.x)) - minX;
65
+ const height = Math.max(...points.map((point) => point.y)) - minY;
66
+ if (width <= 0 || height <= 0) {
67
+ return undefined;
68
+ }
69
+ return points
70
+ .map((point) => `${point.command}${Math.round(((point.x - minX) / width) * 10000)},${Math.round(((point.y - minY) / height) * 10000)}`)
71
+ .join(" ");
72
+ }
73
+ export function identifyMapIcon(shape) {
74
+ const actual = normalizedIcon(shape.content);
75
+ if (!actual) {
76
+ return undefined;
77
+ }
78
+ return Object.keys(nativeMapIcons).find((icon) => normalizedIcon(nativeMapIcons[icon]) === actual);
79
+ }
@@ -0,0 +1,2 @@
1
+ import type { MapDrawing, NativeMapProjection, NativeMapProposal } from "./types.js";
2
+ export declare function importNativeContent(drawing: MapDrawing, observed: NativeMapProjection, findings: string[]): Pick<NativeMapProposal, "changes" | "metadataChanges">;
@@ -0,0 +1,150 @@
1
+ import { nativeMapTextEntries } from "./content.js";
2
+ import { defaultMapIcon, identifyMapIcon } from "./icons.js";
3
+ export function importNativeContent(drawing, observed, findings) {
4
+ const entries = nativeMapTextEntries(drawing);
5
+ const changes = [];
6
+ const metadataChanges = {};
7
+ const read = (scope, field, nodeId) => {
8
+ const relevant = entries.filter((entry) => entry.semantic?.scope === scope &&
9
+ entry.semantic.field === field &&
10
+ entry.semantic.nodeId === nodeId);
11
+ const changed = relevant.flatMap((entry) => {
12
+ const actual = observed.shapes.find((shape) => shape.role === entry.role && shape.logicalId === entry.logicalId);
13
+ const text = actual?.label?.trim() ?? "";
14
+ return text !== entry.text
15
+ ? [
16
+ {
17
+ text,
18
+ compact: entry.semantic?.compact === true,
19
+ },
20
+ ]
21
+ : [];
22
+ });
23
+ if (!relevant.length) {
24
+ const roles = scope === "map"
25
+ ? field === "goal"
26
+ ? [
27
+ "map-goal",
28
+ ]
29
+ : field === "summary"
30
+ ? [
31
+ "map-summary",
32
+ ]
33
+ : [
34
+ "map-description",
35
+ ]
36
+ : field === "label"
37
+ ? [
38
+ "label",
39
+ "full-label",
40
+ ]
41
+ : field === "summary"
42
+ ? [
43
+ "summary",
44
+ "full-summary",
45
+ ]
46
+ : [
47
+ "description",
48
+ ];
49
+ const actual = observed.shapes.find((shape) => roles.includes(shape.role) &&
50
+ shape.logicalId === (nodeId ?? drawing.mapId) &&
51
+ !entries.some((entry) => entry.role === shape.role &&
52
+ entry.logicalId === shape.logicalId &&
53
+ entry.semantic &&
54
+ entry.semantic.field !== field));
55
+ return actual?.label?.trim() || undefined;
56
+ }
57
+ if (!changed.length) {
58
+ return undefined;
59
+ }
60
+ const full = changed.find((change) => !change.compact);
61
+ const selected = full ?? changed[0];
62
+ if (selected.compact && selected.text.endsWith("…")) {
63
+ findings.push(`Edit the full ${field} annotation for ${nodeId ?? drawing.mapId}; compact text is incomplete`);
64
+ return undefined;
65
+ }
66
+ if (changed.some((change) => change.text !== selected.text && !selected.text.startsWith(change.text.replace(/…$/, "")))) {
67
+ findings.push(`Conflicting compact/full ${field} edits: ${nodeId ?? drawing.mapId}`);
68
+ }
69
+ return selected.text || null;
70
+ };
71
+ for (const field of [
72
+ "title",
73
+ "goal",
74
+ "summary",
75
+ "description",
76
+ ]) {
77
+ const value = read("map", field);
78
+ if (value === undefined) {
79
+ continue;
80
+ }
81
+ if (field === "goal" || field === "title") {
82
+ if (!value) {
83
+ findings.push("Map goal cannot be empty");
84
+ }
85
+ else {
86
+ metadataChanges[field] = value;
87
+ }
88
+ }
89
+ else {
90
+ metadataChanges[field] = value;
91
+ }
92
+ }
93
+ for (const node of drawing.nodes) {
94
+ const change = {
95
+ nodeId: node.id,
96
+ };
97
+ for (const field of [
98
+ "label",
99
+ "summary",
100
+ "description",
101
+ ]) {
102
+ let value = read("node", field, node.id);
103
+ if (value === undefined) {
104
+ continue;
105
+ }
106
+ if (field === "label") {
107
+ if (!value) {
108
+ findings.push(`Node title cannot be empty: ${node.id}`);
109
+ continue;
110
+ }
111
+ if (node.cardTitle === undefined) {
112
+ value =
113
+ node.kind === "decision"
114
+ ? value.replace(/^Decision: /, "")
115
+ : node.kind === "detail-link"
116
+ ? value.replace(/^Details: /, "")
117
+ : value;
118
+ }
119
+ change.label = value;
120
+ }
121
+ else {
122
+ change[field] = value;
123
+ }
124
+ }
125
+ const icon = observed.shapes.find((shape) => shape.role === "icon" && shape.logicalId === node.id);
126
+ const key = icon ? identifyMapIcon(icon) : undefined;
127
+ if (!key) {
128
+ findings.push(`Unrecognized or missing native icon: ${node.id}`);
129
+ }
130
+ else if (icon?.iconKey &&
131
+ icon.iconKey !== key &&
132
+ icon.iconKey !== (node.icon ?? defaultMapIcon(node.kind))) {
133
+ findings.push(`Icon metadata and glyph disagree: ${node.id}`);
134
+ }
135
+ else if (key !== (node.icon ?? defaultMapIcon(node.kind))) {
136
+ change.icon = key;
137
+ }
138
+ if (Object.keys(change).length > 1) {
139
+ changes.push(change);
140
+ }
141
+ }
142
+ return {
143
+ changes,
144
+ ...(Object.keys(metadataChanges).length
145
+ ? {
146
+ metadataChanges,
147
+ }
148
+ : {}),
149
+ };
150
+ }
@@ -0,0 +1,5 @@
1
+ import type { MapContract, MapProposal } from "../maps/types.js";
2
+ import type { MapDrawing, NativeMapProjection, NativeMapProposal } from "./types.js";
3
+ export declare function importMapProposal(drawing: MapDrawing, observed: NativeMapProjection, baseline?: NativeMapProjection): NativeMapProposal;
4
+ /** Convert a native observation into the canonical reviewed proposal format. */
5
+ export declare function nativeProposalToMapProposal(map: MapContract, drawing: MapDrawing, observed: NativeMapProjection, baseline?: NativeMapProjection): MapProposal;