canicode 0.11.0 → 0.11.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.
- package/README.md +16 -4
- package/dist/cli/index.js +520 -20
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +20 -17
- package/dist/index.js +20 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +24 -4
- package/dist/mcp/server.js.map +1 -1
- package/docs/CUSTOMIZATION.md +24 -1
- package/package.json +1 -1
- package/skills/canicode/SKILL.md +6 -0
- package/skills/canicode-gotchas/SKILL.md +38 -59
- package/skills/canicode-roundtrip/SKILL.md +39 -260
- package/skills/canicode-roundtrip/helpers.js +287 -17
- package/skills/cursor/canicode/SKILL.md +6 -0
- package/skills/cursor/canicode-gotchas/SKILL.md +38 -59
- package/skills/cursor/canicode-roundtrip/SKILL.md +39 -260
- package/skills/cursor/canicode-roundtrip/helpers.js +287 -17
|
@@ -78,8 +78,189 @@ ${footer}`;
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
// src/core/roundtrip/annotation-payload.ts
|
|
82
|
+
var CANICODE_JSON_FENCE = "```canicode-json";
|
|
83
|
+
function formatIntentValueForDisplay(value) {
|
|
84
|
+
if (value === void 0) return "undefined";
|
|
85
|
+
if (value === null) return "null";
|
|
86
|
+
if (typeof value === "object") {
|
|
87
|
+
try {
|
|
88
|
+
return `\`${JSON.stringify(value)}\``;
|
|
89
|
+
} catch {
|
|
90
|
+
return String(value);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return `\`${String(value)}\``;
|
|
94
|
+
}
|
|
95
|
+
function buildCodegenDirective(args) {
|
|
96
|
+
const { sceneNodeId, intent } = args;
|
|
97
|
+
const val = intent.value === void 0 ? "undefined" : JSON.stringify(intent.value);
|
|
98
|
+
return `When generating code for node ${sceneNodeId}, set ${intent.field} to ${val} (user intent, scope: ${intent.scope}). Prefer this over the current Figma scene value when they disagree.`;
|
|
99
|
+
}
|
|
100
|
+
function sceneOutcomeToAck(result, reason) {
|
|
101
|
+
return reason !== void 0 ? { result, reason } : { result };
|
|
102
|
+
}
|
|
103
|
+
function buildOutcomeHumanLine(args) {
|
|
104
|
+
if (args.skippedDefinitionDueToAdr012) {
|
|
105
|
+
const adrHint = " Canicode skipped writing the source component without `allowDefinitionWrite: true` (ADR-012 safer default). The instance-level change did not apply as intended in the scene.";
|
|
106
|
+
if (args.reason === "silent-ignore") {
|
|
107
|
+
return "**Scene write outcome:** The write ran, but the property value did not change on this instance (silent-ignore)." + adrHint;
|
|
108
|
+
}
|
|
109
|
+
return "**Scene write outcome:** Figma rejected an instance-level change" + (args.errorMessage ? `: ${args.errorMessage}` : "") + "." + adrHint;
|
|
110
|
+
}
|
|
111
|
+
if (args.reason === "silent-ignore") {
|
|
112
|
+
return "**Scene write outcome:** The write ran, but the property value did not change on this instance (silent-ignore). No source definition was available to escalate.";
|
|
113
|
+
}
|
|
114
|
+
if (args.reason === "override-error") {
|
|
115
|
+
return "**Scene write outcome:** Figma rejected an instance-level change" + (args.errorMessage ? `: ${args.errorMessage}` : "") + ". No source definition was available to escalate.";
|
|
116
|
+
}
|
|
117
|
+
return "**Scene write outcome:** Could not apply automatically" + (args.errorMessage ? `: ${args.errorMessage}` : "") + ".";
|
|
118
|
+
}
|
|
119
|
+
function buildAdr012PropagationParagraph(args) {
|
|
120
|
+
const { componentName, replicaCount } = args;
|
|
121
|
+
const fanOutHint = typeof replicaCount === "number" && replicaCount >= 2 ? ` This batched question covers ${replicaCount} instance scenes \u2014 changing **${componentName}** at the definition still affects every inheriting instance, not just one row in the batch.` : "";
|
|
122
|
+
return `Canicode's safer default (ADR-012) is to skip writing the source component **${componentName}** without explicit opt-in, because that write propagates to every non-overridden instance of **${componentName}** in the file.${fanOutHint} Prefer a manual override on **this** instance when you only need a local fix. Use \`allowDefinitionWrite: true\` only when you intend to change **${componentName}** for all inheriting instances \u2014 it is not a neutral shortcut for a single-instance tweak.`;
|
|
123
|
+
}
|
|
124
|
+
function buildDefinitionWriteSkippedBody(args) {
|
|
125
|
+
const {
|
|
126
|
+
ruleId,
|
|
127
|
+
sceneNodeId,
|
|
128
|
+
componentName,
|
|
129
|
+
reason,
|
|
130
|
+
errorMessage,
|
|
131
|
+
replicaCount,
|
|
132
|
+
intent
|
|
133
|
+
} = args;
|
|
134
|
+
const ackIntent = intent ? {
|
|
135
|
+
field: intent.field,
|
|
136
|
+
value: intent.value,
|
|
137
|
+
scope: intent.scope
|
|
138
|
+
} : void 0;
|
|
139
|
+
const sceneWriteOutcome = sceneOutcomeToAck("user-declined-propagation", "adr-012-opt-in-disabled");
|
|
140
|
+
const codegenDirective = intent !== void 0 ? buildCodegenDirective({ sceneNodeId, intent }) : void 0;
|
|
141
|
+
const jsonBlock = {
|
|
142
|
+
v: 1,
|
|
143
|
+
ruleId,
|
|
144
|
+
nodeId: sceneNodeId,
|
|
145
|
+
...ackIntent ? { intent: ackIntent } : {},
|
|
146
|
+
sceneWriteOutcome,
|
|
147
|
+
...codegenDirective ? { codegenDirective } : {}
|
|
148
|
+
};
|
|
149
|
+
const userAnswerLine = intent !== void 0 ? `**User answered:** ${formatIntentValueForDisplay(intent.value)} for **${intent.field}** (scope: ${intent.scope}).` : null;
|
|
150
|
+
const outcomeLine = buildOutcomeHumanLine({
|
|
151
|
+
reason,
|
|
152
|
+
...errorMessage !== void 0 ? { errorMessage } : {},
|
|
153
|
+
skippedDefinitionDueToAdr012: true
|
|
154
|
+
});
|
|
155
|
+
const adrBlock = buildAdr012PropagationParagraph({
|
|
156
|
+
componentName,
|
|
157
|
+
...replicaCount !== void 0 ? { replicaCount } : {}
|
|
158
|
+
});
|
|
159
|
+
const proseParts = [userAnswerLine, outcomeLine, adrBlock].filter(
|
|
160
|
+
(p) => p !== null
|
|
161
|
+
);
|
|
162
|
+
const prose = proseParts.join("\n\n");
|
|
163
|
+
return appendJsonFenceAndFooter(prose, jsonBlock, ruleId);
|
|
164
|
+
}
|
|
165
|
+
function buildNoDefinitionFallbackBody(args) {
|
|
166
|
+
const { ruleId, sceneNodeId, reason, errorMessage, intent } = args;
|
|
167
|
+
const ackIntent = intent ? { field: intent.field, value: intent.value, scope: intent.scope } : void 0;
|
|
168
|
+
const outcomeResult = reason === "silent-ignore" ? "silent-ignored" : reason === "override-error" ? "api-rejected" : "api-rejected";
|
|
169
|
+
const sceneWriteOutcome = sceneOutcomeToAck(
|
|
170
|
+
outcomeResult,
|
|
171
|
+
reason === "silent-ignore" ? "silent-ignore-no-definition" : "no-definition-escalation"
|
|
172
|
+
);
|
|
173
|
+
const codegenDirective = intent !== void 0 ? buildCodegenDirective({ sceneNodeId, intent }) : void 0;
|
|
174
|
+
const jsonBlock = {
|
|
175
|
+
v: 1,
|
|
176
|
+
ruleId,
|
|
177
|
+
nodeId: sceneNodeId,
|
|
178
|
+
...ackIntent ? { intent: ackIntent } : {},
|
|
179
|
+
sceneWriteOutcome,
|
|
180
|
+
...codegenDirective ? { codegenDirective } : {}
|
|
181
|
+
};
|
|
182
|
+
const userAnswerLine = intent !== void 0 ? `**User answered:** ${formatIntentValueForDisplay(intent.value)} for **${intent.field}** (scope: ${intent.scope}).` : null;
|
|
183
|
+
const outcomeLine = buildOutcomeHumanLine({
|
|
184
|
+
reason,
|
|
185
|
+
...errorMessage !== void 0 ? { errorMessage } : {},
|
|
186
|
+
skippedDefinitionDueToAdr012: false
|
|
187
|
+
});
|
|
188
|
+
const prose = [userAnswerLine, outcomeLine].filter((p) => p !== null).join("\n\n");
|
|
189
|
+
return appendJsonFenceAndFooter(prose, jsonBlock, ruleId);
|
|
190
|
+
}
|
|
191
|
+
function buildDefinitionTierFailureBody(args) {
|
|
192
|
+
const { ruleId, sceneNodeId, intent, kind, errorMessage } = args;
|
|
193
|
+
const sceneWriteOutcome = sceneOutcomeToAck(
|
|
194
|
+
kind === "read-only-library" ? "api-rejected" : "api-rejected",
|
|
195
|
+
kind === "read-only-library" ? "definition-read-only" : "definition-write-failed"
|
|
196
|
+
);
|
|
197
|
+
const codegenDirective = intent !== void 0 ? buildCodegenDirective({ sceneNodeId, intent }) : void 0;
|
|
198
|
+
const jsonBlock = {
|
|
199
|
+
v: 1,
|
|
200
|
+
ruleId,
|
|
201
|
+
nodeId: sceneNodeId,
|
|
202
|
+
...intent ? {
|
|
203
|
+
intent: {
|
|
204
|
+
field: intent.field,
|
|
205
|
+
value: intent.value,
|
|
206
|
+
scope: intent.scope
|
|
207
|
+
}
|
|
208
|
+
} : {},
|
|
209
|
+
sceneWriteOutcome,
|
|
210
|
+
...codegenDirective ? { codegenDirective } : {}
|
|
211
|
+
};
|
|
212
|
+
const human = kind === "read-only-library" ? "source component lives in an external library and is read-only from this file \u2014 apply the fix in the library file itself." : `could not apply at source definition: ${errorMessage}`;
|
|
213
|
+
const userAnswerLine = intent !== void 0 ? `**User answered:** ${formatIntentValueForDisplay(intent.value)} for **${intent.field}** (scope: ${intent.scope}).` : null;
|
|
214
|
+
const outcomeLine = `**Scene write outcome:** ${human}`;
|
|
215
|
+
const prose = [userAnswerLine, outcomeLine].filter((p) => p !== null).join("\n\n");
|
|
216
|
+
return appendJsonFenceAndFooter(prose, jsonBlock, ruleId);
|
|
217
|
+
}
|
|
218
|
+
function appendJsonFenceAndFooter(prose, jsonBlock, ruleId) {
|
|
219
|
+
const footer = `\u2014 *${ruleId}*`;
|
|
220
|
+
const hasIntent = jsonBlock.intent !== void 0;
|
|
221
|
+
if (!hasIntent) {
|
|
222
|
+
return `${prose}
|
|
223
|
+
|
|
224
|
+
${footer}`;
|
|
225
|
+
}
|
|
226
|
+
const jsonText = JSON.stringify(jsonBlock, null, 0);
|
|
227
|
+
return `${prose}
|
|
228
|
+
|
|
229
|
+
${CANICODE_JSON_FENCE}
|
|
230
|
+
${jsonText}
|
|
231
|
+
\`\`\`
|
|
232
|
+
|
|
233
|
+
${footer}`;
|
|
234
|
+
}
|
|
235
|
+
var FENCED_JSON_RE = new RegExp(
|
|
236
|
+
`${CANICODE_JSON_FENCE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*([\\s\\S]*?)\\s*\`\`\``,
|
|
237
|
+
"m"
|
|
238
|
+
);
|
|
239
|
+
function parseCanicodeJsonPayloadFromMarkdown(text) {
|
|
240
|
+
const m = FENCED_JSON_RE.exec(text);
|
|
241
|
+
if (!m?.[1]) return void 0;
|
|
242
|
+
try {
|
|
243
|
+
const raw = JSON.parse(m[1].trim());
|
|
244
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
245
|
+
const o = raw;
|
|
246
|
+
if (o.v !== 1 || typeof o.ruleId !== "string") return void 0;
|
|
247
|
+
return raw;
|
|
248
|
+
} catch {
|
|
249
|
+
return void 0;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
81
253
|
// src/core/roundtrip/apply-with-instance-fallback.ts
|
|
82
254
|
var DEFINITION_WRITE_SKIPPED_EVENT = "cic_roundtrip_definition_write_skipped";
|
|
255
|
+
function categoryIdForAnnotate(categories, kind, roundtripIntent) {
|
|
256
|
+
if (kind === "adr012-definition-skipped") {
|
|
257
|
+
return categories.fallback;
|
|
258
|
+
}
|
|
259
|
+
if (roundtripIntent !== void 0) {
|
|
260
|
+
return categories.gotcha;
|
|
261
|
+
}
|
|
262
|
+
return categories.flag;
|
|
263
|
+
}
|
|
83
264
|
function resolveSourceComponentName(definition, question) {
|
|
84
265
|
if (definition && typeof definition.name === "string" && definition.name) {
|
|
85
266
|
return definition.name;
|
|
@@ -93,11 +274,24 @@ ${footer}`;
|
|
|
93
274
|
async function routeToDefinitionOrAnnotate(definition, writeFn, ctx) {
|
|
94
275
|
if (definition && !ctx.allowDefinitionWrite && ctx.reason !== "non-override-error") {
|
|
95
276
|
const componentName = resolveSourceComponentName(definition, ctx.question);
|
|
277
|
+
const replicaCount = typeof ctx.question.replicas === "number" && Number.isInteger(ctx.question.replicas) ? ctx.question.replicas : void 0;
|
|
96
278
|
if (ctx.categories) {
|
|
97
279
|
upsertCanicodeAnnotation(ctx.scene, {
|
|
98
280
|
ruleId: ctx.question.ruleId,
|
|
99
|
-
markdown:
|
|
100
|
-
|
|
281
|
+
markdown: buildDefinitionWriteSkippedBody({
|
|
282
|
+
ruleId: ctx.question.ruleId,
|
|
283
|
+
sceneNodeId: ctx.scene.id,
|
|
284
|
+
componentName,
|
|
285
|
+
reason: ctx.reason,
|
|
286
|
+
...ctx.errorMessage !== void 0 ? { errorMessage: ctx.errorMessage } : {},
|
|
287
|
+
...replicaCount !== void 0 ? { replicaCount } : {},
|
|
288
|
+
...ctx.roundtripIntent !== void 0 ? { intent: ctx.roundtripIntent } : {}
|
|
289
|
+
}),
|
|
290
|
+
categoryId: categoryIdForAnnotate(
|
|
291
|
+
ctx.categories,
|
|
292
|
+
"adr012-definition-skipped",
|
|
293
|
+
ctx.roundtripIntent
|
|
294
|
+
)
|
|
101
295
|
});
|
|
102
296
|
}
|
|
103
297
|
ctx.telemetry?.(DEFINITION_WRITE_SKIPPED_EVENT, {
|
|
@@ -111,11 +305,21 @@ ${footer}`;
|
|
|
111
305
|
}
|
|
112
306
|
if (!definition) {
|
|
113
307
|
if (ctx.categories) {
|
|
114
|
-
const markdown =
|
|
308
|
+
const markdown = buildNoDefinitionFallbackBody({
|
|
309
|
+
ruleId: ctx.question.ruleId,
|
|
310
|
+
sceneNodeId: ctx.scene.id,
|
|
311
|
+
reason: ctx.reason,
|
|
312
|
+
...ctx.errorMessage !== void 0 ? { errorMessage: ctx.errorMessage } : {},
|
|
313
|
+
...ctx.roundtripIntent !== void 0 ? { intent: ctx.roundtripIntent } : {}
|
|
314
|
+
});
|
|
115
315
|
upsertCanicodeAnnotation(ctx.scene, {
|
|
116
316
|
ruleId: ctx.question.ruleId,
|
|
117
317
|
markdown,
|
|
118
|
-
categoryId:
|
|
318
|
+
categoryId: categoryIdForAnnotate(
|
|
319
|
+
ctx.categories,
|
|
320
|
+
"other-failure",
|
|
321
|
+
ctx.roundtripIntent
|
|
322
|
+
)
|
|
119
323
|
});
|
|
120
324
|
}
|
|
121
325
|
return ctx.reason === "silent-ignore" ? { icon: "\u{1F4DD}", label: "silent-ignore, annotated" } : { icon: "\u{1F4DD}", label: `error: ${ctx.errorMessage ?? ""}` };
|
|
@@ -132,8 +336,18 @@ ${footer}`;
|
|
|
132
336
|
if (ctx.categories) {
|
|
133
337
|
upsertCanicodeAnnotation(ctx.scene, {
|
|
134
338
|
ruleId: ctx.question.ruleId,
|
|
135
|
-
markdown:
|
|
136
|
-
|
|
339
|
+
markdown: buildDefinitionTierFailureBody({
|
|
340
|
+
ruleId: ctx.question.ruleId,
|
|
341
|
+
sceneNodeId: ctx.scene.id,
|
|
342
|
+
...ctx.roundtripIntent !== void 0 ? { intent: ctx.roundtripIntent } : {},
|
|
343
|
+
kind: isRemoteReadOnly ? "read-only-library" : "definition-error",
|
|
344
|
+
errorMessage: defMsg
|
|
345
|
+
}),
|
|
346
|
+
categoryId: categoryIdForAnnotate(
|
|
347
|
+
ctx.categories,
|
|
348
|
+
"other-failure",
|
|
349
|
+
ctx.roundtripIntent
|
|
350
|
+
)
|
|
137
351
|
});
|
|
138
352
|
}
|
|
139
353
|
return {
|
|
@@ -143,7 +357,7 @@ ${footer}`;
|
|
|
143
357
|
}
|
|
144
358
|
}
|
|
145
359
|
async function applyWithInstanceFallback(question, writeFn, context = {}) {
|
|
146
|
-
const { categories, allowDefinitionWrite = false, telemetry } = context;
|
|
360
|
+
const { categories, allowDefinitionWrite = false, telemetry, roundtripIntent } = context;
|
|
147
361
|
const scene = await figma.getNodeByIdAsync(question.nodeId);
|
|
148
362
|
if (!scene) return { icon: "\u{1F4DD}", label: "missing node" };
|
|
149
363
|
const definition = question.sourceChildId ? await figma.getNodeByIdAsync(question.sourceChildId) : null;
|
|
@@ -156,7 +370,8 @@ ${footer}`;
|
|
|
156
370
|
categories,
|
|
157
371
|
reason: "silent-ignore",
|
|
158
372
|
allowDefinitionWrite,
|
|
159
|
-
telemetry
|
|
373
|
+
telemetry,
|
|
374
|
+
...roundtripIntent !== void 0 ? { roundtripIntent } : {}
|
|
160
375
|
});
|
|
161
376
|
}
|
|
162
377
|
return { icon: "\u2705", label: "instance/scene" };
|
|
@@ -171,7 +386,8 @@ ${footer}`;
|
|
|
171
386
|
reason: "non-override-error",
|
|
172
387
|
errorMessage: msg,
|
|
173
388
|
allowDefinitionWrite,
|
|
174
|
-
telemetry
|
|
389
|
+
telemetry,
|
|
390
|
+
...roundtripIntent !== void 0 ? { roundtripIntent } : {}
|
|
175
391
|
});
|
|
176
392
|
}
|
|
177
393
|
return routeToDefinitionOrAnnotate(definition, writeFn, {
|
|
@@ -181,7 +397,8 @@ ${footer}`;
|
|
|
181
397
|
reason: "override-error",
|
|
182
398
|
errorMessage: msg,
|
|
183
399
|
allowDefinitionWrite,
|
|
184
|
-
telemetry
|
|
400
|
+
telemetry,
|
|
401
|
+
...roundtripIntent !== void 0 ? { roundtripIntent } : {}
|
|
185
402
|
});
|
|
186
403
|
}
|
|
187
404
|
}
|
|
@@ -220,6 +437,39 @@ ${footer}`;
|
|
|
220
437
|
target.setBoundVariable(prop, variable);
|
|
221
438
|
return true;
|
|
222
439
|
}
|
|
440
|
+
function buildRoundtripIntentFromPropertyAnswer(question, answerValue) {
|
|
441
|
+
const raw = question.targetProperty;
|
|
442
|
+
if (raw === void 0) return void 0;
|
|
443
|
+
const props = Array.isArray(raw) ? raw : [raw];
|
|
444
|
+
if (props.length === 0) return void 0;
|
|
445
|
+
if (props.length === 1) {
|
|
446
|
+
const prop = props[0];
|
|
447
|
+
const perProp = answerValue && typeof answerValue === "object" && !("variable" in answerValue) && !Array.isArray(answerValue) ? answerValue[prop] : answerValue;
|
|
448
|
+
const parsed = parseValueForIntent(perProp);
|
|
449
|
+
if (parsed === void 0) return void 0;
|
|
450
|
+
return { field: prop, value: parsed, scope: "instance" };
|
|
451
|
+
}
|
|
452
|
+
const obj = answerValue && typeof answerValue === "object" && !("variable" in answerValue) && !Array.isArray(answerValue) ? answerValue : void 0;
|
|
453
|
+
const picked = {};
|
|
454
|
+
for (const p of props) {
|
|
455
|
+
if (obj && p in obj && obj[p] !== void 0) picked[p] = obj[p];
|
|
456
|
+
}
|
|
457
|
+
if (Object.keys(picked).length === 0) return void 0;
|
|
458
|
+
return {
|
|
459
|
+
field: props.join(", "),
|
|
460
|
+
value: picked,
|
|
461
|
+
scope: "instance"
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
function parseValueForIntent(raw) {
|
|
465
|
+
if (raw && typeof raw === "object" && "variable" in raw) {
|
|
466
|
+
return { variable: raw.variable };
|
|
467
|
+
}
|
|
468
|
+
if (raw && typeof raw === "object" && "fallback" in raw) {
|
|
469
|
+
return raw.fallback;
|
|
470
|
+
}
|
|
471
|
+
return raw;
|
|
472
|
+
}
|
|
223
473
|
function applyPropertyScalar(target, prop, scalar) {
|
|
224
474
|
const rec = target;
|
|
225
475
|
const before = rec[prop];
|
|
@@ -228,6 +478,10 @@ ${footer}`;
|
|
|
228
478
|
return true;
|
|
229
479
|
}
|
|
230
480
|
async function applyPropertyMod(question, answerValue, context = {}) {
|
|
481
|
+
const roundtripIntent = buildRoundtripIntentFromPropertyAnswer(
|
|
482
|
+
question,
|
|
483
|
+
answerValue
|
|
484
|
+
);
|
|
231
485
|
const props = Array.isArray(question.targetProperty) ? question.targetProperty : question.targetProperty !== void 0 ? [question.targetProperty] : [];
|
|
232
486
|
return applyWithInstanceFallback(
|
|
233
487
|
question,
|
|
@@ -258,7 +512,10 @@ ${footer}`;
|
|
|
258
512
|
}
|
|
259
513
|
return changed;
|
|
260
514
|
},
|
|
261
|
-
|
|
515
|
+
{
|
|
516
|
+
...context,
|
|
517
|
+
...roundtripIntent !== void 0 ? { roundtripIntent } : {}
|
|
518
|
+
}
|
|
262
519
|
);
|
|
263
520
|
}
|
|
264
521
|
|
|
@@ -334,7 +591,15 @@ ${footer}`;
|
|
|
334
591
|
}
|
|
335
592
|
const ruleId = extractRuleId(text);
|
|
336
593
|
if (!ruleId) continue;
|
|
337
|
-
|
|
594
|
+
const payload = parseCanicodeJsonPayloadFromMarkdown(text);
|
|
595
|
+
const payloadAligned = payload && payload.ruleId === ruleId;
|
|
596
|
+
out.push({
|
|
597
|
+
nodeId: node.id,
|
|
598
|
+
ruleId,
|
|
599
|
+
...payloadAligned && payload.intent ? { intent: payload.intent } : {},
|
|
600
|
+
...payloadAligned && payload.sceneWriteOutcome ? { sceneWriteOutcome: payload.sceneWriteOutcome } : {},
|
|
601
|
+
...payloadAligned && payload.codegenDirective ? { codegenDirective: payload.codegenDirective } : {}
|
|
602
|
+
});
|
|
338
603
|
}
|
|
339
604
|
return out;
|
|
340
605
|
}
|
|
@@ -360,17 +625,22 @@ ${footer}`;
|
|
|
360
625
|
walk(root, canicodeCategoryIds, out);
|
|
361
626
|
return out;
|
|
362
627
|
}
|
|
628
|
+
function safeChildren(node) {
|
|
629
|
+
try {
|
|
630
|
+
const c = node.children;
|
|
631
|
+
return Array.isArray(c) ? c : [];
|
|
632
|
+
} catch {
|
|
633
|
+
return [];
|
|
634
|
+
}
|
|
635
|
+
}
|
|
363
636
|
function walk(node, canicodeCategoryIds, out) {
|
|
364
637
|
try {
|
|
365
638
|
const local = extractAcknowledgmentsFromNode(node, canicodeCategoryIds);
|
|
366
639
|
for (const a of local) out.push(a);
|
|
367
640
|
} catch {
|
|
368
641
|
}
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
for (const child of children) {
|
|
372
|
-
if (child && typeof child === "object") walk(child, canicodeCategoryIds, out);
|
|
373
|
-
}
|
|
642
|
+
for (const child of safeChildren(node)) {
|
|
643
|
+
if (child && typeof child === "object") walk(child, canicodeCategoryIds, out);
|
|
374
644
|
}
|
|
375
645
|
}
|
|
376
646
|
|