Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ packages organized by domain responsibility.
<!-- MANUAL ADDITIONS END -->

## Recent Changes
- ci-step-summary-report: Emoji status indicators added to `--format pretty` markdown report; `resultEmoji()` helper maps `gemara.Result` to `complytime.Status*` constants; `writeControlsTable()` prefixes Result column with emoji for control and requirement rows; `writeSummary()` prefixes counts table headers with emoji; `writeFindings()` prefixes group headers with emoji; report now directly usable as GitHub Actions Step Summary via `cat report-*.md >> $GITHUB_STEP_SUMMARY` (#697)
- complypack-cache-versioning: `COMPLYTIME_CACHE_VERSIONS` env var configures retention count (default 1) for complypack cache versions per evaluator-id; `NewComplypackCache()` gains `*State` parameter for state-driven lookup and timestamp-based eviction ordering; `evictOldVersions()` becomes retention-count-aware (orphaned dirs first, then oldest by `LastUpdated`); `LookupByEvaluatorID()` resolves from state.json with directory-scan fallback; `SyncComplypack()` checks local cache before remote fetch with re-verification when verifier is configured, returns `(true, nil)` for cache hits to trigger generation invalidation; `EvaluatorIDToVersion()` reverse lookup on `*State`; `CacheRetentionCount()` in `internal/cache/retention.go`; `CheckComplypacks()` extended with `walkCacheSize()` and `findOrphanedVersions()` for cache health reporting; `complyctl doctor` reports cache size and orphaned/untracked versions (#676)
- per-entry-verification: Per-entry `verification:` and `skip_verify:` fields on `PolicyEntry` in `internal/complytime/config.go` for policy-level signature verification overrides; `resolveVerifier()` in `cmd/complyctl/cli/get.go` with verifier cache and resolution priority chain (entry → workspace → none); error collection in `syncAllPolicies()`/`syncAllComplypacks()` via `errors.Join()` for partial-failure resilience; cross-group error collection in `syncAll()` so policy failures do not block complypack sync; per-entry WARNING to stderr on sync failure for real-time feedback; `validateEntries()` extended for per-entry verification validation and mutual exclusivity of `verification:` and `skip_verify:` (#680)
- debug-visible-output: `--debug` flag now tees all log messages to stderr via `teeWriter` in `pkg/log/log.go`; `enableDebug()` in `cmd/complyctl/cli/root.go` reconstructs logger with `NewTeeWriter(stderr, logFile)` and forces `termenv.ANSI256` color profile when stderr is a TTY (respects `NO_COLOR`); `Debug log: <path>` hint printed to stderr after workspace resolution; flag description updated to `"output debug logs to stderr and log file"`; `SetColorProfile()` method added to `CharmHclog` adapter (#614)
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@

### Added

- Emoji status indicators in markdown report (`--format pretty`): Controls
table rows, summary counts headers, and findings group headers now display
emoji prefixes (`✅`, `❌`, `⏭️`, `⚠️`) matching the terminal scan
summary. Makes the report directly usable as a GitHub Actions Step Summary
via `cat report-*.md >> $GITHUB_STEP_SUMMARY` (#697)
- Complypack cache version management: `COMPLYTIME_CACHE_VERSIONS`
environment variable configures how many complypack versions to
retain per evaluator-id in the local cache (default: 1, preserving
Expand Down
29 changes: 24 additions & 5 deletions internal/output/markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ func (m *Markdown) SetEmbedEvaluationLog(path string) {
m.evaluationLog = path
}

// resultEmoji maps a gemara.Result to its corresponding status emoji constant.
func resultEmoji(r gemara.Result) string {
switch r {
case gemara.Passed:
return complytime.StatusPassed
case gemara.Failed:
return complytime.StatusFailed
case gemara.NotApplicable, gemara.NotRun:
return complytime.StatusSkipped
case gemara.Unknown, gemara.NeedsReview:
return complytime.StatusError
default:
return complytime.StatusError
}
}

type summaryCounts struct {
passed int
failed int
Expand Down Expand Up @@ -172,7 +188,9 @@ func (m *Markdown) writeSummary(sb *strings.Builder, now time.Time) {
fmt.Fprintf(sb, "**Overall: %s -- %d%% pass rate (%d/%d applicable)**\n\n",
m.evalLog.Result.String(), passRate, counts.passed, applicable)

fmt.Fprintf(sb, "| Passed | Failed | Needs Review | Unknown | N/A | Not Run | Total |\n")
fmt.Fprintf(sb, "| %s Passed | %s Failed | %s Needs Review | %s Unknown | %s N/A | %s Not Run | Total |\n",
complytime.StatusPassed, complytime.StatusFailed, complytime.StatusError,
complytime.StatusError, complytime.StatusSkipped, complytime.StatusSkipped)
fmt.Fprintf(sb, "|--------|--------|--------------|---------|-----|---------|-------|\n")
fmt.Fprintf(sb, "| %d | %d | %d | %d | %d | %d | %d |\n\n",
counts.passed, counts.failed, counts.needsReview,
Expand All @@ -185,10 +203,11 @@ func (m *Markdown) writeControlsTable(sb *strings.Builder) {
fmt.Fprintf(sb, "|-----------------------|--------|---------|\n")

for _, ce := range m.evalLog.Evaluations {
fmt.Fprintf(sb, "| **%s** | **%s** | %s |\n", ce.Name, ce.Result.String(), ce.Message)
fmt.Fprintf(sb, "| **%s** | **%s %s** | %s |\n",
ce.Name, resultEmoji(ce.Result), ce.Result.String(), ce.Message)
for _, al := range ce.AssessmentLogs {
fmt.Fprintf(sb, "| &nbsp;&nbsp;%s | %s | |\n",
al.Requirement.EntryId, al.Result.String())
fmt.Fprintf(sb, "| &nbsp;&nbsp;%s | %s %s | |\n",
al.Requirement.EntryId, resultEmoji(al.Result), al.Result.String())
}
}
sb.WriteString("\n")
Expand All @@ -210,7 +229,7 @@ func (m *Markdown) writeFindings(sb *strings.Builder) {
if firstGroup || f.result != currentResult {
currentResult = f.result
firstGroup = false
fmt.Fprintf(sb, "### %s\n\n", currentResult.String())
fmt.Fprintf(sb, "### %s %s\n\n", resultEmoji(currentResult), currentResult.String())
}

fmt.Fprintf(sb, "#### %s -- %s\n\n", f.requirementID, f.result.String())
Expand Down
34 changes: 34 additions & 0 deletions internal/output/markdown_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: Apache-2.0

package output

import (
"testing"

"github.com/gemaraproj/go-gemara"
"github.com/stretchr/testify/assert"

"github.com/complytime/complyctl/internal/complytime"
)

func TestResultEmoji(t *testing.T) {
tests := []struct {
name string
result gemara.Result
want string
}{
{"Passed", gemara.Passed, complytime.StatusPassed},
{"Failed", gemara.Failed, complytime.StatusFailed},
{"NotApplicable", gemara.NotApplicable, complytime.StatusSkipped},
{"NotRun", gemara.NotRun, complytime.StatusSkipped},
{"Unknown", gemara.Unknown, complytime.StatusError},
{"NeedsReview", gemara.NeedsReview, complytime.StatusError},
{"default", gemara.Result(99), complytime.StatusError},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := resultEmoji(tt.result)
assert.Equal(t, tt.want, got)
})
}
}
Loading
Loading