canicode 0.12.2 → 0.12.3
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 +109 -36
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +86 -34
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +90 -35
- package/dist/mcp/server.js.map +1 -1
- package/package.json +1 -1
- package/skills/canicode-roundtrip/SKILL.md +86 -0
- package/skills/canicode-roundtrip/helpers-bootstrap.js +1 -1
- package/skills/canicode-roundtrip/helpers-installer.js +2 -2
- package/skills/canicode-roundtrip/helpers.js +429 -0
- package/skills/cursor/canicode-roundtrip/SKILL.md +86 -0
- 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 +429 -0
package/dist/cli/index.js
CHANGED
|
@@ -1408,7 +1408,24 @@ var EVENTS = {
|
|
|
1408
1408
|
// has a single place to wire it up.
|
|
1409
1409
|
ROUNDTRIP_DEFINITION_WRITE_SKIPPED: `${EVENT_PREFIX}roundtrip_definition_write_skipped`,
|
|
1410
1410
|
/** CLI `canicode roundtrip-tally` completed successfully. */
|
|
1411
|
-
ROUNDTRIP_TALLY: `${EVENT_PREFIX}roundtrip_tally
|
|
1411
|
+
ROUNDTRIP_TALLY: `${EVENT_PREFIX}roundtrip_tally`,
|
|
1412
|
+
/**
|
|
1413
|
+
* Phase 3 (#508 / ADR-023): `applyComponentize` outcome — either a
|
|
1414
|
+
* successful `createComponentFromNode` call or one of the guard rejections
|
|
1415
|
+
* (instance-child, free-form parent, error) that route to the Strategy C
|
|
1416
|
+
* annotate-fallback. Surfaces under `props.outcome` so a Node-side reader
|
|
1417
|
+
* can split the funnel.
|
|
1418
|
+
*/
|
|
1419
|
+
ROUNDTRIP_COMPONENTIZE: `${EVENT_PREFIX}roundtrip_componentize`,
|
|
1420
|
+
/**
|
|
1421
|
+
* Phase 3 (#508 / ADR-023, #554): `applyReplaceWithInstance` outcome —
|
|
1422
|
+
* either a successful instance swap (`outcome: "replaced"`) or one of the
|
|
1423
|
+
* guard rejections (`"skipped-free-form-parent"`, `"skipped-prereq-missing"`,
|
|
1424
|
+
* `"error"`). The primitive pairs with `ROUNDTRIP_COMPONENTIZE` so a
|
|
1425
|
+
* Node-side reader can correlate componentize+swap pairs across a single
|
|
1426
|
+
* Phase 3 batch.
|
|
1427
|
+
*/
|
|
1428
|
+
ROUNDTRIP_REPLACE_WITH_INSTANCE: `${EVENT_PREFIX}roundtrip_replace_with_instance`
|
|
1412
1429
|
};
|
|
1413
1430
|
|
|
1414
1431
|
// src/core/monitoring/capture.ts
|
|
@@ -2515,8 +2532,8 @@ var missingComponentMsg = {
|
|
|
2515
2532
|
message: `"${name}" appears ${count} times`,
|
|
2516
2533
|
suggestion: `Extract as a reusable component`
|
|
2517
2534
|
}),
|
|
2518
|
-
structureRepetition: (name,
|
|
2519
|
-
message: `"${name}" and ${
|
|
2535
|
+
structureRepetition: (name, matchCount) => ({
|
|
2536
|
+
message: `"${name}" and ${matchCount} other frame(s) share the same internal structure`,
|
|
2520
2537
|
suggestion: `Extract a shared component from the repeated structure`
|
|
2521
2538
|
}),
|
|
2522
2539
|
styleOverride: (componentName, overrides) => ({
|
|
@@ -3297,6 +3314,41 @@ function getSeenStage1(context) {
|
|
|
3297
3314
|
function getSeenStage4(context) {
|
|
3298
3315
|
return getAnalysisState(context, SEEN_STAGE4_KEY, () => /* @__PURE__ */ new Set());
|
|
3299
3316
|
}
|
|
3317
|
+
function stage3GroupsKey(maxDepth) {
|
|
3318
|
+
return `missing-component:stage3Groups:depth=${maxDepth}`;
|
|
3319
|
+
}
|
|
3320
|
+
function nodeQualifiesForStage3(node, parent, insideInstance) {
|
|
3321
|
+
if (insideInstance) return false;
|
|
3322
|
+
if (node.type !== "FRAME") return false;
|
|
3323
|
+
if (parent?.type === "COMPONENT_SET") return false;
|
|
3324
|
+
if (!node.children || node.children.length === 0) return false;
|
|
3325
|
+
return true;
|
|
3326
|
+
}
|
|
3327
|
+
function buildStage3Groups(root, maxFingerprintDepth) {
|
|
3328
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3329
|
+
const walk = (node, parent, ancestorIsInstance) => {
|
|
3330
|
+
const insideInstance = ancestorIsInstance || node.type === "INSTANCE";
|
|
3331
|
+
if (nodeQualifiesForStage3(node, parent, ancestorIsInstance)) {
|
|
3332
|
+
const fp = buildFingerprint(node, maxFingerprintDepth);
|
|
3333
|
+
const existing = groups.get(fp);
|
|
3334
|
+
if (existing) existing.memberIds.push(node.id);
|
|
3335
|
+
else groups.set(fp, { memberIds: [node.id] });
|
|
3336
|
+
}
|
|
3337
|
+
if (node.children) {
|
|
3338
|
+
for (const child of node.children) walk(child, node, insideInstance);
|
|
3339
|
+
}
|
|
3340
|
+
};
|
|
3341
|
+
walk(root, null, false);
|
|
3342
|
+
return groups;
|
|
3343
|
+
}
|
|
3344
|
+
function getStage3Groups(context, maxFingerprintDepth) {
|
|
3345
|
+
const root = context.analysisRoot ?? context.file.document;
|
|
3346
|
+
return getAnalysisState(
|
|
3347
|
+
context,
|
|
3348
|
+
stage3GroupsKey(maxFingerprintDepth),
|
|
3349
|
+
() => buildStage3Groups(root, maxFingerprintDepth)
|
|
3350
|
+
);
|
|
3351
|
+
}
|
|
3300
3352
|
var missingComponentDef = {
|
|
3301
3353
|
id: "missing-component",
|
|
3302
3354
|
name: "Missing Component",
|
|
@@ -3311,7 +3363,8 @@ var missingComponentCheck = (node, context, options) => {
|
|
|
3311
3363
|
const matchingComponent = Object.values(components).find(
|
|
3312
3364
|
(c) => c.name.toLowerCase() === node.name.toLowerCase()
|
|
3313
3365
|
);
|
|
3314
|
-
const
|
|
3366
|
+
const scopeRoot = context.analysisRoot ?? context.file.document;
|
|
3367
|
+
const frameNames = collectFrameNames(scopeRoot);
|
|
3315
3368
|
const sameNameFrames = frameNames.get(node.name);
|
|
3316
3369
|
const firstFrame = sameNameFrames?.[0];
|
|
3317
3370
|
if (matchingComponent) {
|
|
@@ -3339,37 +3392,32 @@ var missingComponentCheck = (node, context, options) => {
|
|
|
3339
3392
|
};
|
|
3340
3393
|
}
|
|
3341
3394
|
}
|
|
3342
|
-
if (isInsideInstance(context))
|
|
3343
|
-
|
|
3344
|
-
|
|
3395
|
+
if (!nodeQualifiesForStage3(node, context.parent ?? null, isInsideInstance(context))) {
|
|
3396
|
+
return null;
|
|
3397
|
+
}
|
|
3345
3398
|
const structureMinRepetitions = options?.["structureMinRepetitions"] ?? getRuleOption("missing-component", "structureMinRepetitions", 2);
|
|
3346
3399
|
const maxFingerprintDepth = options?.["maxFingerprintDepth"] ?? getRuleOption("missing-component", "maxFingerprintDepth", 3);
|
|
3400
|
+
const groups = getStage3Groups(context, maxFingerprintDepth);
|
|
3347
3401
|
const fingerprint = buildFingerprint(node, maxFingerprintDepth);
|
|
3348
|
-
const
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
);
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
)
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
nodePath: context.path.join(" > "),
|
|
3368
|
-
...missingComponentMsg.structureRepetition(node.name, count - 1)
|
|
3369
|
-
};
|
|
3370
|
-
}
|
|
3371
|
-
}
|
|
3372
|
-
return null;
|
|
3402
|
+
const group = groups.get(fingerprint);
|
|
3403
|
+
if (!group) return null;
|
|
3404
|
+
if (group.memberIds.length < structureMinRepetitions) return null;
|
|
3405
|
+
if (group.memberIds[0] !== node.id) return null;
|
|
3406
|
+
return {
|
|
3407
|
+
ruleId: missingComponentDef.id,
|
|
3408
|
+
subType: "structure-repetition",
|
|
3409
|
+
nodeId: node.id,
|
|
3410
|
+
nodePath: context.path.join(" > "),
|
|
3411
|
+
...missingComponentMsg.structureRepetition(
|
|
3412
|
+
node.name,
|
|
3413
|
+
group.memberIds.length - 1
|
|
3414
|
+
),
|
|
3415
|
+
// #560 / delta 4a: surface the full group so the apply step can drive
|
|
3416
|
+
// the componentize+swap loop from a single user answer. `nodeId` is
|
|
3417
|
+
// the document-order first member; the rest are siblings or cross-
|
|
3418
|
+
// parent matches found by the scope-wide pass (#557).
|
|
3419
|
+
groupMembers: [...group.memberIds]
|
|
3420
|
+
};
|
|
3373
3421
|
}
|
|
3374
3422
|
if (node.type === "INSTANCE" && node.componentId) {
|
|
3375
3423
|
const seenStage4 = getSeenStage4(context);
|
|
@@ -3983,6 +4031,7 @@ var RuleEngine = class {
|
|
|
3983
4031
|
analysisState,
|
|
3984
4032
|
scope,
|
|
3985
4033
|
rootNodeType,
|
|
4034
|
+
rootNode,
|
|
3986
4035
|
void 0,
|
|
3987
4036
|
void 0
|
|
3988
4037
|
);
|
|
@@ -4019,7 +4068,7 @@ var RuleEngine = class {
|
|
|
4019
4068
|
/**
|
|
4020
4069
|
* Recursively traverse the tree and run rules
|
|
4021
4070
|
*/
|
|
4022
|
-
traverseAndCheck(node, file, rules, maxDepth, issues, failedRules, depth, path, ancestorTypes, componentDepth, analysisState, scope, rootNodeType, parent, siblings) {
|
|
4071
|
+
traverseAndCheck(node, file, rules, maxDepth, issues, failedRules, depth, path, ancestorTypes, componentDepth, analysisState, scope, rootNodeType, analysisRoot, parent, siblings) {
|
|
4023
4072
|
const nodePath = [...path, node.name];
|
|
4024
4073
|
const isComponentBoundary = node.type === "COMPONENT" || node.type === "COMPONENT_SET" || node.type === "INSTANCE";
|
|
4025
4074
|
const currentComponentDepth = isComponentBoundary ? 0 : componentDepth;
|
|
@@ -4042,6 +4091,7 @@ var RuleEngine = class {
|
|
|
4042
4091
|
analysisState,
|
|
4043
4092
|
scope,
|
|
4044
4093
|
rootNodeType,
|
|
4094
|
+
analysisRoot,
|
|
4045
4095
|
findAcknowledgment: (nodeId, ruleId) => acknowledgmentsByKey.get(`${normalizeNodeId(nodeId)}::${ruleId}`)
|
|
4046
4096
|
};
|
|
4047
4097
|
for (const rule of rules) {
|
|
@@ -4091,6 +4141,7 @@ var RuleEngine = class {
|
|
|
4091
4141
|
analysisState,
|
|
4092
4142
|
scope,
|
|
4093
4143
|
rootNodeType,
|
|
4144
|
+
analysisRoot,
|
|
4094
4145
|
node,
|
|
4095
4146
|
node.children
|
|
4096
4147
|
);
|
|
@@ -4526,7 +4577,7 @@ function computeApplyContext(violation, instanceContext) {
|
|
|
4526
4577
|
}
|
|
4527
4578
|
|
|
4528
4579
|
// package.json
|
|
4529
|
-
var version2 = "0.12.
|
|
4580
|
+
var version2 = "0.12.3";
|
|
4530
4581
|
|
|
4531
4582
|
// src/core/engine/scoring.ts
|
|
4532
4583
|
var GRADE_ORDER = ["S", "A+", "A", "B+", "B", "C+", "C", "D", "F"];
|
|
@@ -6552,7 +6603,11 @@ function mapToQuestion(issue, file) {
|
|
|
6552
6603
|
...applyContext.annotationProperties !== void 0 ? { annotationProperties: applyContext.annotationProperties } : {},
|
|
6553
6604
|
...suggestedName !== void 0 ? { suggestedName } : {},
|
|
6554
6605
|
isInstanceChild: applyContext.isInstanceChild,
|
|
6555
|
-
...applyContext.sourceChildId !== void 0 ? { sourceChildId: applyContext.sourceChildId } : {}
|
|
6606
|
+
...applyContext.sourceChildId !== void 0 ? { sourceChildId: applyContext.sourceChildId } : {},
|
|
6607
|
+
// #560 / Phase 3 delta 4a: thread groupMembers through from the
|
|
6608
|
+
// violation. Currently only populated by `missing-component`
|
|
6609
|
+
// Stage 3; non-group rules pass undefined and the field is omitted.
|
|
6610
|
+
...issue.violation.groupMembers !== void 0 ? { groupMembers: issue.violation.groupMembers } : {}
|
|
6556
6611
|
};
|
|
6557
6612
|
}
|
|
6558
6613
|
function deduplicateBySourceComponent(questions) {
|
|
@@ -6788,7 +6843,25 @@ var GotchaSurveyQuestionSchema = z.object({
|
|
|
6788
6843
|
// `[nodeId, ...replicaNodeIds]` so the same answer lands on every replica.
|
|
6789
6844
|
// Single-instance questions omit both fields.
|
|
6790
6845
|
replicas: z.number().int().min(2).optional(),
|
|
6791
|
-
replicaNodeIds: z.array(z.string()).optional()
|
|
6846
|
+
replicaNodeIds: z.array(z.string()).optional(),
|
|
6847
|
+
/**
|
|
6848
|
+
* Phase 3 (#508 / #560 / delta 4a): every node id in the rule's emitted
|
|
6849
|
+
* group, including `nodeId`. Currently only populated for
|
|
6850
|
+
* `missing-component:structure-repetition` (Stage 3) — one question per
|
|
6851
|
+
* fingerprint group, the apply step (delta 4b) iterates this list to
|
|
6852
|
+
* componentize the first member and swap the rest.
|
|
6853
|
+
*
|
|
6854
|
+
* Distinct from `replicas` / `replicaNodeIds`: those collapse N
|
|
6855
|
+
* instance-child questions that pre-existed as separate violations into
|
|
6856
|
+
* one. `groupMembers` carries N original group members from a single
|
|
6857
|
+
* group-shaped violation that never became N separate issues. The apply
|
|
6858
|
+
* step distinguishes by which field is set.
|
|
6859
|
+
*/
|
|
6860
|
+
// `.min(2)` locks the contract: a group-shaped emit always carries at
|
|
6861
|
+
// least 2 members (Stage 3 short-circuits below `structureMinRepetitions`
|
|
6862
|
+
// = 2). A future rule emitting `[]` or `[oneId]` is a programming error,
|
|
6863
|
+
// not a runtime case to handle gracefully.
|
|
6864
|
+
groupMembers: z.array(z.string()).min(2).optional()
|
|
6792
6865
|
});
|
|
6793
6866
|
var SurveyQuestionBatchSchema = z.object({
|
|
6794
6867
|
ruleId: z.string(),
|