Compare commits
2 Commits
24247a8550
...
83a1835474
| Author | SHA1 | Date | |
|---|---|---|---|
| 83a1835474 | |||
| 5c6758e990 |
@@ -1,47 +0,0 @@
|
||||
## Dev Loop Status: 2026-05-15 07:02 UTC
|
||||
|
||||
**Issue:** #141 — validate-docmap subcommand
|
||||
**Branch:** issue-141
|
||||
**Status:** ✅ READY FOR PR SUBMISSION
|
||||
|
||||
### Implementation Complete
|
||||
|
||||
- ✅ `cmd/review-bot/validatedocmap.go` — main subcommand implementation (139 lines)
|
||||
- ✅ `cmd/review-bot/validatedocmap_test.go` — 9 test cases (all passing)
|
||||
- ✅ `review/docmap.go` — exported `FileCoveredByDocMap` helper
|
||||
- ✅ `cmd/review-bot/main.go` — integrated `validate-docmap` subcommand
|
||||
- ✅ `README.md` — documented subcommand with examples and flags
|
||||
|
||||
### Test Results
|
||||
|
||||
```
|
||||
TestRunValidateDocmap_Clean ✅
|
||||
TestRunValidateDocmap_MissingDocmapFlag ✅
|
||||
TestRunValidateDocmap_BadYAML ✅
|
||||
TestRunValidateDocmap_StaleDocs ✅
|
||||
TestRunValidateDocmap_UncoveredFile ✅
|
||||
TestRunValidateDocmap_BothFailures ✅
|
||||
TestRunValidateDocmap_EmptyStdin ✅
|
||||
TestRunValidateDocmap_BlankLinesSkipped ✅
|
||||
TestRunValidateDocmap_DuplicateDocsDeduped ✅
|
||||
```
|
||||
|
||||
### Pre-Push Checks
|
||||
|
||||
- ✅ All packages pass: `go test ./...` (6/6)
|
||||
- ✅ `go vet ./...` — clean
|
||||
- ✅ `go build ./cmd/review-bot` — clean
|
||||
- ✅ `scripts/check-deps.sh` — pass
|
||||
|
||||
### Commits
|
||||
|
||||
1. feat(#141): add validate-docmap subcommand
|
||||
2. docs(#141): add validate-docmap subcommand to README
|
||||
3. fix(#141): use stdinValidateDocmap in Clean test — avoid real os.Stdin dependency
|
||||
|
||||
### Next Steps
|
||||
|
||||
- [ ] Create PR via gitea-rodin token (title: "feat(#141): add validate-docmap subcommand")
|
||||
- [ ] Labels: none yet (AI reviewers will assign)
|
||||
- [ ] Link issue #141
|
||||
|
||||
@@ -68,8 +68,15 @@ func runValidateDocmap(args []string) int {
|
||||
failed := false
|
||||
|
||||
// --- Check 1: Coverage ---
|
||||
// Note: an empty docmap (no mappings) means every changed file is
|
||||
// uncovered — there are no patterns to match against. This is intentional:
|
||||
// if you declare a doc-map, every changed file must be accounted for.
|
||||
// On empty stdin the check is vacuously true (no files to cover).
|
||||
var uncovered []string
|
||||
for _, f := range changedFiles {
|
||||
// Normalize Windows-style backslashes to forward slashes so that
|
||||
// changed-file paths from git on Windows match doc-map globs.
|
||||
f = strings.ReplaceAll(f, "\\", "/")
|
||||
if !review.FileCoveredByDocMap(cfg, f) {
|
||||
uncovered = append(uncovered, f)
|
||||
}
|
||||
@@ -93,7 +100,11 @@ func runValidateDocmap(args []string) int {
|
||||
}
|
||||
resolvedRoot, err := filepath.EvalSymlinks(absRoot)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
fmt.Fprintf(errWriter, "Error: --repo-root %q does not exist\n", *repoRootFlag)
|
||||
} else {
|
||||
fmt.Fprintf(errWriter, "Error: failed to resolve --repo-root %q: %v\n", *repoRootFlag, err)
|
||||
}
|
||||
return 2
|
||||
}
|
||||
// checkStaleDocs validates each path before touching the filesystem; see
|
||||
@@ -151,7 +162,7 @@ func checkStaleDocs(cfg *review.DocMapConfig, repoRoot string) []string {
|
||||
// filepath.Rel check confirms the path is still under repoRoot.
|
||||
fullPath := filepath.Clean(filepath.Join(repoRoot, filepath.FromSlash(docPath)))
|
||||
rel, err := filepath.Rel(repoRoot, fullPath)
|
||||
if err != nil || strings.HasPrefix(rel, "..") {
|
||||
if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
||||
stale = append(stale, docPath)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -131,6 +131,12 @@ mappings:
|
||||
}
|
||||
|
||||
// stdinValidateDocmap runs runValidateDocmap with a synthetic stdin.
|
||||
//
|
||||
// Implementation note: we write stdinContent to a temp file and point
|
||||
// os.Stdin at it. The defer f.Close() fires after stdinValidateDocmap
|
||||
// returns, which is after runValidateDocmap has finished reading stdin
|
||||
// synchronously — so the file is not closed while still in use.
|
||||
// Tests must not call t.Parallel() while sharing the global os.Stdin.
|
||||
func stdinValidateDocmap(t *testing.T, stdinContent string, args []string) (code int, stdout, stderr string) {
|
||||
t.Helper()
|
||||
// Write stdin content to a temp file and redirect os.Stdin.
|
||||
|
||||
+5
-5
@@ -310,11 +310,11 @@ func readFileBytes(path string) ([]byte, error) {
|
||||
return os.ReadFile(path)
|
||||
}
|
||||
|
||||
// ValidateDocPath rejects doc paths that could cause path traversal via the
|
||||
// VCS API (absolute paths, any ".." segment, backslashes). Defense-in-depth:
|
||||
// the VCS API should already scope paths to the repo, but we validate locally
|
||||
// to avoid any quirk in backend path handling. Backslashes are rejected
|
||||
// explicitly to prevent Windows platform edge cases.
|
||||
// ValidateDocPath rejects doc paths that could cause path traversal
|
||||
// (absolute paths, any ".." segment, backslashes). Defense-in-depth: callers
|
||||
// must also confine the joined path to the repo root via filepath.Rel before
|
||||
// any filesystem access. Backslashes are rejected explicitly to prevent
|
||||
// Windows platform edge cases.
|
||||
func ValidateDocPath(p string) error {
|
||||
if strings.Contains(p, "\\") {
|
||||
return fmt.Errorf("backslashes not allowed in doc paths")
|
||||
|
||||
Reference in New Issue
Block a user