Skip to content

feat(manifest-analyzer): repo-walker onboarding discovery (F8 first cut)#215

Merged
sunib merged 2 commits into
mainfrom
feat/gitops-api-f8
Jul 9, 2026
Merged

feat(manifest-analyzer): repo-walker onboarding discovery (F8 first cut)#215
sunib merged 2 commits into
mainfrom
feat/gitops-api-f8

Conversation

@sunib

@sunib sunib commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

First cut of F8 repo-discovery / onboarding scan: WalkRepo(ctx, root) in
internal/manifestanalyzer plus a thin manifest-analyzer --mode repo-walker <repo>
CLI (--format text|json).

It walks a whole repo, enumerates candidate GitTarget subtrees, classifies each
folder's layout (plain | kustomize-single | kustomize-overlay |
refused-structural), runs the operator's acceptance gate per candidate, and emits a
text/JSON report with a resources { rendered, editable, nonKrm } split that makes the
F2 overlay gap legible.

Reports, does not propose — no CR/WatchRule generation, no --mode discovery
rename, --policy refuse deferred (see the doc's non-goals).

Design + rationale: docs/design/gitops-api/f8-repo-discovery-and-onboarding-scan.md.

Validation: task lint + task test green (coverage 75.2%); task test-e2e 55/55.

🤖 Generated with Claude Code

Add WalkRepo(ctx, root) in internal/manifestanalyzer plus a thin CLI mode
`--mode repo-walker <root>` (--format text|json). It walks a whole repo,
enumerates candidate GitTargets (render roots + plain KRM leaf folders),
classifies each folder's layout (plain | kustomize-single | kustomize-overlay
| refused-structural), runs the operator's own acceptance gate per candidate,
mirrors the GitTarget overlap check, and emits a text/JSON report.

Reports, does not propose: no CR/WatchRule generation, no `--mode discovery`
rename, and the repo-level `--policy refuse` gate is deferred (exit codes stay
simple). Per candidate: path, layout, acceptedByOperator, refusalReasons,
renderRoot, readScope, inferredNamespace, resources{rendered,editable,nonKrm},
overlapsWith; plus a repo summary (counts by layout, accepted/refused,
overlapConflicts, fleetRoot, unsupportedConstructs). The rendered-vs-editable
split makes the F2 overlay gap legible: an overlay renders base documents it
cannot yet edit (rendered=2, editable=0). The load-bearing
overlay-fan-out-needs-f2 (forward-looking) vs refused-structural (permanent
wall) distinction is preserved.

Golden corpus under testdata/repo-walker/{supported,unsupported}/<fixture>/
with sibling <fixture>.golden.json (UPDATE_GOLDEN=1 regenerates), including an
overlay-nested-base regression pinning the deduped rendered count and minimal
readScope (a nested out-of-subtree base must not double-count).

store.go: extract unsupportedKustomizeFeatureKeys() shared by the acceptance
gate and the walker's per-feature refusal detail (behavior-preserving).

docs(gitops-api): sync the F8 design doc to what shipped, add the
"discovery != support" containment thesis and the ladder-vs-wall framing,
reconcile refused-fleet-root/refused-out-of-band as repo-level vs candidate
layouts, and fold the open decisions into the open questions; add a
cross-reference from kustomize-support-boundary-and-product-model.md.

Validation: task lint and task test green (coverage within ratchet);
task test-e2e passed 55/55 (unaffected by the CLI-library/docs-only changes
made after that run).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gitguardian

gitguardian Bot commented Jul 9, 2026

Copy link
Copy Markdown

️✅ There are no secrets present in this pull request anymore.

If these secrets were true positive and are still valid, we highly recommend you to revoke them.
While these secrets were previously flagged, we no longer have a reference to the
specific commits where they were detected. Once a secret has been leaked into a git
repository, you should consider it compromised, even if it was deleted immediately.
Find here more information about risks.


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new "repo-walker" whole-repository discovery mode: a WalkRepo library function classifying candidate folders by kustomize layout, computing acceptance/refusal, resource counts, overlaps, and a repo summary; wires it into the manifest-analyzer CLI; adds golden fixtures/tests; refactors a store.go helper; updates design docs.

Changes

F8 repo-walker feature

Layer / File(s) Summary
Core report model and WalkRepo driver
internal/manifestanalyzer/repowalk.go
Defines RepoReport, RepoCandidate, RepoSummary, RefusalReason, ResourceCounts, OverlapConflict types and implements WalkRepo to scan a root and assemble candidates.
Render-root classification and plain candidates
internal/manifestanalyzer/repowalk.go
Classifies kustomize render roots into overlay/single/refused-structural and enumerates plain KRM leaf folders with acceptance gating.
Overlay read-scope and graph traversal
internal/manifestanalyzer/repowalk.go
Computes out-of-subtree base directories for overlays and traverses the kustomization resources graph to find reachable dirs/files.
Namespace inference, counting, overlaps, summary
internal/manifestanalyzer/repowalk.go
Infers candidate namespaces, counts rendered/editable/non-KRM resources, detects overlapping candidates, and aggregates repo-level summary/fleet-root detection.
Report rendering and store helper refactor
internal/manifestanalyzer/repowalk.go, internal/manifestanalyzer/store.go
Adds RenderRepoJSON/RenderRepoText output renderers and extracts unsupported-kustomize-feature keys into a shared helper.
CLI wiring for repo-walker mode
cmd/manifest-analyzer/main.go, cmd/manifest-analyzer/main_test.go
Adds repo-walker to --mode choices/help text, refactors validation/dispatch via validChoices/runDirMode, adds runRepoWalk, and adds CLI tests covering text/JSON output and error cases.
WalkRepo tests and golden fixtures
internal/manifestanalyzer/repowalk_test.go, internal/manifestanalyzer/testdata/repo-walker/...
Adds a golden-fixture test harness plus regression tests, and supporting testdata manifests/golden JSON for supported and unsupported layout scenarios.
Design doc updates
docs/design/gitops-api/f8-repo-discovery-and-onboarding-scan.md, docs/design/gitops-api/kustomize-support-boundary-and-product-model.md
Updates status, report contract, CLI surface, and open questions to describe the shipped repo-walker behavior and its relation to the write-contract allowlist.

Estimated code review effort: 4 (Complex) | ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the feature, but it omits the required template sections like Type of Change, Testing checklist, Related Issues, Screenshots, and Additional Notes. Restructure the PR text to match the template and add the missing sections, checkboxes, related issue reference, and any applicable screenshots/notes.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly names the new repo-walker F8 scan in manifest-analyzer and matches the main change.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/gitops-api-f8

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
internal/manifestanalyzer/repowalk.go (1)

345-372: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Optional: memoize fully-visited dirs to avoid re-walking shared bases.

The onPath set only guards cycles; a directory that has finished processing is removed from onPath, so a diamond-shaped resources graph (multiple roots reaching a shared base that itself reaches further bases) re-walks the shared subtree once per inbound edge. For deeply-shared graphs this is exponential. The result set stays correct, so this is purely a defensive perf hardening for pathological/large repos.

♻️ Add a done-set to short-circuit revisits
 func reachedKustomizationDirs(rootDir string, kusts map[string]*kustomizationDoc) map[string]struct{} {
 	reached := map[string]struct{}{}
 	onPath := map[string]struct{}{}
+	done := map[string]struct{}{}
 	var walk func(dir string)
 	walk = func(dir string) {
 		cur := kusts[dir]
 		if cur == nil {
 			return
 		}
 		if _, cycling := onPath[dir]; cycling {
 			return
 		}
+		if _, visited := done[dir]; visited {
+			return
+		}
 		onPath[dir] = struct{}{}
 		for _, entry := range cur.resources {
 			target := cleanJoin(dir, entry)
 			if target == "" {
 				continue
 			}
 			if _, isKust := kusts[target]; isKust {
 				reached[target] = struct{}{}
 				walk(target)
 			}
 		}
 		delete(onPath, dir)
+		done[dir] = struct{}{}
 	}
 	walk(rootDir)
 	return reached
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/manifestanalyzer/repowalk.go` around lines 345 - 372, The recursive
traversal in reachedKustomizationDirs only tracks the current path with onPath,
so shared subtrees can be re-walked repeatedly in diamond-shaped graphs. Add a
memoized done/visited set inside reachedKustomizationDirs to short-circuit
already fully processed dirs, and check it before recursing from walk so shared
bases are traversed once while preserving the existing cycle protection and
reached results.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@internal/manifestanalyzer/testdata/repo-walker/unsupported/unsupported-kustomize/app/kustomization.yaml`:
- Around line 6-9: The test fixture in the kustomize manifest contains a
hardcoded secret-looking literal that GitGuardian flags; update the
`secretGenerator` entry in the `kustomization.yaml` fixture to use a non-secret
placeholder while keeping the unsupported-feature path exercised. Prefer
changing the `literals` value under `secretGenerator` in this manifest to
something clearly fake like a placeholder token, or alternatively add a targeted
ignore for the `testdata/` fixtures if you want to keep the exact value.

---

Nitpick comments:
In `@internal/manifestanalyzer/repowalk.go`:
- Around line 345-372: The recursive traversal in reachedKustomizationDirs only
tracks the current path with onPath, so shared subtrees can be re-walked
repeatedly in diamond-shaped graphs. Add a memoized done/visited set inside
reachedKustomizationDirs to short-circuit already fully processed dirs, and
check it before recursing from walk so shared bases are traversed once while
preserving the existing cycle protection and reached results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4679fc32-edc1-4557-82e7-048caf1985aa

📥 Commits

Reviewing files that changed from the base of the PR and between 4469e2b and 7e502fa.

📒 Files selected for processing (46)
  • cmd/manifest-analyzer/main.go
  • cmd/manifest-analyzer/main_test.go
  • docs/design/gitops-api/f8-repo-discovery-and-onboarding-scan.md
  • docs/design/gitops-api/kustomize-support-boundary-and-product-model.md
  • internal/manifestanalyzer/repowalk.go
  • internal/manifestanalyzer/repowalk_test.go
  • internal/manifestanalyzer/store.go
  • internal/manifestanalyzer/testdata/repo-walker/supported/fleet-root.golden.json
  • internal/manifestanalyzer/testdata/repo-walker/supported/fleet-root/apps/podinfo/deployment.yaml
  • internal/manifestanalyzer/testdata/repo-walker/supported/fleet-root/clusters/prod/flux-system.yaml
  • internal/manifestanalyzer/testdata/repo-walker/supported/fleet-root/infra/ingress-nginx/deployment.yaml
  • internal/manifestanalyzer/testdata/repo-walker/supported/helm-release-document.golden.json
  • internal/manifestanalyzer/testdata/repo-walker/supported/helm-release-document/app/helmrelease.yaml
  • internal/manifestanalyzer/testdata/repo-walker/supported/kustomize-single.golden.json
  • internal/manifestanalyzer/testdata/repo-walker/supported/kustomize-single/app/deployment.yaml
  • internal/manifestanalyzer/testdata/repo-walker/supported/kustomize-single/app/kustomization.yaml
  • internal/manifestanalyzer/testdata/repo-walker/supported/kustomize-single/app/service.yaml
  • internal/manifestanalyzer/testdata/repo-walker/supported/no-krm.golden.json
  • internal/manifestanalyzer/testdata/repo-walker/supported/no-krm/docs/README.md
  • internal/manifestanalyzer/testdata/repo-walker/supported/no-krm/docs/notes.txt
  • internal/manifestanalyzer/testdata/repo-walker/supported/overlapping.golden.json
  • internal/manifestanalyzer/testdata/repo-walker/supported/overlapping/apps/web/deployment.yaml
  • internal/manifestanalyzer/testdata/repo-walker/supported/overlapping/apps/web/extra/configmap.yaml
  • internal/manifestanalyzer/testdata/repo-walker/supported/plain-per-env.golden.json
  • internal/manifestanalyzer/testdata/repo-walker/supported/plain-per-env/apps/app/prod/deployment.yaml
  • internal/manifestanalyzer/testdata/repo-walker/supported/plain-per-env/apps/app/prod/service.yaml
  • internal/manifestanalyzer/testdata/repo-walker/supported/plain-per-env/apps/app/test/deployment.yaml
  • internal/manifestanalyzer/testdata/repo-walker/supported/plain-per-env/apps/app/test/service.yaml
  • internal/manifestanalyzer/testdata/repo-walker/unsupported/base-overlays.golden.json
  • internal/manifestanalyzer/testdata/repo-walker/unsupported/base-overlays/base/deployment.yaml
  • internal/manifestanalyzer/testdata/repo-walker/unsupported/base-overlays/base/kustomization.yaml
  • internal/manifestanalyzer/testdata/repo-walker/unsupported/base-overlays/base/service.yaml
  • internal/manifestanalyzer/testdata/repo-walker/unsupported/base-overlays/overlays/acc/kustomization.yaml
  • internal/manifestanalyzer/testdata/repo-walker/unsupported/base-overlays/overlays/prod/kustomization.yaml
  • internal/manifestanalyzer/testdata/repo-walker/unsupported/base-overlays/overlays/test/kustomization.yaml
  • internal/manifestanalyzer/testdata/repo-walker/unsupported/helm-inflation.golden.json
  • internal/manifestanalyzer/testdata/repo-walker/unsupported/helm-inflation/app/kustomization.yaml
  • internal/manifestanalyzer/testdata/repo-walker/unsupported/overlay-nested-base.golden.json
  • internal/manifestanalyzer/testdata/repo-walker/unsupported/overlay-nested-base/base/common/configmap.yaml
  • internal/manifestanalyzer/testdata/repo-walker/unsupported/overlay-nested-base/base/common/kustomization.yaml
  • internal/manifestanalyzer/testdata/repo-walker/unsupported/overlay-nested-base/base/deployment.yaml
  • internal/manifestanalyzer/testdata/repo-walker/unsupported/overlay-nested-base/base/kustomization.yaml
  • internal/manifestanalyzer/testdata/repo-walker/unsupported/overlay-nested-base/overlays/test/kustomization.yaml
  • internal/manifestanalyzer/testdata/repo-walker/unsupported/unsupported-kustomize.golden.json
  • internal/manifestanalyzer/testdata/repo-walker/unsupported/unsupported-kustomize/app/deployment.yaml
  • internal/manifestanalyzer/testdata/repo-walker/unsupported/unsupported-kustomize/app/kustomization.yaml

Comment on lines +142 to +148
// TestWalkRepo_NotADirectory covers the usage error path: a missing or non-directory
// root is an error, not an empty report.
func TestWalkRepo_NotADirectory(t *testing.T) {
if _, err := WalkRepo(context.Background(), filepath.Join("testdata", "does-not-exist")); err == nil {
t.Fatal("expected an error for a missing root")
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test only covers the missing-path branch, not the file-as-root branch.

The test name TestWalkRepo_NotADirectory implies coverage of the !info.IsDir() error path in WalkRepo, but it only tests a non-existent path (the os.Stat error branch). The actual "not a directory" code path — passing a file as root — is not exercised.

🧪 Proposed fix: add a file-as-root subtest
 func TestWalkRepo_NotADirectory(t *testing.T) {
-	if _, err := WalkRepo(context.Background(), filepath.Join("testdata", "does-not-exist")); err == nil {
-		t.Fatal("expected an error for a missing root")
+	t.Run("missing", func(t *testing.T) {
+		if _, err := WalkRepo(context.Background(), filepath.Join("testdata", "does-not-exist")); err == nil {
+			t.Fatal("expected an error for a missing root")
+		}
+	})
+	t.Run("file", func(t *testing.T) {
+		// Use this test file itself as a non-directory root.
+		if _, err := WalkRepo(context.Background(), "repowalk_test.go"); err == nil {
+			t.Fatal("expected an error for a file root")
+		}
+	})
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// TestWalkRepo_NotADirectory covers the usage error path: a missing or non-directory
// root is an error, not an empty report.
func TestWalkRepo_NotADirectory(t *testing.T) {
if _, err := WalkRepo(context.Background(), filepath.Join("testdata", "does-not-exist")); err == nil {
t.Fatal("expected an error for a missing root")
}
}
// TestWalkRepo_NotADirectory covers the usage error path: a missing or non-directory
// root is an error, not an empty report.
func TestWalkRepo_NotADirectory(t *testing.T) {
t.Run("missing", func(t *testing.T) {
if _, err := WalkRepo(context.Background(), filepath.Join("testdata", "does-not-exist")); err == nil {
t.Fatal("expected an error for a missing root")
}
})
t.Run("file", func(t *testing.T) {
// Use this test file itself as a non-directory root.
if _, err := WalkRepo(context.Background(), "repowalk_test.go"); err == nil {
t.Fatal("expected an error for a file root")
}
})
}

Comment on lines +6 to +9
secretGenerator:
- name: creds
literals:
- password=s3cret

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

GitGuardian flagged a hardcoded secret in this test fixture.

The secretGenerator literal password=s3cret is intentionally fake test data used to trigger the unsupported-feature detection, but it has triggered a real GitGuardian pipeline failure. This will block CI until remediated.

Options to resolve:

  1. Replace the value with a non-secret-looking placeholder (e.g., password=REDACTED or key=placeholder) that still exercises the secretGenerator detection path.
  2. Add a GitGuardian ignore rule/match exclusion for the testdata/ directory.

Option 1 is simpler and keeps the fixture self-contained without scanner configuration changes.

🔐 Proposed fix: replace the flagged literal
 secretGenerator:
   - name: creds
     literals:
-      - password=s3cret
+      - placeholder=not-a-real-secret
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
secretGenerator:
- name: creds
literals:
- password=s3cret
secretGenerator:
- name: creds
literals:
- placeholder=not-a-real-secret
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@internal/manifestanalyzer/testdata/repo-walker/unsupported/unsupported-kustomize/app/kustomization.yaml`
around lines 6 - 9, The test fixture in the kustomize manifest contains a
hardcoded secret-looking literal that GitGuardian flags; update the
`secretGenerator` entry in the `kustomization.yaml` fixture to use a non-secret
placeholder while keeping the unsupported-feature path exercised. Prefer
changing the `literals` value under `secretGenerator` in this manifest to
something clearly fake like a placeholder token, or alternatively add a targeted
ignore for the `testdata/` fixtures if you want to keep the exact value.

Source: Pipeline failures

@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.69822% with 72 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/manifestanalyzer/repowalk.go 78.1% 51 Missing and 21 partials ⚠️

📢 Thoughts on this report? Let us know!

…easons, writer parity)

- rendered/editable now count only what the kustomization resources graph
  renders (reachedResourceFilesFrom), never parked YAML a kustomization does
  not reference; editable is the rendered subset physically in the candidate's
  own subtree. Fixes rendered overstating "everything the candidate renders".
- refused plain / self-contained kustomize candidates now surface the
  acceptance gate's issues as refusalReasons ({code, detail}) instead of a bare
  acceptedByOperator:false, so the product sees WHY (duplicate, non-KRM,
  foreign, mixed, unsupported nested kustomization, ...).
- repo-walker acceptance now uses WriterAllowlist (the live writer's allowlist,
  incl. the operator .sops.yaml bootstrap config) for both the whole-repo store
  and per-candidate acceptance, so a folder the writer would adopt is not
  falsely reported refused.
- gate: add openapi and crds to unsupportedKustomizeFeatureKeys, aligning the
  acceptance gate with the documented support boundary (they silently change
  merge-key semantics; configurations was already refused).

Corpus: add overlay-parked-base (parked YAML excluded from rendered),
plain-nonkrm (gate refusal surfaced), and extend unsupported-kustomize with
openapi/crds; plus regression tests and an in-memory .sops.yaml acceptance test.

Docs: fence the report-contract example as jsonc (it carries an explanatory
comment); note the WriterAllowlist parity, gate-issue surfacing, and openapi/crds
refusal in F8's first-cut section.

Validation: task lint and task test green (coverage 75.2%); task test-e2e
passed 55/55 (the shared-gate openapi/crds change was exercised).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sunib

sunib commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Review round 2 — addressed in 6179082

Finding Resolution
M — rendered overstates the render rendered/editable now count only the kustomization resources graph (reachedResourceFilesFrom), never parked YAML a kustomization doesn't reference. New overlay-parked-base fixture (base holds an unreferenced parked.yaml) locks rendered: 1, not 2.
M — refused candidates lacked reasons Refused plain / self-contained kustomize candidates now surface the gate's issues as refusalReasons ({code, detail}) — candidateAcceptedcandidateAcceptance returning the full Acceptance. New plain-nonkrm fixture shows {code: "non-krm-yaml", detail: "values.yaml: …"}.
M-low — allowlist ≠ live writer repo-walker now uses WriterAllowlist (incl. the operator .sops.yaml bootstrap config) for both the whole-repo store and per-candidate acceptance. In-memory .sops.yaml test asserts the folder is accepted (not refused non-KRM) and .sops.yaml isn't counted as nonKrm.
Low — docs say openapi/crds refused, gate didn't Added openapi + crds to unsupportedKustomizeFeatureKeys (they silently change merge-key semantics). Extended unsupported-kustomize fixture — unsupportedConstructs now includes both. This is a shared-gate (operator) change, so e2e was re-run: 55/55 ✅.
Low — json fence with // comment Fenced the report-contract example as jsonc (it carries one explanatory comment; the shipped report is strict JSON).

Validation: task lint ✅ · task test ✅ (coverage 75.2%) · task test-e2e ✅ 55/55.

Pushed back on none this round — all five were fair. The only judgment call: openapi/crds is an operator-gate behavior change (not just repo-walker), included here because the walker's refused-structural detail reuses the same key list and the boundary doc already decided it; e2e re-run confirms no regression.

@sunib sunib changed the title feat(manifest-analyzer): F8 repo-walker onboarding discovery (first cut) feat(manifest-analyzer): repo-walker onboarding discovery (F8 first cut) Jul 9, 2026
@sunib
sunib merged commit 22e9e92 into main Jul 9, 2026
17 checks passed
@sunib
sunib deleted the feat/gitops-api-f8 branch July 9, 2026 09:56
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