1use super::json_record::quote;
16
17pub const SELF_HINTED: &[(&str, &str)] = &[
19 ("require-root", "not-at-root"),
20 ("linked-worktree-only", "linked-worktree-required"),
21 ("branch-worktree-only", "branch-creation-requires-worktree"),
22];
23
24pub fn finding_hint(policy: &str, code: &str, fix_available: bool) -> Option<&'static str> {
35 match (policy, code, fix_available) {
36 ("add-explicit", "bulk-add-rejected", _) => {
37 return Some(
38 "Hint: if you meant to stage everything you changed, run `git status --short` to list it and name \
39 each path, as in `git add -- <path> <path>`; if you meant only files git already tracks, name those \
40 paths the same way.",
41 );
42 }
43 ("final-newline", "noncanonical-final-newline", true) => {
44 return Some(
45 "Hint: if you are committing or checking this file, a commit corrects the ending for you; \
46 to correct it now without committing, run `git cli-git fix -- <path>`.",
47 );
48 }
49 ("final-newline", "noncanonical-final-newline", false) => {
50 return Some(
51 "Hint: if you are committing or checking this file, edit it so it ends with exactly one newline, \
52 then stage it again.",
53 );
54 }
55 ("markdown/autofix", "markdown-autofix", _) => {
56 return Some(
57 "Hint: if you are committing this Markdown, the commit applies the linter's corrections itself; \
58 to apply them now, run `git cli-git fix -- <path>`.",
59 );
60 }
61 ("markdown/autofix", "markdown-violation", _) => {
62 return Some(
63 "Hint: if you are committing this Markdown, the linter cannot repair this rule by itself; \
64 edit the file at the reported line and column, stage it again, and retry.",
65 );
66 }
67 ("mono/forbidden-root-context", "root-context-forbidden", _) => {
68 return Some(
69 "Hint: if you meant to keep project notes or terms for later readers, put them in a file under doc/; \
70 to drop CONTEXT.md from this commit and keep the file on disk, run `git rm --cached CONTEXT.md`.",
71 );
72 }
73 ("mono/dependent-version-bump", "dependent-version-stale", _) => {
74 return Some(
75 "Hint: if you changed a package that others depend on, raise its dependents' version ranges to match \
76 the new version in the same commit, then stage those files; `git cli-git fix -- <path>` applies the \
77 correction when one is available.",
78 );
79 }
80 ("mono/dependent-version-bump", "dependent-version-unsupported", _) => {
81 return Some(
82 "Hint: if you edited a dependency declaration of a shape this check does not read, update the \
83 dependents by hand in the same commit; the message names the file.",
84 );
85 }
86 ("mono/catalog-specifiers", "disallowed-specifier", _) => {
87 return Some(
88 "Hint: if you are adding or changing a dependency, move its version into the catalog section of \
89 pnpm-workspace.yaml and write catalog: here, or write workspace:* for a package of this workspace; \
90 peerDependencies take * or catalog:.",
91 );
92 }
93 ("mono/catalog-specifiers", "non-string-specifier", _) => {
94 return Some(
95 "Hint: if you are writing a dependency version, make it a string such as catalog: or workspace:*; \
96 a number, list, object or null is not a specifier.",
97 );
98 }
99 ("mono/catalog-specifiers", "malformed-section", _) => {
100 return Some(
101 "Hint: if you are editing a dependency section, write it as an object that maps package names to \
102 specifiers; the message names the section.",
103 );
104 }
105 ("mono/catalog-specifiers", "duplicate-key", _) => {
106 return Some(
107 "Hint: if you are editing this manifest by hand, keep one copy of the repeated section or \
108 dependency name; pnpm keeps the last copy, which may not be the one you meant.",
109 );
110 }
111 ("mono/catalog-specifiers", "invalid-manifest", _) => {
112 return Some(
113 "Hint: if you are committing this package.json, make it a JSON object that parses and is UTF-8 \
114 text (a stray character, an empty file or a link instead of a file shows here), then stage it again.",
115 );
116 }
117 ("mono/catalog-specifiers", "unsupported-manifest-format", _) => {
118 return Some(
119 "Hint: if you are adding a package.json5, package.yaml or package.yml, use package.json instead, \
120 because the catalog rule reads only that format; rename or convert the file and stage it again.",
121 );
122 }
123 ("security/forbidden-strings", "forbidden-string", _) => {
124 return Some(
125 "Hint: if you are committing text or a path that contains a secret or a banned term, remove or \
126 replace it where the message points and stage the file again; if you believe the rule is wrong, \
127 review it in the rules file named by the configuration instead of splitting the commit.",
128 );
129 }
130 _ => return None,
131 }
132}
133
134pub fn engine_failure_hint(code: &str) -> Option<&'static str> {
145 match code {
146 "config-invalid" => {
147 return Some(
148 "Hint: if you just edited cli-git.config.jsonc, fix the key the message names or revert the edit; \
149 an unknown key, a wrong type or an unknown policy id fails before any policy runs.",
150 );
151 }
152 "core-incomplete" => {
153 return Some(
154 "Hint: if another session was changing the repository at the same moment (a commit, a checkout or a \
155 rebase running elsewhere), run the command again; if it repeats, run `git status` to check that the \
156 repository is healthy.",
157 );
158 }
159 "content-unavailable" => {
160 return Some(
161 "Hint: if you are committing or pushing content that is not in this clone (a missing object, a \
162 shallow history, or a file removed during the command), fetch the history with `git fetch`, make \
163 sure the files exist, and retry.",
164 );
165 }
166 "policy-incomplete" => {
167 return Some(
168 "Hint: if you are committing or pushing, a policy could not finish reading the content, so it \
169 decided nothing; fix the cause the message names (an unreadable file, a path with a line break) \
170 and retry.",
171 );
172 }
173 "patch-invalid" | "patch-conflict" => {
174 return Some(
175 "Hint: if you are committing files that changed while the command ran or that another policy also \
176 rewrites, save your edits, run `git cli-git fix -- <path>` to apply the corrections, review the \
177 result with `git diff`, and commit again.",
178 );
179 }
180 "fix-cycle" | "fix-pass-limit" => {
181 return Some(
182 "Hint: two corrections keep undoing each other on this file; apply one of them by hand, stage the \
183 file, and commit again. Report the pair of policies if the file is ordinary text.",
184 );
185 }
186 "transaction-failed" => {
187 return Some(
188 "Hint: if you were committing, run `git status` and `git log --oneline -3` to see whether the \
189 commit landed before you retry; staged files stay staged unless the message says otherwise, and the \
190 message names the step that failed.",
191 );
192 }
193 "legacy-config-ignored" => {
194 return Some(
195 "Hint: if you are still keeping the old configuration files, delete them once \
196 cli-git.config.jsonc holds the settings; they are no longer read.",
197 );
198 }
199 "traditional-hooks-unsupported" => {
200 return Some(
201 "Hint: scripts in the hooks directory never run through cli-git. If one still checks something for you, \
202 express it as a policy under policies in cli-git.config.jsonc; `git cli-git doctor` lists the \
203 scripts it finds and exits 1 while any remain.",
204 );
205 }
206 _ => return None,
207 }
208}
209
210pub const WARN_UNSAFE_HINT: &str = "Hint: if you set it to warn to keep commits flowing, note that this policy \
221 changes or misses content when it only warns; set its severity to error to enforce it, or to off to disable it, \
222 in cli-git.config.jsonc.";
223
224pub const POST_COMMIT_BLOCKED_HINT: &str = "Hint: if you wanted this commit backed up, fix what the gate reported \
226 and run `git push` yourself; the commit stays in your local history until then.";
227
228pub fn push_hint_field(line: &mut String, hint: Option<&str>) {
240 if let Some(text) = hint {
241 line.push_str(",\"hint\":");
242 line.push_str(quote(text).as_str());
243 }
244}
245
246#[cfg(test)]
248#[path = "event_hints_tests.rs"]
249mod tests;