canicode 0.12.0 → 0.12.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/dist/cli/index.js +394 -47
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +119 -27
- package/dist/index.js +238 -11
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +252 -15
- package/dist/mcp/server.js.map +1 -1
- package/package.json +1 -1
- package/skills/canicode-roundtrip/SKILL.md +46 -8
- package/skills/canicode-roundtrip/helpers-bootstrap.js +1 -1
- package/skills/canicode-roundtrip/helpers-installer.js +2 -2
- package/skills/canicode-roundtrip/helpers.js +41 -1
- package/skills/cursor/canicode-roundtrip/SKILL.md +46 -8
- package/skills/cursor/canicode-roundtrip/helpers-bootstrap.js +1 -1
- package/skills/cursor/canicode-roundtrip/helpers-installer.js +2 -2
- package/skills/cursor/canicode-roundtrip/helpers.js +41 -1
package/dist/mcp/server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { writeFile, existsSync, statSync, mkdirSync,
|
|
3
|
-
import { join, resolve, basename, dirname } from 'path';
|
|
2
|
+
import { writeFile, existsSync, statSync, mkdirSync, readFileSync, chmodSync, writeFileSync, readdirSync, copyFileSync } from 'fs';
|
|
3
|
+
import { join, resolve, basename, dirname, isAbsolute, sep } from 'path';
|
|
4
4
|
import pixelmatch from 'pixelmatch';
|
|
5
5
|
import { PNG } from 'pngjs';
|
|
6
6
|
import { createRequire } from 'module';
|
|
@@ -695,11 +695,31 @@ function defineRule(rule) {
|
|
|
695
695
|
ruleRegistry.register(rule);
|
|
696
696
|
return rule;
|
|
697
697
|
}
|
|
698
|
-
var
|
|
698
|
+
var PropertyAcknowledgmentIntentSchema = z.object({
|
|
699
|
+
kind: z.literal("property").default("property"),
|
|
699
700
|
field: z.string(),
|
|
700
701
|
value: z.unknown(),
|
|
701
702
|
scope: z.enum(["instance", "definition"])
|
|
702
703
|
});
|
|
704
|
+
var RuleOptOutAcknowledgmentIntentSchema = z.object({
|
|
705
|
+
kind: z.literal("rule-opt-out"),
|
|
706
|
+
ruleId: z.string()
|
|
707
|
+
}).strict();
|
|
708
|
+
var AcknowledgmentIntentSchema = z.preprocess((raw) => {
|
|
709
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
710
|
+
const obj = raw;
|
|
711
|
+
if (obj["kind"] === void 0) {
|
|
712
|
+
return { ...obj, kind: "property" };
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return raw;
|
|
716
|
+
}, z.discriminatedUnion("kind", [
|
|
717
|
+
PropertyAcknowledgmentIntentSchema,
|
|
718
|
+
RuleOptOutAcknowledgmentIntentSchema
|
|
719
|
+
]));
|
|
720
|
+
function isRuleOptOutIntent(intent) {
|
|
721
|
+
return intent !== void 0 && intent.kind === "rule-opt-out";
|
|
722
|
+
}
|
|
703
723
|
var AcknowledgmentSceneWriteOutcomeSchema = z.object({
|
|
704
724
|
result: z.enum([
|
|
705
725
|
"succeeded",
|
|
@@ -777,6 +797,7 @@ var RuleEngine = class {
|
|
|
777
797
|
excludeNamePattern;
|
|
778
798
|
excludeNodeTypes;
|
|
779
799
|
acknowledgments;
|
|
800
|
+
acknowledgmentsByKey;
|
|
780
801
|
scopeOverride;
|
|
781
802
|
constructor(options = {}) {
|
|
782
803
|
this.configs = options.configs ?? RULE_CONFIGS;
|
|
@@ -785,10 +806,15 @@ var RuleEngine = class {
|
|
|
785
806
|
this.targetNodeId = options.targetNodeId;
|
|
786
807
|
this.excludeNamePattern = options.excludeNodeNames && options.excludeNodeNames.length > 0 ? new RegExp(`\\b(${options.excludeNodeNames.join("|")})\\b`, "i") : null;
|
|
787
808
|
this.excludeNodeTypes = options.excludeNodeTypes && options.excludeNodeTypes.length > 0 ? new Set(options.excludeNodeTypes) : null;
|
|
809
|
+
const ackList = options.acknowledgments ?? [];
|
|
788
810
|
this.acknowledgments = new Set(
|
|
789
|
-
(
|
|
790
|
-
|
|
791
|
-
|
|
811
|
+
ackList.map((a) => `${normalizeNodeId(a.nodeId)}::${a.ruleId}`)
|
|
812
|
+
);
|
|
813
|
+
this.acknowledgmentsByKey = new Map(
|
|
814
|
+
ackList.map((a) => [
|
|
815
|
+
`${normalizeNodeId(a.nodeId)}::${a.ruleId}`,
|
|
816
|
+
a
|
|
817
|
+
])
|
|
792
818
|
);
|
|
793
819
|
this.scopeOverride = options.scope;
|
|
794
820
|
}
|
|
@@ -872,6 +898,7 @@ var RuleEngine = class {
|
|
|
872
898
|
if (this.excludeNamePattern && this.excludeNamePattern.test(node.name)) {
|
|
873
899
|
return;
|
|
874
900
|
}
|
|
901
|
+
const acknowledgmentsByKey = this.acknowledgmentsByKey;
|
|
875
902
|
const context = {
|
|
876
903
|
file,
|
|
877
904
|
parent,
|
|
@@ -883,7 +910,8 @@ var RuleEngine = class {
|
|
|
883
910
|
siblings,
|
|
884
911
|
analysisState,
|
|
885
912
|
scope,
|
|
886
|
-
rootNodeType
|
|
913
|
+
rootNodeType,
|
|
914
|
+
findAcknowledgment: (nodeId, ruleId) => acknowledgmentsByKey.get(`${normalizeNodeId(nodeId)}::${ruleId}`)
|
|
887
915
|
};
|
|
888
916
|
for (const rule of rules) {
|
|
889
917
|
const ruleId = rule.definition.id;
|
|
@@ -1047,6 +1075,32 @@ var FigmaClient = class _FigmaClient {
|
|
|
1047
1075
|
const buffer = await response.arrayBuffer();
|
|
1048
1076
|
return Buffer.from(buffer).toString("base64");
|
|
1049
1077
|
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Get the components a file has published to a team library.
|
|
1080
|
+
*
|
|
1081
|
+
* `GET /v1/files/:file_key/components` returns only components that have
|
|
1082
|
+
* been pushed via the Publish Library action — local-but-unpublished
|
|
1083
|
+
* components are absent. This is the authoritative way to detect whether
|
|
1084
|
+
* a Figma component is mappable via Code Connect (#532): `add_code_connect_map`
|
|
1085
|
+
* requires a published component and otherwise fails with "Published
|
|
1086
|
+
* component not found."
|
|
1087
|
+
*/
|
|
1088
|
+
async getPublishedComponents(fileKey) {
|
|
1089
|
+
const url = `${FIGMA_API_BASE}/files/${fileKey}/components`;
|
|
1090
|
+
const response = await fetch(url, {
|
|
1091
|
+
headers: { "X-Figma-Token": this.token }
|
|
1092
|
+
});
|
|
1093
|
+
if (!response.ok) {
|
|
1094
|
+
const error = await response.json().catch(() => ({}));
|
|
1095
|
+
throw new FigmaClientError(
|
|
1096
|
+
`Failed to fetch published components: ${response.status} ${response.statusText}`,
|
|
1097
|
+
response.status,
|
|
1098
|
+
error
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
const data = await response.json();
|
|
1102
|
+
return data.meta?.components ?? [];
|
|
1103
|
+
}
|
|
1050
1104
|
async getFileNodes(fileKey, nodeIds) {
|
|
1051
1105
|
const ids = nodeIds.join(",");
|
|
1052
1106
|
const url = `${FIGMA_API_BASE}/files/${fileKey}/nodes?ids=${encodeURIComponent(ids)}`;
|
|
@@ -1889,7 +1943,7 @@ function computeApplyContext(violation, instanceContext) {
|
|
|
1889
1943
|
}
|
|
1890
1944
|
|
|
1891
1945
|
// package.json
|
|
1892
|
-
var version = "0.12.
|
|
1946
|
+
var version = "0.12.1";
|
|
1893
1947
|
|
|
1894
1948
|
// src/core/engine/scoring.ts
|
|
1895
1949
|
var GRADE_ORDER = ["S", "A+", "A", "B+", "B", "C+", "C", "D", "F"];
|
|
@@ -2081,6 +2135,19 @@ function formatScoreSummary(report) {
|
|
|
2081
2135
|
}
|
|
2082
2136
|
return lines.join("\n");
|
|
2083
2137
|
}
|
|
2138
|
+
function formatCodeConnectCoverageLine(coverage) {
|
|
2139
|
+
const { mapped, total } = coverage;
|
|
2140
|
+
const pct = total === 0 ? 0 : Math.round(mapped / total * 100);
|
|
2141
|
+
return `Code Connect coverage: ${mapped}/${total} components (${pct}%) mapped`;
|
|
2142
|
+
}
|
|
2143
|
+
var ROUNDTRIP_OPT_OUT_HINT = "Some components may carry roundtrip-recorded opt-outs that this standalone analyze cannot see (Figma REST annotations field is in private beta). Run /canicode-roundtrip to apply opt-outs.";
|
|
2144
|
+
function formatRoundtripOptOutHintLine(issues, acknowledgmentsProvided) {
|
|
2145
|
+
const hasUnmapped = issues.some(
|
|
2146
|
+
(issue) => issue.violation.ruleId === "unmapped-component"
|
|
2147
|
+
);
|
|
2148
|
+
if (!hasUnmapped) return null;
|
|
2149
|
+
return ROUNDTRIP_OPT_OUT_HINT;
|
|
2150
|
+
}
|
|
2084
2151
|
function buildResultJson(fileName, result, scores, options) {
|
|
2085
2152
|
const issuesByRule = {};
|
|
2086
2153
|
for (const issue of result.issues) {
|
|
@@ -2110,6 +2177,14 @@ function buildResultJson(fileName, result, scores, options) {
|
|
|
2110
2177
|
...issue.acknowledged === true ? { acknowledged: true } : {}
|
|
2111
2178
|
};
|
|
2112
2179
|
});
|
|
2180
|
+
const optOutHint = options?.roundtripOptOutHintEligible ? formatRoundtripOptOutHintLine(result.issues) : null;
|
|
2181
|
+
const summaryParts = [formatScoreSummary(scores)];
|
|
2182
|
+
if (options?.codeConnectCoverage) {
|
|
2183
|
+
summaryParts.push(formatCodeConnectCoverageLine(options.codeConnectCoverage));
|
|
2184
|
+
}
|
|
2185
|
+
if (optOutHint) {
|
|
2186
|
+
summaryParts.push(optOutHint);
|
|
2187
|
+
}
|
|
2113
2188
|
const json = {
|
|
2114
2189
|
version,
|
|
2115
2190
|
analyzedAt: result.analyzedAt,
|
|
@@ -2129,13 +2204,153 @@ function buildResultJson(fileName, result, scores, options) {
|
|
|
2129
2204
|
},
|
|
2130
2205
|
issuesByRule,
|
|
2131
2206
|
issues,
|
|
2132
|
-
summary:
|
|
2207
|
+
summary: summaryParts.join("\n\n")
|
|
2133
2208
|
};
|
|
2209
|
+
if (options?.codeConnectCoverage) {
|
|
2210
|
+
json["codeConnectCoverage"] = options.codeConnectCoverage;
|
|
2211
|
+
}
|
|
2212
|
+
if (optOutHint) {
|
|
2213
|
+
json["roundtripOptOutHint"] = optOutHint;
|
|
2214
|
+
}
|
|
2134
2215
|
if (result.failedRules.length > 0) {
|
|
2135
2216
|
json["failedRules"] = result.failedRules;
|
|
2136
2217
|
}
|
|
2137
2218
|
return json;
|
|
2138
2219
|
}
|
|
2220
|
+
var FIGMA_CONFIG_FILENAME = "figma.config.json";
|
|
2221
|
+
var FIGMA_CONNECT_FILE_GLOB = /\.figma\.(tsx?|jsx?)$/;
|
|
2222
|
+
var NODE_ID_QUERY_RE = /[?&]node-id=([0-9A-Za-z%:\-_]+)/;
|
|
2223
|
+
function parseCodeConnectMappings(cwd) {
|
|
2224
|
+
const configPath = join(cwd, FIGMA_CONFIG_FILENAME);
|
|
2225
|
+
if (!existsSync(configPath)) {
|
|
2226
|
+
return {
|
|
2227
|
+
mappedNodeIds: /* @__PURE__ */ new Set(),
|
|
2228
|
+
scannedFiles: [],
|
|
2229
|
+
skipReason: "no-config",
|
|
2230
|
+
skippedReason: `${FIGMA_CONFIG_FILENAME} not found at ${cwd}`
|
|
2231
|
+
};
|
|
2232
|
+
}
|
|
2233
|
+
let config2;
|
|
2234
|
+
try {
|
|
2235
|
+
config2 = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
2236
|
+
} catch (err) {
|
|
2237
|
+
return {
|
|
2238
|
+
mappedNodeIds: /* @__PURE__ */ new Set(),
|
|
2239
|
+
scannedFiles: [],
|
|
2240
|
+
skipReason: "malformed-config",
|
|
2241
|
+
skippedReason: `malformed ${FIGMA_CONFIG_FILENAME}: ${err.message}`
|
|
2242
|
+
};
|
|
2243
|
+
}
|
|
2244
|
+
const includes = config2.codeConnect?.include ?? config2.include ?? [];
|
|
2245
|
+
if (includes.length === 0) {
|
|
2246
|
+
return {
|
|
2247
|
+
mappedNodeIds: /* @__PURE__ */ new Set(),
|
|
2248
|
+
scannedFiles: [],
|
|
2249
|
+
skipReason: "no-includes",
|
|
2250
|
+
skippedReason: `${FIGMA_CONFIG_FILENAME} has no codeConnect.include paths`
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
2253
|
+
const candidateFiles = /* @__PURE__ */ new Set();
|
|
2254
|
+
for (const includePattern of includes) {
|
|
2255
|
+
for (const file of resolveInclude(cwd, includePattern)) {
|
|
2256
|
+
candidateFiles.add(file);
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
const mappedNodeIds = /* @__PURE__ */ new Set();
|
|
2260
|
+
const scannedFiles = [];
|
|
2261
|
+
for (const file of candidateFiles) {
|
|
2262
|
+
scannedFiles.push(file);
|
|
2263
|
+
let contents;
|
|
2264
|
+
try {
|
|
2265
|
+
contents = readFileSync(file, "utf-8");
|
|
2266
|
+
} catch {
|
|
2267
|
+
continue;
|
|
2268
|
+
}
|
|
2269
|
+
for (const nodeId of extractNodeIdsFromSource(contents)) {
|
|
2270
|
+
mappedNodeIds.add(nodeId);
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
return { mappedNodeIds, scannedFiles };
|
|
2274
|
+
}
|
|
2275
|
+
function resolveInclude(cwd, includePattern) {
|
|
2276
|
+
const results = [];
|
|
2277
|
+
const absolute = isAbsolute(includePattern) ? includePattern : resolve(cwd, includePattern);
|
|
2278
|
+
const segments = absolute.split(sep);
|
|
2279
|
+
let firstGlobIdx = segments.findIndex((s) => /[*?{[]/.test(s));
|
|
2280
|
+
if (firstGlobIdx === -1) {
|
|
2281
|
+
if (existsSync(absolute)) {
|
|
2282
|
+
const stat = statSync(absolute);
|
|
2283
|
+
if (stat.isFile() && FIGMA_CONNECT_FILE_GLOB.test(absolute)) {
|
|
2284
|
+
results.push(absolute);
|
|
2285
|
+
} else if (stat.isDirectory()) {
|
|
2286
|
+
walkDir(absolute, results);
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
return results;
|
|
2290
|
+
}
|
|
2291
|
+
const rootSegments = segments.slice(0, firstGlobIdx);
|
|
2292
|
+
const root = rootSegments.length === 0 ? sep : rootSegments.join(sep);
|
|
2293
|
+
if (!existsSync(root)) return results;
|
|
2294
|
+
const rootStat = statSync(root);
|
|
2295
|
+
if (!rootStat.isDirectory()) return results;
|
|
2296
|
+
walkDir(root, results);
|
|
2297
|
+
const prefix = rootSegments.join(sep) + sep;
|
|
2298
|
+
return results.filter((f) => f.startsWith(prefix) || rootSegments.length === 0);
|
|
2299
|
+
}
|
|
2300
|
+
function walkDir(dir, out) {
|
|
2301
|
+
let entries;
|
|
2302
|
+
try {
|
|
2303
|
+
entries = readdirSync(dir);
|
|
2304
|
+
} catch {
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
for (const entry of entries) {
|
|
2308
|
+
if (entry === "node_modules" || entry.startsWith(".")) continue;
|
|
2309
|
+
const full = join(dir, entry);
|
|
2310
|
+
let stat;
|
|
2311
|
+
try {
|
|
2312
|
+
stat = statSync(full);
|
|
2313
|
+
} catch {
|
|
2314
|
+
continue;
|
|
2315
|
+
}
|
|
2316
|
+
if (stat.isDirectory()) {
|
|
2317
|
+
walkDir(full, out);
|
|
2318
|
+
} else if (stat.isFile() && FIGMA_CONNECT_FILE_GLOB.test(full)) {
|
|
2319
|
+
out.push(full);
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
function extractNodeIdsFromSource(source) {
|
|
2324
|
+
const nodeIds = /* @__PURE__ */ new Set();
|
|
2325
|
+
const re = new RegExp(NODE_ID_QUERY_RE, "g");
|
|
2326
|
+
let match;
|
|
2327
|
+
while ((match = re.exec(source)) !== null) {
|
|
2328
|
+
const raw = match[1];
|
|
2329
|
+
if (!raw) continue;
|
|
2330
|
+
const decoded = safeDecode(raw);
|
|
2331
|
+
nodeIds.add(decoded.replace(/-/g, ":"));
|
|
2332
|
+
}
|
|
2333
|
+
return nodeIds;
|
|
2334
|
+
}
|
|
2335
|
+
function safeDecode(raw) {
|
|
2336
|
+
try {
|
|
2337
|
+
return decodeURIComponent(raw);
|
|
2338
|
+
} catch {
|
|
2339
|
+
return raw;
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
// src/core/rules/component/code-connect-coverage.ts
|
|
2344
|
+
function computeCodeConnectCoverage(components, cwd = process.cwd()) {
|
|
2345
|
+
const result = parseCodeConnectMappings(cwd);
|
|
2346
|
+
if (result.skipReason === "no-config") return void 0;
|
|
2347
|
+
const componentNodeIds = Object.keys(components);
|
|
2348
|
+
let mapped = 0;
|
|
2349
|
+
for (const nodeId of componentNodeIds) {
|
|
2350
|
+
if (result.mappedNodeIds.has(nodeId)) mapped++;
|
|
2351
|
+
}
|
|
2352
|
+
return { mapped, total: componentNodeIds.length };
|
|
2353
|
+
}
|
|
2139
2354
|
z.object({
|
|
2140
2355
|
ruleId: z.string(),
|
|
2141
2356
|
detection: z.literal("rule-based"),
|
|
@@ -4866,11 +5081,19 @@ defineRule({
|
|
|
4866
5081
|
check: variantStructureMismatchCheck
|
|
4867
5082
|
});
|
|
4868
5083
|
var CODE_CONNECT_SETUP_KEY = "unmapped-component:setup-detected";
|
|
5084
|
+
var CODE_CONNECT_MAPPINGS_KEY = "unmapped-component:mappings";
|
|
4869
5085
|
function codeConnectIsSetUp(context) {
|
|
4870
5086
|
return getAnalysisState(context, CODE_CONNECT_SETUP_KEY, () => {
|
|
4871
5087
|
return existsSync(join(process.cwd(), "figma.config.json"));
|
|
4872
5088
|
});
|
|
4873
5089
|
}
|
|
5090
|
+
function codeConnectMappings(context) {
|
|
5091
|
+
return getAnalysisState(
|
|
5092
|
+
context,
|
|
5093
|
+
CODE_CONNECT_MAPPINGS_KEY,
|
|
5094
|
+
() => parseCodeConnectMappings(process.cwd())
|
|
5095
|
+
);
|
|
5096
|
+
}
|
|
4874
5097
|
var unmappedComponentDef = {
|
|
4875
5098
|
id: "unmapped-component",
|
|
4876
5099
|
name: "Unmapped Component",
|
|
@@ -4883,6 +5106,12 @@ var unmappedComponentCheck = (node, context) => {
|
|
|
4883
5106
|
if (node.type !== "COMPONENT" && node.type !== "COMPONENT_SET") return null;
|
|
4884
5107
|
if (isInsideInstance(context)) return null;
|
|
4885
5108
|
if (!codeConnectIsSetUp(context)) return null;
|
|
5109
|
+
const mappings = codeConnectMappings(context);
|
|
5110
|
+
if (mappings.mappedNodeIds.has(node.id)) return null;
|
|
5111
|
+
const ack = context.findAcknowledgment(node.id, unmappedComponentDef.id);
|
|
5112
|
+
if (ack && isRuleOptOutIntent(ack.intent) && ack.intent.ruleId === unmappedComponentDef.id) {
|
|
5113
|
+
return null;
|
|
5114
|
+
}
|
|
4886
5115
|
return {
|
|
4887
5116
|
ruleId: unmappedComponentDef.id,
|
|
4888
5117
|
nodeId: node.id,
|
|
@@ -5321,10 +5550,10 @@ Provide a Figma URL or fixture path via the input parameter. Requires FIGMA_TOKE
|
|
|
5321
5550
|
const ts = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}-${String(now.getHours()).padStart(2, "0")}-${String(now.getMinutes()).padStart(2, "0")}`;
|
|
5322
5551
|
ensureReportsDir();
|
|
5323
5552
|
const reportPath = `${getReportsDir()}/report-${ts}-${file.fileKey}.html`;
|
|
5324
|
-
await new Promise((
|
|
5553
|
+
await new Promise((resolve7, reject) => {
|
|
5325
5554
|
writeFile(reportPath, html, "utf-8", (err) => {
|
|
5326
5555
|
if (err) reject(err);
|
|
5327
|
-
else
|
|
5556
|
+
else resolve7();
|
|
5328
5557
|
});
|
|
5329
5558
|
});
|
|
5330
5559
|
if (openReport === true) {
|
|
@@ -5338,11 +5567,19 @@ Provide a Figma URL or fixture path via the input parameter. Requires FIGMA_TOKE
|
|
|
5338
5567
|
percentage: scores.overall.percentage,
|
|
5339
5568
|
source: isJsonFile(input) || isFixtureDir(input) ? "fixture" : "figma"
|
|
5340
5569
|
});
|
|
5570
|
+
const coverage = computeCodeConnectCoverage(file.components);
|
|
5571
|
+
const optOutHintEligible = acknowledgments === void 0;
|
|
5341
5572
|
return {
|
|
5342
5573
|
content: [
|
|
5343
5574
|
{
|
|
5344
5575
|
type: "text",
|
|
5345
|
-
text: JSON.stringify(buildResultJson(file.name, result, scores, {
|
|
5576
|
+
text: JSON.stringify(buildResultJson(file.name, result, scores, {
|
|
5577
|
+
fileKey: file.fileKey,
|
|
5578
|
+
designKey: computeDesignKey(input),
|
|
5579
|
+
...effectiveMinGrade ? { codegenReadyMinGrade: effectiveMinGrade } : {},
|
|
5580
|
+
...coverage ? { codeConnectCoverage: coverage } : {},
|
|
5581
|
+
roundtripOptOutHintEligible: optOutHintEligible
|
|
5582
|
+
}), null, 2)
|
|
5346
5583
|
}
|
|
5347
5584
|
]
|
|
5348
5585
|
};
|
|
@@ -5673,16 +5910,16 @@ ${inlineTopics[selectedTopic]}` }]
|
|
|
5673
5910
|
};
|
|
5674
5911
|
}
|
|
5675
5912
|
const { readFile: readFile3 } = await import('fs/promises');
|
|
5676
|
-
const { resolve:
|
|
5913
|
+
const { resolve: resolve7, dirname: dirname4 } = await import('path');
|
|
5677
5914
|
const { fileURLToPath } = await import('url');
|
|
5678
5915
|
try {
|
|
5679
5916
|
const __dirname = dirname4(fileURLToPath(import.meta.url));
|
|
5680
|
-
const docPath =
|
|
5917
|
+
const docPath = resolve7(__dirname, "../../docs/CUSTOMIZATION.md");
|
|
5681
5918
|
let content;
|
|
5682
5919
|
try {
|
|
5683
5920
|
content = await readFile3(docPath, "utf-8");
|
|
5684
5921
|
} catch {
|
|
5685
|
-
const altPath =
|
|
5922
|
+
const altPath = resolve7(__dirname, "../docs/CUSTOMIZATION.md");
|
|
5686
5923
|
content = await readFile3(altPath, "utf-8");
|
|
5687
5924
|
}
|
|
5688
5925
|
if (selectedTopic !== "all") {
|