I was typing this into my reply to Zeev, but realized that on one hand it is of interest mainly to readers with an experimental frame of mind, and on the other that it got far, far ahead of where Zeev wanted to go immediately. So I've separated it out into a "to whom it may concern" message.
One way to use "error rules" and "diagnostic rules" is to have several grammars, and at the top level something like
Top ::= Error-start rank => 1 | Normal-start
A "normal" grammar derives from <Normal-start>. This is the grammar you'd ordinarily write.
An "error" grammar derives from <Error-start>. It contains "error rules" -- rules that, when they match, you do not want to see a parse. The rank controls what you see -- if there's an error, the parse with error rules is all you see -- it overrides a good parse. If no error rules match, then you see only the "normal" parse, if there is one.
You could also throw in a 3rd "diagnostic grammar", which could be full of rules which look for bad practices. It would be a "catch-all", which matches even defective input. It's "semantics" would be a list of diagnostic messages. You'd get the result of the diagnostic grammar only when no error rules match, but you don't get a good parse either.
I have not tested the above -- it's off the top of my head. And it's more on a idea level anyway -- there are a lot of details between this suggestion and an implementation.