Skip to content

feat(lint): broken-link check + lint before push; drop scan-stage tool#75

Open
sVIKs wants to merge 1 commit into
corezoid:mainfrom
sVIKs:feat/scan-stage-go
Open

feat(lint): broken-link check + lint before push; drop scan-stage tool#75
sVIKs wants to merge 1 commit into
corezoid:mainfrom
sVIKs:feat/scan-stage-go

Conversation

@sVIKs

@sVIKs sVIKs commented Jul 13, 2026

Copy link
Copy Markdown

What

Reworked per maintainer feedback: pre-deploy validation should be lint, run right before push — not a separate stage-scan tool. So this PR no longer adds a scan-stage tool; instead it folds the one salvageable check into lint-process and makes push-process lint automatically.

  • lint-process gains broken node-link detection — a logic to_node_id/err_node_id/go_to/goto, or a semaphor to_node_id/esc_node_id, pointing at a static 24-hex node id that is absent from the process. The server rejects such a deploy ("referenced node does not exist"); now caught offline, before the round-trip. Dynamic {{...}}/@alias targets resolve at deploy and are left alone.
  • push-process runs lint before deploying and blocks on issues that would break the deploy or its callers: broken links, old-format nodes, RPC paths without reply, nodes missing a default go, sub-30s timers, literal reply values. Advisory findings (noop, unused set_param, orphans, passthrough, shared clusters) are shown but do not block. force=true overrides.
  • The standalone stage-export scanner is removed — the corezoid-stage-scan skill and its scan_stage.py are gone. Its per-process check moved into lint (above); the cross-process conv_id/api_get_task reference checks are left to the server, which already rejects bad references on deploy — no point duplicating them offline (they'd need to fetch the whole stage anyway).

Net: 172 insertions / 400 deletions — the change removes more than it adds.

Verified

  • go build, go vet, go test -race ./... green; TestToolRegistryMatchesREADME green.
  • New unit tests for the broken-link check (dangling logic/semaphor targets flagged; valid and dynamic targets not).
  • End-to-end via the CLI binary: a process with a dangling err_node_id reports === BROKEN NODE LINKS (1) === with a precise message.
  • Skill removal is reference-clean: README, POWER.md (skill count + capability), the main skill's list, install-kiro.sh, and public/ discovery artifacts all updated; no dangling references remain.

🤖 Generated with Claude Code

@gh-corezoid

Copy link
Copy Markdown
Contributor

AI Review

Ports the corezoid-stage-scan skill's Python validator (scan_stage.py) into a new scan-stage MCP tool implemented in Go inside convctl, preserving all five defect classes and report semantics while adding zip-slip protection and duplicate-obj_id resilience; the Python script is deleted and the checks now run as ordinary Go tests.

Checklist

Check Result
U1 — Conventional commit format ✅ pass
U2 — No leaked credentials ✅ pass
U3 — No merge commits ✅ pass
U4 — PR targets correct base branch (main) ✅ pass
U5 — Build & tests (Go) ✅ pass
U6 — Architectural & design consequences ⚠️ warning (see below)
C1 — Manifest files version-synced ⬜ skip (no manifest files changed)
C2 — Node IDs in .conv.json valid ⬜ skip (no .conv.json files changed)
C3 — No hardcoded env values in .conv.json ⬜ skip (no .conv.json files changed)
C4 — extra fields have extra_type ⬜ skip (no .conv.json files changed)
C5 — README updated for new skills ✅ pass

U5 detail: go build ./..., go vet ./..., and go test ./... -count=1 all exit 0 on the PR branch. The same test suite exits 0 on the base commit (2107719) — no regressions introduced.

Issues found

[warning] U6 — CLI exit-code contract changed for existing CI consumers

The Python scan_stage.py exited with 1 when any blocker was found and 0 when clean — a standard CI gate. The Go server CLI exit code now reflects tool-level errors only (bad path, unreadable ZIP), never the scan verdict. The SKILL.md has been updated to document the grep 'No blockers found.' workaround, but any pipeline that currently runs:

python3 .../scan_stage.py stage.zip --quiet

and gates on the exit code will silently break after this change: it will always exit 0 regardless of findings, because the CI step will likely be updated to call the Go CLI without adding the grep gate. Concrete failure scenario: a broken stage is merged because the old exit 1 path no longer fires and the pipeline appears green.

Suggested fix: Add a one-line migration note near the top of SKILL.md (e.g. "⚠ If you previously gated CI on the Python exit code, replace with | grep -q 'No blockers found.' — see the CI recipe below"). Alternatively, the CLI wrapper could re-add a non-zero exit when the report contains BLOCKERS FOUND (grep of its own output), but that is a separate decision.


This review was generated automatically. A human maintainer should still make the merge decision.

@salimovartem

Copy link
Copy Markdown
Collaborator

Stepping back from the code — I'm not convinced this tool belongs in the plugin right now.

The plugin has no whole-stage operation: push-process pushes one .conv.json at a time, and there is no stage-merge or stage-deploy tool here. All five checks in scan-stage are aimed at exactly that missing workflow ("pre-merge / pre-deploy validator" per SKILL.md:4). In the actual dev cycle we have — edit a process, push it — the platform itself will return the same "Only active process can be used" / broken-link errors at push time. scan-stage predicts errors for a merge that this plugin cannot perform.

Its real audience is support/integration work (auditing exported stage zips) and Corezoid-UI stage merges — both orthogonal to what the plugin does today.

Two ways to make the work land without carrying dead weight:

  1. Move the per-file checks — [1b] empty process and [2a] intra-process broken node links (to_node_id / err_node_id / go_to) — into lint-process. They need no export and no cross-file view; devs would catch them on every push-process for free. plugins/corezoid/mcp-server/lint.go:105 is the natural home.
  2. Keep scan-stage out of the plugin until there is a whole-stage operation to anchor it (stage export/merge/deploy). At that point the remaining [1] / [2b] / [2c] checks fit naturally as its pre-flight; today they have nothing to gate.

Suggest deferring the merge and doing (1) as a smaller PR against lint-process.

Reworked per maintainer feedback: validation should be lint, run right
before push — not a separate stage-scan tool.

- lint-process gains broken node-link detection: a logic
  to_node_id/err_node_id/go_to/goto or a semaphor to_node_id/esc_node_id
  pointing at a static 24-hex node id absent from the process. The
  server rejects such a deploy ("referenced node does not exist");
  now caught offline. Dynamic {{...}}/@alias targets resolve at deploy
  and are left alone. (Salvaged from the former stage-scan tool.)
- push-process runs lint before deploying and blocks on issues that
  would break the deploy or its callers (broken links, old-format,
  RPC-without-reply, missing default go, sub-30s timers, literal reply
  values); advisory findings are shown but do not block; force=true
  overrides.
- Standalone stage-export scanner removed: the corezoid-stage-scan skill
  and scan_stage.py are gone. Its per-process check moved into lint;
  cross-process conv_id/api_get_task validation is left to the server,
  which already rejects bad references on deploy, rather than
  duplicated offline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@sVIKs sVIKs force-pushed the feat/scan-stage-go branch from d7c63a5 to f67ab50 Compare July 15, 2026 14:47
@sVIKs sVIKs changed the title feat(mcp): scan-stage tool — Go port of the stage-scan validator feat(lint): broken-link check + lint before push; drop scan-stage tool Jul 15, 2026
@sVIKs

sVIKs commented Jul 15, 2026

Copy link
Copy Markdown
Author

Reworked this PR per the feedback that validation belongs in lint-before-push, not a separate tool.

Pivot: dropped the scan-stage tool entirely (and the corezoid-stage-scan skill + scan_stage.py). Instead:

  • the salvageable per-process check (broken node links) is now part of lint-process;
  • push-process runs lint-process before deploying and blocks on deploy-breaking findings (force=true overrides);
  • the cross-process conv_id/api_get_task checks are not reimplemented offline — the server already rejects bad references on deploy, and pre-checking them would mean fetching the whole stage to duplicate the server's own validation.

Force-pushed; title and description updated. Gates green (build/vet/test -race, registry↔README).

@gh-corezoid

Copy link
Copy Markdown
Contributor

AI Review

Re-review after synchronize: reworks the PR from a scan-stage MCP tool into two orthogonal improvements — broken-link detection added to lint-process and a pre-push lint gate wired into push-process; the Python corezoid-stage-scan skill and its scan_stage.py are removed entirely.

Checklist

Check Result
U1 — Conventional commit format ✅ pass
U2 — No leaked credentials ✅ pass
U3 — No merge commits ✅ pass
U4 — PR targets correct base branch (main) ✅ pass
U5 — Build & tests (Go) ✅ pass
U6 — Architectural & design consequences ⚠️ warning (see below)
C1 — Manifest files version-synced ⬜ skip (no manifest files changed)
C2 — Node IDs in .conv.json valid ⬜ skip (no .conv.json files changed)
C3 — No hardcoded env values in .conv.json ⬜ skip
C4 — extra fields have extra_type ⬜ skip
C5 — README updated for new skills ✅ pass

U5 detail: go build ./... exits 0 on the PR branch (commit f67ab50). go test ./... -count=1 exits 0 on the PR branch (ok convctl 45.2s). Same suite exits 0 on base main (ok convctl 50.7s) — no regressions introduced. New TestBrokenLinks_* tests all pass.

Fixed since last review ✅

  • Previous U6 (CLI exit-code contract) — the previous review flagged that scan_stage.py exited 1 on blockers while the Go path did not, breaking CI pipelines that gated on that exit code. scan_stage.py and the entire corezoid-stage-scan skill are now deleted; the concern no longer applies.

Issues found

[warning] U6 — Advisory lint findings are silently discarded by push-process when no hard findings exist

The tool description in tools_registry.go says:

"advisory findings are shown but do not block"

The PR body also says: "advisory findings are shown but do not block."

But the handlePushProcess implementation in mcp_handlers_process.go only returns lint output in the early-return blocking path:

if hard > 0 && !force {
    return "Push blocked: ...\n\n" + FormatLintResult(lintRes), true  // shown + blocked
}
if hard > 0 && force {
    fmt.Fprintf(os.Stderr, "[lint] %d blocking issue(s) overridden\n", hard)
    // FormatLintResult still not returned to the caller
}
// hard == 0 → falls through silently; advisory-only findings are never shown

When a process has only advisory findings and no hard findings, the push succeeds with zero lint output. The user or AI agent never sees the advisory issues.

Concrete failure scenario: an agent pushes a process that has 3 noop conditions and an orphaned node. All go silently into production. Later a standalone lint-process run surfaces issues the agent never saw — the "shown but do not block" promise in the tool description was never fulfilled during the push.

Suggested fix (two options):

  1. Surface advisory findings in the push result when the push succeeds — append them as a trailing note rather than blocking.

  2. If silent behavior is intentional, update the tool description to drop "are shown" — a one-line fix in tools_registry.go that brings the spec in line with the implementation.


This review was generated automatically. A human maintainer should still make the merge decision.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants