angular-guide and spring-boot-guide v1.1.0

Released v1.1.0 of both angular-guide and spring-boot-guide on the same day. They share a hook architecture, so most of this release is the same set of changes landing in both: a rewritten lint engine, two fixes for features that never worked in 1.0, a security fix, and ways to turn rules off without editing the plugin.

The September posts each ended with a list of what was headed for v1.1. I went back through both lists against the released code, and most of those items did not make it. That part is at the end.

Two Features That Never Worked in 1.0

Both September posts made a point of the SubagentStart hook. Skills are advisory and a Task-dispatched subagent inherits none of them, so each plugin injected a condensed rule set into every subagent’s context. That was a central part of the pitch for both plugins.

It did not reach any subagent. The hook wrote plain stdout, and Claude Code sends plain SubagentStart stdout to its debug log, not into the subagent’s context. Every subagent in both showcase builds started without the rule summary. The PreToolUse hooks still ran underneath them, so the blocking rules held, but the context the posts described was never there. In 1.1.0 the hook returns JSON hookSpecificOutput.additionalContext, which is the form that actually gets injected.

It also got narrower. The injection now only happens inside a matching workspace, and only includes rules the workspace’s profile enables. A Boot 3 subagent is no longer told about the Boot 4 rules, and an Angular v20 subagent is no longer told OnPush is the default.

The second one is skill scoping. The angular-guide skills used file_patterns and auto_suggest in their frontmatter, which are not Claude Code skill fields and were silently ignored. The spring-boot-guide skills had no scoping at all, and descriptions like “ALL database work” and “ALL test files” did not mention Spring, so they were offered in every project. Both now use paths, and every skill description names its framework.

Neither of these showed up in a test suite because both suites tested what the scripts printed, not what Claude Code did with it.

Hooks No Longer Run Your Repository’s Python

Every hook in both plugins now runs Python in isolated mode (-I). Before, a repository containing a file named json.py, re.py, tempfile.py, or datetime.py would have that file executed by every hook, SessionStart included, because Python puts the script’s directory and the current directory on the import path. Opening a cloned repository was enough.

I checked this against the release by planting a json.py that writes a marker file into a throwaway Angular workspace, then running detection and a lint pass. The marker never appeared.

Blocking Only What a Change Adds

This is the change I expect to notice most day to day. In 1.0, a blocking rule ran against the whole post-edit file, so an unrelated one-line edit to a file that already contained an old violation was blocked. That made the plugins hard to use on any existing codebase.

Now each blocking rule runs on the file before and after the change and only blocks if the count went up. Fixing a typo in a component that already has an [innerHTML] binding goes through. Adding a second [innerHTML] to that same file is still blocked, with a note saying how many were already there. I ran both cases against the released angular-guide hook and got exit 0 and exit 2 respectively.

Advisories work the same way across a session. In 1.0 every advisory was repeated on every edit to the file. Now a file’s advisory only comes back when a change adds another instance.

Turning Rules Off

Before 1.1.0, the only way to disable a rule was to edit the installed plugin’s scripts. There are now three ways:

  • A disabled_rules option under /plugin configure, per user.
  • An environment variable a team can commit in .claude/settings.json: ANGULAR_GUIDE_DISABLED_RULES or SPRING_BOOT_GUIDE_DISABLED_RULES. These cover the Bash rules as well.
  • Inline ignore comments: angular-guide-ignore-next-line, -line, and -file (and the spring-boot-guide- equivalents). Each one names the rule IDs and requires a -- reason. They only count inside a real comment, so a string that happens to contain the text does not switch anything off.

Disabled rules are also left out of the subagent injection, so a subagent is not told to follow a rule the project turned off.

The Profile Moved Out of Your Repository

Both plugins used to write a dotfile to the repository root (.angular-guide-project.json, .spring-boot-guide-project.json) that you then had to gitignore. The profile now lives under $CLAUDE_PLUGIN_DATA, or $XDG_CACHE_HOME/<plugin> as a fallback.

Detection is also lazy now. Hooks detect a workspace on first use and re-detect when an input changes: package.json, angular.json, tsconfig.json, or a lockfile for Angular, and a build file, the version catalog, the wrapper, or a main application* config file for Spring. Running ng update or bumping the Boot version mid-session takes effect on the next write instead of the next session. A Spring module resolves to its aggregator’s profile, and a monorepo session started at the repo root now reaches the subagent and Bash hooks too.

spring-boot-guide also stopped treating any Java build file as a Spring project. A Quarkus, Micronaut, or plain Java build that never mentions Spring Boot no longer gets the ungated rules.

The Bash Guard Parses Commands

Both Bash guards used to match against raw command text. The angular-guide post covered what that did: BG004 blocked a git commit message that described the ng test flag, and BG001 blocked a shell heredoc because the paragraph inside it described a force push. The spring-boot-guide post found a related problem: neither guard checked what kind of project it was in, so both inspected every Bash command, and angular-guide’s BG004 blocked a command issued while working on the Spring backend.

In 1.1.0 each guard only runs inside its own kind of workspace, and it parses the command instead of grepping it. Quoted text no longer triggers a rule, and a flag only counts for the command it belongs to. I checked a commit message mentioning ng test and an echo of a force-push command against the released angular-guide guard, and both went through, while the actual force push to main was still blocked.

Parsing also made the guards catch more. Both now see through bash -c "...", combined flags like -fu, and +main refspecs. angular-guide catches npx ng ..., git push -f origin HEAD, removing a lockfile without -r, and npm test when the test script runs ng test in watch mode. spring-boot-guide catches rm mvnw without -r, spring-boot:test-run, bootTestRun, :module:bootRun, and a destructive ddl-auto passed as a --spring.jpa.hibernate.ddl-auto= argument or the SPRING_JPA_HIBERNATE_DDL_AUTO environment variable.

How much the old approach costs became obvious while I was researching this post. A different plugin I have installed still uses a text-matching Bash guard, and it blocked my own test script twice because the script contained a force-push command inside a quoted string. I had to write the probe to a file and run that instead.

One Engine, One Registry

The Write/Edit rules in both plugins moved out of hook-lint.sh, which was bash calling a separate Python process per rule or per pass, into one Python module, scripts/lint.py. The Bash rules moved into scripts/guard.py. The angular-guide changelog puts a pre-write check at about 30 ms, down from about 200 ms.

Every rule’s wording and gate now lives in one file, scripts/rules.py. Hook messages, the subagent rule set, the README rule tables, and the CLAUDE.md.template rule list are all generated from it by tools/gen_docs.py, and the test suite fails if the generated docs are stale. In 1.0 those were maintained separately, which is how a rule’s README description could drift from what the rule actually did.

spring-boot-guide’s mutation gate came along to the new engine. For each rule it copies scripts/, replaces that rule’s registered matcher with a no-op, and asserts the rule’s own fixtures fail. Same defense as before, working on Python functions instead of a shell sentinel.

Both plugins also got a tools/release.py that sets the version in VERSION, both manifests, and the changelog in one step.

Rule Fixes

angular-guide:

  • NG010 no longer misses a teardown that comes before an arrow-function operator in the same pipe(), and no longer credits an outer call’s take( to an inner .subscribe().
  • NG012 no longer flags two-argument .set(k, v) calls inside an effect, which are Map, HttpParams, URLSearchParams, or FormData, not signals. Its message also stopped suggesting untracked() as a general escape hatch.
  • NG016 judges the value, not just the key name. storageKey: "app-theme", hotkey, a Firebase apiKey (AIza...), and Stripe publishable keys no longer block.
  • NG103 now matches @Injectable({ ... }) split across lines.
  • NG104 skips <img src="data:...">, which NgOptimizedImage cannot handle.
  • NG105 no longer flags this.document from inject(DOCUMENT), which is the fix the rule itself recommends.
  • The skills used resource()’s pre-v20 request option and now use params.

spring-boot-guide:

  • SB013 fires when * is one entry of a list, like include: health,*.
  • SB018 catches import static org.junit.Assert.*.
  • SB007 sees past an inline YAML comment.
  • SB018 and SB113 now report one message each instead of one per finding.

What Did Not Make It

Here is each item the September posts said was headed for v1.1, checked against the released code. Where I could, I ran the actual 1.1.0 hook against the case that produced the finding.

angular-guide

Quote-aware Bash guard: shipped. Covered above.

BG004 on ng test --help: not fixed. ng test --help and ng test --version are still blocked as if they would hang the session. The fix text still recommends ng test --run for Vitest, which the September post noted the v22 builder does not accept.

Test files quoting rule triggers as data: not fixed. I wrote a .spec.ts file whose string literals contain *ngIf, an [innerHTML] template, and bypassSecurityTrustHtml, which is the shape of a test fixture for the showcase’s Rules page. NG003, NG014, and NG015 all blocked it. Delta blocking softens this for edits to an existing spec file, but a new one is still blocked.

testing-essentials coverage for @defer and httpResource(): not shipped. The skill’s only change was the frontmatter fix. It still says nothing about TestBed.tick(), deferBlockBehavior, or getDeferBlocks(). The September post called this the one worth fixing first, because that gap caused more real defects in the showcase than every hook firing combined.

spring-boot-guide

A PostToolUse hook on Bash: not shipped. hooks.json still only lints Write and Edit. A file written through a heredoc, sed, or cp is still never checked, so the advice from September stands: use Write and Edit for any file a rule can apply to. This post was written with the Write tool for that reason.

SB106 reading a package line as a field declaration: not fixed. I ran the released hook on a @Service in package com.example.github with a field declared private final GithubClient github. SB106 still told me to make it final. The matcher looks for the first line outside a method body where the name is followed by ; or =, and package com.example.github; is that line.

SB110 detecting @Entity semantically: not fixed. A controller taking a same-package @Entity as a @RequestBody produced nothing. The same controller importing a type from a .entity. package fired. It is still the package-name heuristic, which the rule’s message says.

The P1 items: not shipped. The config rules still only look at files named application*, so db-secrets.yml still gets no coverage. SB111’s message still reads as general N+1 coverage rather than naming jpa-review as the backstop. The README still lists “The plugin has never been run against a real Spring Boot codebase” as a known limitation, even though the showcase backend now exists.

Why the Release Went This Way

Looking at the two lists side by side, 1.1.0 fixed the problems that affect every user of the plugins: subagent injection, skill scoping, the import-path hole, blocking on old code, no off switch, a dotfile in every repo, and guards that fired on quoted text in unrelated projects. The September findings were mostly about specific rules misfiring on specific shapes of code. Those are smaller and they are still real.

Several of them are also easier now than they would have been on 1.0. SB106 is one function in lint.py instead of a regex inside a shell script, BG004’s fix text is one string in rules.py, and the generated docs and mutation gate will catch a change that breaks the docs or turns a rule into a no-op.

Test Suites

angular-guide’s suite is at 244 checks, up from 130 in 1.0, and all pass on the released code. spring-boot-guide’s suite passes all 320 checks, and its mutation gate tested all 39 rules and every mutation was killed, meaning each rule’s own fixtures fail when its matcher is replaced with a no-op.

Upgrading

For either plugin:

  1. Update through /plugin, then start a new session.
  2. Delete the old .angular-guide-project.json or .spring-boot-guide-project.json from your workspaces and drop it from .gitignore.
  3. If you edited the installed plugin’s hooks.json or scripts to turn a rule off, undo that and use disabled_rules or the environment variable instead. The README’s Tuning section has the details.

Try It

GitHub: angular-guide | spring-boot-guide

Changelogs: angular-guide | spring-boot-guide

Earlier posts: angular-guide v1.0.0 | spring-boot-guide v1.0.0