Skip to content

Commit c0efa10

Browse files
committed
fix: address remaining Cursor review findings
1 parent e6fcfd0 commit c0efa10

8 files changed

Lines changed: 55 additions & 25 deletions

File tree

.github/workflows/mcp-release-watch.yml

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,27 +54,30 @@ jobs:
5454
latest_release=""
5555
fi
5656
57+
# Ignore prerelease/suffixed tags such as 2026-07-28-RC. Only final
58+
# date tags can identify a published specification revision.
59+
if [[ -n "$latest_release" && ! "$latest_release" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
60+
echo "Ignoring non-final MCP release/tag: $latest_release"
61+
latest_release=""
62+
fi
63+
5764
if [[ -z "$latest_release" ]]; then
58-
# No releases, check tags instead
59-
if ! latest_release=$(gh api "repos/$MCP_REPO/tags" --jq '.[0].name // empty' 2>/dev/null); then
65+
# No final release, select the newest final date tag instead of
66+
# blindly accepting an RC/final-suffixed tag at index zero.
67+
if ! latest_release=$(gh api "repos/$MCP_REPO/tags" --jq '[.[].name | select(test("^[0-9]{4}-[0-9]{2}-[0-9]{2}$"))][0] // empty' 2>/dev/null); then
6068
echo "ERROR: Failed to query MCP releases and tags"
6169
exit 1
6270
fi
6371
fi
6472
6573
if [[ -z "$latest_release" ]]; then
66-
echo "ERROR: MCP repository returned no releases or tags"
67-
exit 1
74+
echo "No final date-based MCP release found; ignoring prerelease tags"
75+
echo "NEW_RELEASE=false" >> "$GITHUB_OUTPUT"
76+
exit 0
6877
fi
6978
7079
echo "Latest release/tag: $latest_release"
7180
72-
# MCP uses date-based versioning like "2025-11-25"
73-
if [[ ! "$latest_release" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
74-
echo "ERROR: Unsupported MCP release format: $latest_release"
75-
exit 1
76-
fi
77-
7881
if [[ "$latest_release" > "$baseline_version" ]]; then
7982
echo "NEW_RELEASE=true" >> "$GITHUB_OUTPUT"
8083
echo "NEW_VERSION=$latest_release" >> "$GITHUB_OUTPUT"

crates/agnix-core/src/file_types/detection.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,8 @@ pub fn detect_file_type(path: &Path) -> FileType {
424424
FileType::ClaudeOutputStyle
425425
}
426426
// Cursor project rules (.cursor/rules/**/*.md and .mdc)
427-
name if (ends_with_ignore_ascii_case(name, ".md") || name.ends_with(".mdc"))
427+
name if (ends_with_ignore_ascii_case(name, ".md")
428+
|| ends_with_ignore_ascii_case(name, ".mdc"))
428429
&& is_under_cursor_rules(path) =>
429430
{
430431
FileType::CursorRule
@@ -930,7 +931,7 @@ mod tests {
930931
detect_file_type(Path::new(".cursor/rules/custom.MD")),
931932
FileType::CursorRule
932933
);
933-
assert_ne!(
934+
assert_eq!(
934935
detect_file_type(Path::new(".cursor/rules/custom.MDC")),
935936
FileType::CursorRule
936937
);

crates/agnix-core/src/rules/cursor.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -828,10 +828,7 @@ fn validate_cursor_agent_file(
828828

829829
if let Some(model_value) = frontmatter_map.get(key("model")) {
830830
match model_value {
831-
YamlValue::String(model)
832-
if model == "fast"
833-
|| model == "inherit"
834-
|| is_valid_cursor_model_id(model) => {}
831+
YamlValue::String(model) if is_valid_cursor_model_id(model) => {}
835832
YamlValue::String(_) => diagnostics.push(
836833
Diagnostic::error(
837834
path.to_path_buf(),
@@ -969,6 +966,8 @@ fn validate_cursor_environment_file(
969966
}
970967
};
971968

969+
// Cursor's VS Code JSON service treats root `$schema` as the schema-association
970+
// key even though the published schema's closed property set does not declare it.
972971
const ALLOWED_ROOT_FIELDS: &[&str] = &[
973972
"$schema",
974973
"name",
@@ -997,7 +996,7 @@ fn validate_cursor_environment_file(
997996
}
998997
}
999998

1000-
for field in ["name", "user", "snapshot"] {
999+
for field in ["$schema", "name", "user", "snapshot"] {
10011000
if root.get(field).is_some_and(|value| !value.is_string()) {
10021001
diagnostics.push(
10031002
cursor_environment_error(
@@ -2493,6 +2492,7 @@ is_background: false
24932492

24942493
for (name, content) in [
24952494
("unknown root field", r#"{"unknown":true}"#),
2495+
("invalid schema association", r#"{"$schema":42}"#),
24962496
("invalid name", r#"{"name":1}"#),
24972497
("invalid user", r#"{"user":false}"#),
24982498
(
@@ -2807,11 +2807,16 @@ Review the diff and suggest improvements."#;
28072807

28082808
#[test]
28092809
fn test_cur_020_mdc_rule_is_recognized() {
2810-
let diagnostics = validate_cursor_rule(
2810+
for path in [
28112811
".cursor/rules/typescript.mdc",
2812-
"---\ndescription: TypeScript rules\n---\nUse strict mode.",
2813-
);
2814-
assert!(diagnostics.iter().all(|d| d.rule != "CUR-020"));
2812+
".cursor/rules/typescript.MDC",
2813+
] {
2814+
let diagnostics = validate_cursor_rule(
2815+
path,
2816+
"---\ndescription: TypeScript rules\n---\nUse strict mode.",
2817+
);
2818+
assert!(diagnostics.iter().all(|d| d.rule != "CUR-020"));
2819+
}
28152820
}
28162821

28172822
#[test]

crates/agnix-rules/rules.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7586,7 +7586,7 @@
75867586
"fix": {
75877587
"autofix": false
75887588
},
7589-
"good_example": "{\"build\":{\"dockerfile\":\"Dockerfile\",\"context\":\"..\"},\"install\":\"npm ci\",\"start\":\"npm run dev\",\"terminals\":[{\"name\":\"app\",\"command\":\"npm run dev\"}]}",
7589+
"good_example": "{\"$schema\":\"https://cursor.com/schemas/environment.schema.json\",\"build\":{\"dockerfile\":\"Dockerfile\",\"context\":\"..\"},\"install\":\"npm ci\",\"start\":\"npm run dev\",\"terminals\":[{\"name\":\"app\",\"command\":\"npm run dev\"}]}",
75907590
"bad_example": "{\"install\":42,\"terminals\":[{\"name\":\"app\"}]}"
75917591
},
75927592
{

knowledge-base/VALIDATION-RULES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1817,7 +1817,7 @@ Output-style files (`.claude/output-styles/*.md` or `~/.claude/output-styles/*.m
18171817

18181818
<a id="cur-016"></a>
18191819
### CUR-016 [HIGH] Invalid .cursor/environment.json Schema
1820-
**Requirement**: `.cursor/environment.json` MUST match the published schema at cursor.com/schemas/environment.schema.json. Comments are allowed, trailing commas are not, no root field is required, `build.dockerfile` is required when `build` exists, and unknown root/build fields are rejected. Terminal entries require only `command`; `name` and `description` are optional. `update` is not in the schema and is reported as renamed to `install`.
1820+
**Requirement**: `.cursor/environment.json` MUST match the published schema at cursor.com/schemas/environment.schema.json. Comments are allowed, trailing commas are not, no root field is required, `build.dockerfile` is required when `build` exists, and unknown root/build fields are rejected except for the conventional root `$schema` association key. Terminal entries require only `command`; `name` and `description` are optional. `update` is not in the schema and is reported as renamed to `install`.
18211821
**Detection**: Strip JSON comments, parse JSON, enforce the root/build closed-field sets, and validate setup strings, repository dependencies, ports, build fields, snapshot fields, and terminal entries
18221822
**Fix**: Correct field types; rename `update` to `install`
18231823
**Source**: cursor.com/docs/cloud-agent/setup

knowledge-base/rules.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7586,7 +7586,7 @@
75867586
"fix": {
75877587
"autofix": false
75887588
},
7589-
"good_example": "{\"build\":{\"dockerfile\":\"Dockerfile\",\"context\":\"..\"},\"install\":\"npm ci\",\"start\":\"npm run dev\",\"terminals\":[{\"name\":\"app\",\"command\":\"npm run dev\"}]}",
7589+
"good_example": "{\"$schema\":\"https://cursor.com/schemas/environment.schema.json\",\"build\":{\"dockerfile\":\"Dockerfile\",\"context\":\"..\"},\"install\":\"npm ci\",\"start\":\"npm run dev\",\"terminals\":[{\"name\":\"app\",\"command\":\"npm run dev\"}]}",
75907590
"bad_example": "{\"install\":42,\"terminals\":[{\"name\":\"app\"}]}"
75917591
},
75927592
{

tests/ci_workflow.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,24 @@ fn ci_keeps_linux_only_quality_gates_on_ubuntu() {
4444
);
4545
}
4646
}
47+
48+
#[test]
49+
fn mcp_release_watch_filters_prerelease_tags() {
50+
let root = env!("CARGO_MANIFEST_DIR");
51+
let workflow = fs::read_to_string(format!("{root}/.github/workflows/mcp-release-watch.yml"))
52+
.expect("failed to read MCP release-watch workflow")
53+
.replace("\r\n", "\n");
54+
55+
assert!(
56+
workflow.contains(r#"select(test("^[0-9]{4}-[0-9]{2}-[0-9]{2}$"))"#),
57+
"MCP tag fallback must select only final date-based versions"
58+
);
59+
assert!(
60+
workflow.contains("Ignoring non-final MCP release/tag"),
61+
"MCP prerelease tags must be ignored instead of failing the workflow"
62+
);
63+
assert!(
64+
!workflow.contains("ERROR: Unsupported MCP release format"),
65+
"known MCP prerelease tag shapes must not hard-fail the workflow"
66+
);
67+
}

website/docs/rules/generated/cur-016.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,5 @@ The following examples demonstrate what triggers this rule and how to fix it.
4545
### Valid
4646

4747
```json
48-
{"build":{"dockerfile":"Dockerfile","context":".."},"install":"npm ci","start":"npm run dev","terminals":[{"name":"app","command":"npm run dev"}]}
48+
{"$schema":"https://cursor.com/schemas/environment.schema.json","build":{"dockerfile":"Dockerfile","context":".."},"install":"npm ci","start":"npm run dev","terminals":[{"name":"app","command":"npm run dev"}]}
4949
```

0 commit comments

Comments
 (0)