Skip to content

feat(cli): add structlint suggest — propose config diffs and file moves#19

Merged
machado144 merged 2 commits into
mainfrom
feat/suggest-command
Jul 10, 2026
Merged

feat(cli): add structlint suggest — propose config diffs and file moves#19
machado144 merged 2 commits into
mainfrom
feat/suggest-command

Conversation

@machado144

Copy link
Copy Markdown
Contributor

Summary

  • New `structlint suggest` command. Runs the same engine as `validate`, then maps each violation to a concrete proposal: config addition (unified diff), file move (`git mv`), file create, or a review note.
  • Print-only, exits 0 even with proposals present — this is advisory; violation gating stays `validate`'s job.
  • Tenth PR in the roadmap (Wave 3 — Suggest). Spec: `docs/specs/010-suggest.md`.

Design highlights

  • Never loosens `disallowed` — deliberate prohibitions become a note ("the rule is deliberate; review it or remove the path"), never a config_add. Test-asserted.
  • JSON contract versioned from day one (`version: 1`) so editors and AI agents can build against a stable shape. Documented in `docs/user/cli-reference.md`.
  • configDiff is text-level, not YAML re-marshal: insertion into the original file text preserves comments, ordering, quoting — the diff applies cleanly with `patch -p1`. Round-trip test asserts this end-to-end.
  • Proposal deduping: many files sharing an extension collapse into one `config_add` proposal. Sorted deterministically.

The fix loop

```bash
structlint suggest --format json > /tmp/report.json
jq -r .configDiff /tmp/report.json | patch -p1
jq -r '.proposals[] | select(.kind=="move") | .command' /tmp/report.json | sh
structlint validate
```

Test plan

  • `go test -race ./...` — 153 pass (7 new binary-based):
    • JSON contract v1 shape
    • per-code proposals (config_add / move / note)
    • disallowed_* NEVER loosened (no proposal touches disallowed section)
    • round-trip: pipe configDiff through `patch(1)`, re-run validate, exit 0
    • exit 0 with proposals; exit non-zero on operational error
    • move emits `git mv `
  • `make build && ./bin/structlint validate` — self-dogfood, exit 0.
  • Manual: scratch tree with stale config → `suggest` output includes correct additions, git mv command for placement violation, and the note-only entry for `.env.local`.

Runs the same engine as validate, then maps each violation to a concrete
proposal: config addition (rendered as a unified diff), file move (as a
git mv command), file create, or a review note. Print-only, exit 0 even
with proposals — this is advisory; violation gating is validate's job.

Spec: docs/specs/010-suggest.md.

Files:
- internal/suggest/proposals.go — Proposal + Report types; Kind enum;
  JSON tags for the v1 contract consumed by editors and agents.
- internal/suggest/suggest.go — Analyze(cfg, configPath, root) runs the
  validator (silent) and turns violations into proposals via a per-code
  mapping table. disallowed_* codes NEVER produce a loosening proposal
  — they get a note that says "the rule is deliberate".
- internal/suggest/configdiff.go — line-level insertion into the original
  config text (never re-marshal, preserves comments/ordering/quoting)
  plus a small unified-diff renderer since we only ever INSERT lines.
- internal/cli/suggest.go — flag parsing, text/json rendering.

Uses spec 009's infer generalizer indirectly (via reused validator.Tree
paths) for glob generalization.

JSON v1 contract (versioned from day one):
{version:1, configPath, proposals:[{kind, section, value | from/to/command
| path, reason, paths}], configDiff}

Tests (test/suggest_test.go, binary-based):
- JSON contract version 1
- per-code proposals: config_add for unallowed_*, move for placement,
  note for disallowed_*
- disallowed never loosened (no proposal touches disallowed section)
- round-trip: pipe configDiff through patch(1), re-run validate, exit 0
  (the primary property that agents/editors depend on)
- exit 0 with proposals present
- exit non-zero on operational error (no config)
- move emits git mv <from> <to>

Docs: cli-reference.md documents the command, JSON contract, and the
suggest → patch → git mv → validate fix loop.
@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown

StructLint — All checks passed

129 rules validated against .structlint.yaml. No violations found.

View full run · Powered by StructLint

@machado144 machado144 merged commit b2cee58 into main Jul 10, 2026
5 checks passed
@machado144 machado144 deleted the feat/suggest-command branch July 10, 2026 07:36
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.

1 participant