Skip to content

Refactor duplicated workflow parsing and timeline rendering helpers in pkg/cli and pkg/workflow#45858

Merged
pelikhan merged 9 commits into
mainfrom
copilot/refactor-semantic-function-clustering-again
Jul 16, 2026
Merged

Refactor duplicated workflow parsing and timeline rendering helpers in pkg/cli and pkg/workflow#45858
pelikhan merged 9 commits into
mainfrom
copilot/refactor-semantic-function-clustering-again

Conversation

Copilot AI commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

This PR addresses verified semantic duplication hotspots in pkg/cli and pkg/workflow, focusing on parser consolidation, workflow ID normalization reuse, ${{ }} predicate unification, and timeline row renderer deduplication. The goal is behavior-preserving consolidation of repeated logic into canonical helpers.

  • Engine config parser dedup (pkg/workflow/engine_config_parser.go)

    • Extracted shared helpers for repeated integer parsing paths:
      • parsePositiveIntValue(raw, fieldName)
      • parseIntOrExpressionValue(raw, minValue, fieldName)
    • Routed max-runs, max-turn-cache-misses, max-turns, harness.max-retries, and max-tool-denials through shared logic.
    • Kept per-field semantics intact via minValue and field-specific logging labels.
  • Expression wrapper predicate unification (pkg/workflow)

    • Replaced inline ${{ ... }} wrapper checks with shared isExpression(...) usage in:
      • expression_parser.go
      • safe_outputs_config_base.go
      • observability_otlp.go
      • engine_config_parser.go
    • Removed repeated prefix/suffix checks while preserving existing behavior.
  • Workflow-ID-from-path consolidation (pkg/cli)

    • Replaced local .md basename stripping paths with the existing normalization flow (normalizeWorkflowIDstringutil.NormalizeWorkflowName) in:
      • workflows.go
      • spec.go
      • experiments_command.go
    • Centralized extension handling (.md, .lock.yml) and path/basename normalization.
  • Timeline row renderer dedup (pkg/cli/gateway_logs_timeline_render.go)

    • Collapsed duplicated assistant/reasoning row construction into:
      • renderMessageContentTimelineRow(kind, evt)
    • Kept per-kind label/icon behavior by passing explicit TimelineEventKind.
func parseMaxTurnsValue(raw any) string {
	return parseIntOrExpressionValue(raw, 1, "max-turns")
}

func parseNonNegativeIntOrExpressionValue(raw any) string {
	return parseIntOrExpressionValue(raw, 0, "harness.max-retries")
}

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor semantic function clustering with 8 de-duplication opportunities Refactor duplicated workflow parsing and timeline rendering helpers in pkg/cli and pkg/workflow Jul 16, 2026
Copilot AI requested a review from pelikhan July 16, 2026 01:19
@github-actions

Copy link
Copy Markdown
Contributor

Hey @copilot-swe-agent 👋 — thanks for tackling the semantic function clustering de-duplication in pkg/cli and pkg/workflow! The refactoring direction looks solid and the new test files are a great addition.

A couple of things to wrap up before this is ready for review:

  • Add a prose description — the PR body is currently a task checklist, but reviewers need a short summary of what was changed and why (e.g., which helper extractions were made, what duplication was removed, and what the motivation was). A few sentences at the top would go a long way.
  • Complete the final checks — the last checklist item (make fmt, make agent-report-progress, security/secret scans, parallel validation) is still unchecked. Run and report those results before marking the draft as ready.

If you'd like a hand finishing this up, you can assign this prompt to your coding agent:

Complete the final validation steps for PR #45858 (refactor semantic function clustering):
1. Run `make fmt` and commit any formatting fixes.
2. Run `make agent-report-progress` and include the output in the PR description.
3. Run security/secret scans and confirm no issues.
4. Run `go test ./pkg/cli/... ./pkg/workflow/...` in parallel and confirm all tests pass.
5. Replace the PR body checklist with a short prose description covering: what duplicate clusters were consolidated, which helpers were extracted, and why this improves maintainability.
6. Mark the PR as ready for review.

Generated by ✅ Contribution Check · 124.7 AIC · ⌖ 9.92 AIC · ⊞ 6.2K ·

@pelikhan
pelikhan marked this pull request as ready for review July 16, 2026 04:24
Copilot AI review requested due to automatic review settings July 16, 2026 04:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Consolidates duplicated parsing, workflow normalization, expression detection, and timeline rendering while preserving behavior.

Changes:

  • Centralizes engine integer/expression parsing.
  • Reuses canonical expression and workflow-ID helpers.
  • Deduplicates message timeline rendering and adds tests.
Show a summary per file
File Description
pkg/workflow/safe_outputs_config_base.go Reuses expression detection.
pkg/workflow/observability_otlp.go Unifies OTLP expression checks.
pkg/workflow/expression_parser.go Reuses wrapper detection.
pkg/workflow/engine_config_parser.go Consolidates numeric parsing.
pkg/workflow/engine_config_parser_test.go Tests consolidated parsers.
pkg/cli/workflows.go Centralizes workflow-ID normalization.
pkg/cli/spec.go Normalizes parsed workflow names.
pkg/cli/experiments_command.go Normalizes experiment workflow filenames.
pkg/cli/gateway_logs_timeline_render.go Deduplicates message row rendering.
pkg/cli/gateway_logs_timeline_test.go Tests assistant and reasoning rows.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 10/10 changed files
  • Comments generated: 0
  • Review effort level: Medium

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review: Behavior-preserving refactor ✅

The consolidation is clean and correct. All four areas (engine config parser, isExpression predicate, workflow-ID normalization, timeline renderer) converge on the same semantics as before.

One non-blocking issue flagged inline:

parseNonNegativeIntOrExpressionValue (the named wrapper) hard-codes "harness.max-retries" as the diagnostic label inside the shared parseIntOrExpressionValue helper. Because the wrapper is named generically, any future re-use at a different call site would produce misleading log output. Rename the wrapper to parseHarnessMaxRetriesValue or add a fieldName parameter to make the coupling explicit.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 27.2 AIC · ⌖ 4.39 AIC · ⊞ 5K

Comment thread pkg/workflow/engine_config_parser.go Outdated
// non-negative integer (≥ 0) or a GitHub Actions expression template (${{ ... }}).
// It is intentionally distinct from parseMaxTurnsValue which rejects zero.
// Returns the canonical string representation, or "" when the value is absent/invalid.
func parseNonNegativeIntOrExpressionValue(raw any) string {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The wrapper parseNonNegativeIntOrExpressionValue hardcodes "harness.max-retries" as the field name passed to parseIntOrExpressionValue, but the function name and doc comment describe it as a generic non-negative integer/expression helper. Any future caller (or test log output) will see a misleading field label.

Consider either:

  1. Adding a fieldName string parameter to parseNonNegativeIntOrExpressionValue so each call site supplies the correct label, or
  2. Renaming the wrapper to parseHarnessMaxRetriesValue to make the coupling explicit.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Renamed to parseHarnessMaxRetriesValue (Option 2) to make the single-purpose coupling explicit. Updated call site in engine.go and test references accordingly. Commit: d1b5b8e.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

⚠️ Test Quality Score: 72/100 — Acceptable

Analyzed 4 test(s): 4 design, 0 implementation, 0 violation(s).

📊 Metrics (4 tests)
Metric Value
Analyzed 4 (Go: 4, JS: 0)
✅ Design 4 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 3 (75%)
Duplicate clusters 0
Inflation Yes (3:1–4:1)
🚨 Violations 0
Test File Classification Status
TestParsePositiveIntValues pkg/workflow/engine_config_parser_test.go Design (table-driven, 6 scenarios) ✅ Pass
TestParseIntOrExpressionValues pkg/workflow/engine_config_parser_test.go Design (table-driven, 6 scenarios) ✅ Pass
TestRenderAgentAssistantMessageRow pkg/cli/gateway_logs_timeline_test.go Design ⚠️ Happy-path only
TestRenderAgentReasoningRow pkg/cli/gateway_logs_timeline_test.go Design ⚠️ Happy-path only

Quality Observations

Strengths:

  • ✅ All 4 tests are design-focused, verifying behavioral contracts of refactored parser functions
  • ✅ Proper Go build tags (//go:build !integration)
  • ✅ Table-driven tests with parallel execution enabled
  • ✅ Assertion messages are descriptive

Concerns:

  • ⚠️ Test inflation: engine_config_parser_test.go adds 60 lines for 20 lines of refactored code (3:1 ratio). This is expected for extracted helpers but exceeds best-practice thresholds.
  • ⚠️ Timeline render tests (TestRenderAgentAssistantMessageRow, TestRenderAgentReasoningRow) only test happy path—no truncation edge cases or empty content scenarios.

Verdict

Passed. 0% implementation tests (threshold: 30%). All new tests enforce behavioral contracts of refactored parser and render functions. Build tags are correct; no mocking violations.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 20.5 AIC · ⌖ 8.77 AIC · ⊞ 6.8K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 72/100. All 4 new tests are design-focused with proper build tags and no mocking violations. 0% implementation tests (threshold: 30%). See detailed analysis in comment above.

…elpers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (127 new lines in pkg/workflow and pkg/cli) but does not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/45858-consolidate-duplicated-parsing-helpers.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI could not infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-45858: Consolidate Duplicated Parsing Helpers

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I will deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 45858-consolidate-duplicated-parsing-helpers.md for PR #45858).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · 46.7 AIC · ⌖ 9.34 AIC · ⊞ 8.5K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Skills-Based Review\n\nApplied /codebase-design and /tdd — no blocking issues, but a few improvements worth addressing. See inline comments.\n\nPositive highlights: clean renderMessageContentTimelineRow extraction, consistent isExpression usage, tests added alongside new helpers, behavior-preserving refactor.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 39.4 AIC · ⌖ 4.75 AIC · ⊞ 6.7K
Comment /matt to run again

// Returns the canonical string representation, or "" when the value is absent/invalid.
func parseNonNegativeIntOrExpressionValue(raw any) string {
if val, ok := typeutil.ParseIntValue(raw); ok && val >= 0 {
return parseIntOrExpressionValue(raw, 0, "harness.max-retries")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/codebase-design] parseNonNegativeIntOrExpressionValue hardcodes "harness.max-retries" as the field name inside the generic parseIntOrExpressionValue helper — any future reuse of this wrapper for a different field would emit misleading log messages.

💡 Suggested fix

Either pass the field name through as a parameter, or rename this wrapper to parseHarnessMaxRetriesValue to make it clear it is single-purpose:

// Option A: rename to be explicit
func parseHarnessMaxRetriesValue(raw any) string {
    return parseIntOrExpressionValue(raw, 0, "harness.max-retries")
}

// Option B: accept a field name param and update callers
func parseNonNegativeIntOrExpressionValue(raw any, fieldName string) string {
    return parseIntOrExpressionValue(raw, 0, fieldName)
}

The generic name with a hard-coded label is the worst of both worlds — it looks reusable but logs the wrong field name if reused.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Renamed to parseHarnessMaxRetriesValue (Option A) — the function is only called in one place so naming it after the field makes the coupling explicit and prevents misleading log output on reuse. Commit: d1b5b8e.

return parsePositiveIntValue(raw, "max-turn-cache-misses")
}

func parsePositiveIntValue(raw any, fieldName string) int {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/codebase-design] parsePositiveIntValue and parseIntOrExpressionValue have overlapping logic (both parse int-or-string), but the former returns int and the latter returns string. This creates two parallel parsing paths that can diverge. Consider whether parsePositiveIntValue could internally call parseIntOrExpressionValue (ignoring the expression branch) to reduce the chance of subtle future drift.

💡 Example
func parsePositiveIntValue(raw any, fieldName string) int {
    s := parseIntOrExpressionValue(raw, 1, fieldName)
    if s == "" {
        return 0
    }
    val, _ := strconv.Atoi(s)
    return val
}

This keeps a single source of truth for the int-validation logic.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Refactored parsePositiveIntValue to delegate to parseIntOrExpressionValue, making it the single source of truth for int-validation logic. GitHub Actions expression strings are documented to silently produce 0 (not configured) since these fields are integer-only. Commit: d1b5b8e.

var matches []string
for _, filename := range filenames {
base := strings.TrimSuffix(filename, ".md")
base := normalizeWorkflowID(filename)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/codebase-design] matchWorkflowFilenameByExperiment receives plain filenames (no path prefix), but normalizeWorkflowID calls filepath.Base before stripping the extension. Calling filepath.Base on a plain filename like my-flow.md is harmless, but this is an implicit coupling — the function's behaviour depends on an internal detail of normalizeWorkflowID. A brief comment or the existing function-level doc should note this assumption.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added a doc-comment paragraph to matchWorkflowFilenameByExperiment explicitly noting that normalizeWorkflowID calls filepath.Base internally, so any path prefix is stripped before matching and only the basename is returned. Commit: d1b5b8e.

{name: "max-runs invalid", parse: parseMaxRunsValue, raw: "oops", expected: 0},
{name: "max-turn-cache-misses int", parse: parseMaxTurnCacheMissesValue, raw: 2, expected: 2},
{name: "max-turn-cache-misses string", parse: parseMaxTurnCacheMissesValue, raw: "7", expected: 7},
{name: "max-turn-cache-misses invalid", parse: parseMaxTurnCacheMissesValue, raw: "bad", expected: 0},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] The test for max-runs invalid only covers a non-numeric string. Consider adding boundary cases: 0 (should return 0 since positive means > 0), negative integers, and nil. These edge cases are the most common sources of regressions when a shared helper like parsePositiveIntValue is changed.

💡 Suggested additions
{name: "max-runs zero", parse: parseMaxRunsValue, raw: 0, expected: 0},
{name: "max-runs negative", parse: parseMaxRunsValue, raw: -1, expected: 0},
{name: "max-runs nil", parse: parseMaxRunsValue, raw: nil, expected: 0},

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added max-runs zero, max-runs negative, and max-runs nil test cases to TestParsePositiveIntValues. All three return 0 as expected. Commit: d1b5b8e.

raw any
expected string
}{
{name: "max-turns rejects zero", parse: parseMaxTurnsValue, raw: 0, expected: ""},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] TestParseIntOrExpressionValues does not test the truncation/trimming behaviour for the string path (e.g. " 5 " with surrounding whitespace should parse to "5"). The previous inline implementation had an explicit strings.TrimSpace call — a test documenting this would guard against accidental removal.

💡 Suggested addition
{name: "max-turns trims whitespace", parse: parseMaxTurnsValue, raw: " 3 ", expected: "3"},

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added max-turns trims whitespace (raw: " 3 ", expected: "3") and a companion non-negative trims whitespace case for parseHarnessMaxRetriesValue. Both pass. Commit: d1b5b8e / fa53a73.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Overall Assessment

Solid, well-scoped refactor. The deduplication is behaviour-preserving in all current call paths, and the added tests are a clear improvement. Two non-blocking observations worth addressing.

Findings summary

1. parseNonNegativeIntOrExpressionValue hardcodes "harness.max-retries" in the generic helper (see inline comment) — medium maintainability concern; if the wrapper is ever reused for another field, error logs will silently misidentify the source.

2. matchWorkflowFilenameByExperiment implicit filepath.Base addition (see inline comment) — the old code did not call filepath.Base, normalizeWorkflowID does. Behaviour is correct for bare filenames from the GitHub API, but the semantic change is undocumented and worth a test or comment to protect against future regressions.

All other changed paths (isExpression extraction, parseIntOrExpressionValue generalization, timeline renderer dedup, parsePositiveIntValue) are clean equivalence refactors.

🔎 Code quality review by PR Code Quality Reviewer · 53 AIC · ⌖ 4.65 AIC · ⊞ 5.6K
Comment /review to run again

// Returns the canonical string representation, or "" when the value is absent/invalid.
func parseNonNegativeIntOrExpressionValue(raw any) string {
if val, ok := typeutil.ParseIntValue(raw); ok && val >= 0 {
return parseIntOrExpressionValue(raw, 0, "harness.max-retries")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

parseNonNegativeIntOrExpressionValue hardcodes an unrelated field name in the generic helper: calling parseIntOrExpressionValue(raw, 0, "harness.max-retries") bakes a specific caller's field name into the reusable function.

💡 Why this matters / suggested fix

The fieldName argument is used only for logging. Currently accurate because this function is only called from the harness max-retries code path, but if parseNonNegativeIntOrExpressionValue is ever reused for a second field, every invalid-value log line will incorrectly say harness.max-retries — silent, confusing, and hard to catch in production.

Consider accepting the field name as a parameter:

func parseNonNegativeIntOrExpressionValue(raw any, fieldName string) string {
    return parseIntOrExpressionValue(raw, 0, fieldName)
}

or, since the function is only called in one place, calling the generic helper directly at that call site to keep the label accurate.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Renamed to parseHarnessMaxRetriesValue — the name now accurately reflects its single purpose and the fieldName label in parseIntOrExpressionValue will always be correct. Commit: d1b5b8e.

var matches []string
for _, filename := range filenames {
base := strings.TrimSuffix(filename, ".md")
base := normalizeWorkflowID(filename)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

matchWorkflowFilenameByExperiment now applies filepath.Base to input filenames via normalizeWorkflowID; the old code did not: the change silently alters what gets appended to matches and returned to the caller.

💡 Analysis

Old code: strings.TrimSuffix(filename, ".md") — strips the extension but preserves any path prefix in the returned value.

New code: normalizeWorkflowID(filename) → calls filepath.Base first, so any path component is stripped before the result is stored in matches.

If the GitHub API returns bare filenames like my-workflow.md, filepath.Base is a no-op and behaviour is identical. But if the API ever returns paths (e.g. .github/workflows/my-workflow.md), the old code would append .github/workflows/my-workflow to matches while the new code appends just my-workflow. Since the function is documented as returning the basename, the new behaviour is arguably more correct — but the change is implicit and undocumented, and callers relying on the old path-preserving behaviour would silently break.

Recommend adding a test that verifies the function handles path-prefixed filenames as expected, or a comment confirming that callers always receive bare basenames.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added a doc-comment to matchWorkflowFilenameByExperiment documenting the filepath.Base call inside normalizeWorkflowID, clarifying the behavior for bare filenames vs. path-prefixed ones and confirming callers receive only the basename. Commit: d1b5b8e.

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

Copilot AI and others added 2 commits July 16, 2026 04:46
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…command

- Rename parseNonNegativeIntOrExpressionValue → parseHarnessMaxRetriesValue to make
  the single-purpose coupling explicit and avoid misleading log output on reuse
- Refactor parsePositiveIntValue to delegate to parseIntOrExpressionValue,
  eliminating a parallel int-validation path that could silently diverge
- Add boundary test cases (zero, negative, nil) for parsePositiveIntValue
- Add whitespace-trimming test case for parseIntOrExpressionValue
- Add doc comment to matchWorkflowFilenameByExperiment clarifying that
  normalizeWorkflowID calls filepath.Base, stripping any path prefix from filenames

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…dd whitespace test for parseHarnessMaxRetriesValue

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan

Copy link
Copy Markdown
Collaborator

Copilot AI and others added 2 commits July 16, 2026 05:16
…PositiveIntValue

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…or in parsePositiveIntValue

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

@copilot lint go

https://github.com/github/gh-aw/actions/runs/29472694212/job/87539119390#step:9:1

Fixed in the latest commit. The strconvparseignorederror linter was flagging line 48 where strconv.Atoi error was silently discarded. Updated parsePositiveIntValue to explicitly check isExpression(s) first (returning 0 for expression strings, as intended) and then handle the strconv.Atoi error explicitly.

@pelikhan
pelikhan merged commit de126a1 into main Jul 16, 2026
28 of 29 checks passed
@pelikhan
pelikhan deleted the copilot/refactor-semantic-function-clustering-again branch July 16, 2026 06:14
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.82.10

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.

[refactor] Semantic function clustering: 8 verified de-duplication opportunities in pkg/cli and pkg/workflow

3 participants