Skip to content

Render token table to core.info in parse_token_usage step#40227

Merged
pelikhan merged 2 commits into
mainfrom
copilot/render-token-table-to-core-info
Jun 19, 2026
Merged

Render token table to core.info in parse_token_usage step#40227
pelikhan merged 2 commits into
mainfrom
copilot/render-token-table-to-core-info

Conversation

Copilot AI commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

The token usage table was only written to the GitHub step summary (collapsible <details> block), making it invisible in the raw step log output. Step 58 now also emits the table directly to core.info.

Changes

  • parse_token_usage.cjs: Added renderTokenTableAsPlainText(title, markdown) — strips table separator rows, leading/trailing pipes, and **bold** markers — and calls core.info() with the result before writing to the step summary.
  • parse_token_usage.test.cjs: Tests for renderTokenTableAsPlainText (separator removal, pipe stripping, bold removal, title prefix) and asserts core.info receives table content in the main() integration test.

Example log output (step 58):

Token Usage

 #  | Alias    | Input  | Output | Cache Read | Cache Write | ΔAI Credits | AI Credits | Duration
 1  | sonnet46 | 1,234  | 456    | 5,000      | 3,000       | 0.023       | 0.023      | 2,500ms
 Total |       | 1,234  | 456    | 5,000      | 3,000       |             | 0.023      | 2,500ms

Legend: ...

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title Add token table rendering to core.info in parse_token_usage step Render token table to core.info in parse_token_usage step Jun 19, 2026
Copilot AI requested a review from pelikhan June 19, 2026 04:30
@pelikhan pelikhan marked this pull request as ready for review June 19, 2026 04:31
Copilot AI review requested due to automatic review settings June 19, 2026 04:31
@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

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

This PR makes the token usage table visible in the raw GitHub Actions step logs by rendering the markdown token-usage table into a more log-friendly plain-text form and emitting it via core.info, in addition to the existing step summary output.

Changes:

  • Added renderTokenTableAsPlainText(title, markdown) to convert the generated markdown table into log-friendly text.
  • Updated main() in parse_token_usage.cjs to core.info() the rendered table before writing the step summary.
  • Added unit tests for the plain-text renderer and extended the main() integration test to assert core.info receives the table content.
Show a summary per file
File Description
actions/setup/js/parse_token_usage.cjs Adds a plain-text renderer and logs the token usage table via core.info during parsing.
actions/setup/js/parse_token_usage.test.cjs Adds targeted tests for the renderer and updates main() integration expectations around core.info.

Copilot's findings

Tip

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

  • Files reviewed: 2/2 changed files
  • Comments generated: 2

Comment on lines +113 to +123
function renderTokenTableAsPlainText(title, markdown) {
const plainText = markdown
.replace(/^\|-+.*-+\|$/gm, "") // Remove table separator lines
.replace(/^\|/gm, "") // Remove leading pipe from table rows
.replace(/\|$/gm, "") // Remove trailing pipe from table rows
.replace(/\s*\|\s*/g, " | ") // Normalize remaining pipes to spaced separators
.replace(/\*\*(.*?)\*\*/g, "$1") // Remove bold markers
.replace(/\n{3,}/g, "\n\n") // Collapse excess blank lines
.trim();
return `${title}\n\n${plainText}`;
}
Comment on lines +549 to +553
expect(result).toContain("Token Usage");
// separator line is removed
expect(result).not.toMatch(/\|--/);
// leading/trailing pipes are stripped
expect(result).not.toMatch(/^\|/m);
@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

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

No ADR enforcement needed: PR #40227 does not have the 'implementation' label and has 0 new lines of code in business logic directories (default_business_additions=0, threshold=100). Neither enforcement condition is met.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 80/100 — Excellent

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

📊 Metrics & Test Classification (3 tests analyzed)
Metric Value
New/modified tests analyzed 3
✅ Design tests (behavioral contracts) 3 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 1 (33%)
Duplicate test clusters 0
Test inflation detected No (ratio 1.32:1)
🚨 Coding-guideline violations 0
Test File Classification Issues Detected
main appends token usage to step summary (assertions added) parse_token_usage.test.cjs:195 ✅ Design
renderTokenTableAsPlainText strips table separator lines and pipes parse_token_usage.test.cjs:543 ✅ Design
renderTokenTableAsPlainText prefixes output with title parse_token_usage.test.cjs:564 ✅ Design Minor: overlaps with test above

Go: 0 (*_test.go); JavaScript: 3 (*.test.cjs). No other languages detected.

📝 Test Notes

renderTokenTableAsPlainText prefixes output with title (parse_token_usage.test.cjs:564) — The single startsWith assertion is largely covered by the strips-and-pipes test above (which already asserts toContain("Token Usage")). Not flagged as a violation, but consider merging into the larger test or adding a unique edge case (e.g., empty markdown, no-table input) to distinguish its value.

Missing edge casesrenderTokenTableAsPlainText is a pure text transformation with no error paths, so the absence of .toThrow() / .rejects is expected. Consider adding a case for empty-string or no-table input to ensure graceful output.

Verdict

Check passed. 0% implementation tests (threshold: 30%). All new tests verify observable behavior — the rendered plain-text output and the core.info side-effect — making them resilient to internal refactoring.

🧪 Test quality analysis by Test Quality Sentinel · 106.5 AIC · ⌖ 10.3 AIC · ⊞ 8.3K ·

@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 🧠

Applied /diagnose and /tdd — requesting changes for one confirmed correctness bug plus test coverage gaps that mask it.

📋 Key Themes & Findings

❌ Confirmed Bug — Separator Regex (line 115)

The regex /^\|-+.*-+\|$/gm requires dashes immediately before the closing |, so it silently skips the production separator row:

|--:|-------|------:|-------:|-----------:|...

Every right-aligned column (---:|) ends with :|, not -|. The separator row therefore survives the strip step and lands in the step log as a garbage line like --: | ------- | ------: | -------:. Verifiable with:

node -e "const s='|--:|-------|------:|-------:|'; console.log(/^\\|-+.*-+\\|$/.test(s))" // false

Suggested fix: /^\|(?:[-:\s]+\|)+$/gm — matches any line whose pipe-delimited cells contain only -, :, and spaces.

⚠️ Test Coverage Gaps

  • False-positive assertion (line 551): not.toMatch(/\|--/) passes with the broken regex because the leading | was already stripped before this check runs.
  • Over-loose integration matchers (lines 199-200): stringContaining("Alias") would pass even if the function was a no-op.
  • Missing production-format test: adding expect(result).not.toContain("---") would immediately catch the separator bug.

✅ Positive Highlights

  • Clean, focused, purely additive change — well-scoped to the rendering concern.
  • Good JSDoc on the new function.
  • The regex pipeline approach and core.info usage is exactly right; only the first regex needs a fix.
  • Exporting the function for unit testing is a good practice.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 73.9 AIC · ⌖ 14.6 AIC · ⊞ 6.9K

Comment thread actions/setup/js/parse_token_usage.cjs Outdated
*/
function renderTokenTableAsPlainText(title, markdown) {
const plainText = markdown
.replace(/^\|-+.*-+\|$/gm, "") // Remove table separator lines

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.

[/diagnose] Bug: this regex does not remove separator rows that use column-alignment notation (:---, ---:, :---:), which is exactly what the production table uses.

The production separator in parse_mcp_gateway_log.cjs is:

|--:|-------|------:|-------:|-----------:|...

The pattern ^\|-+.*-+\|$ requires dashes immediately before the closing |, but every right-aligned or center-aligned column ends with :| — so the separator survives stripping and appears verbatim in the step log.

💡 Suggested fix

Match any line whose cells contain only -, :, and spaces:

.replace(/^\|[-:\s|]+\|$/gm, "") // Remove table separator lines (including aligned columns)

or more explicitly:

.replace(/^\|(?:[-:\s]+\|)+$/gm, "") // Remove table separator lines

A quick verification:

node -e "
const sep = '|--:|-------|------:|-------:|';
console.log(/^\|(?:[-:\s]+\|)+$/.test(sep)); // true
"


expect(result).toContain("Token Usage");
// separator line is removed
expect(result).not.toMatch(/\|--/);

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] This assertion gives a false positive — it passes even when the separator is NOT removed.

After .replace(/^\|/gm, "") strips the leading pipe, the separator row |--:|-------|------:|-------:| becomes --:|-------|------:|-------:|, which no longer matches /\|--/. The separator content is still present in the output; it just lost its leading |.

This means the test green-lights a buggy implementation (the separator regex above misses aligned columns).

💡 Stronger assertion
// Separator dashes should be completely absent (not just moved)
expect(result).not.toMatch(/^\s*--/m); // no line starting with dashes
// OR more precisely:
expect(result.split("\n")).not.toContain(expect.stringMatching(/^[-:\s|]+$/));

});

test("renderTokenTableAsPlainText strips table separator lines and pipes", () => {
const markdown = ["| # | Alias | Input | Output |", "|--:|-------|------:|-------:|", "| 1 | sonnet46 | 100 | 200 |", "| **Total** | | **100** | **200** |", "", "Legend: `Alias` is the model shorthand.", ""].join("\n");

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 unit test uses the same separator format (|--:| ends with :|) that proves the production bug — but the test’s own assertions are not tight enough to catch the failure (see comment on line 551).

Also worth adding a test using the exact separator from parse_mcp_gateway_log.cjs line 172 to lock in the real-world contract:

💡 Suggested additional test input
const productionSeparator = "|--:|-------|------:|-------:|-----------:|------------:|-------------:|-----------:|---------:|";
const result = renderTokenTableAsPlainText("T", `| # |\n${productionSeparator}\n| 1 |`);
expect(result).not.toContain("---"); // separator fully gone

This would have caught the bug immediately.

expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Token usage summary appended"));
// Token table should also be rendered to core.info
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Token Usage"));
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Alias"));

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] These matchers are too loose to verify the plain-text rendering actually works. stringContaining("Token Usage") and stringContaining("Alias") would both pass even if renderTokenTableAsPlainText returned the raw markdown unmodified (since | Alias | contains "Alias").

💡 Suggested stronger assertion

Add an assertion that proves markdown was stripped:

// Verify the output is plain text, not markdown
expect(mockCore.info).not.toHaveBeenCalledWith(expect.stringContaining("**"));
expect(mockCore.info).not.toHaveBeenCalledWith(expect.stringMatching(/^\|/m));

}
const markdown = generateTokenUsageSummary(summary);
if (markdown.length > 0) {
core.info(renderTokenTableAsPlainText(getSummaryTitle(), markdown));

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] Minor: calling getSummaryTitle() twice (here and in appendStepSummarySection) is harmless, but if the env var ever becomes expensive to read, this could be extracted into a local variable once.

const title = getSummaryTitle();
core.info(renderTokenTableAsPlainText(title, markdown));
await appendStepSummarySection(title, markdown);

This also makes the intent clearer — same title for both the log and the summary.

@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: 80/100. Test quality is acceptable — 0% of new tests are implementation tests (threshold: 30%).

@github-actions github-actions Bot mentioned this pull request Jun 19, 2026

@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.

REQUEST_CHANGES — two distinct correctness bugs must be fixed before merging.

Blocking issues

Separator regex never fires (high)

The regex /^\|-+.*-+\|$/gm on line 115 requires the separator line to end in one or more dashes followed by |. Every separator produced by generateTokenUsageSummary ends in :| (alignment marker), e.g.:

|--:|-------|------:|-------:|-----------:|------------:|-------------:|-----------:|---------:|

So the separator is never removed — it just gets its outer pipes stripped by steps 2–3, becoming --:|-------|...: noise visible in the raw log. Existing inline comment on L123 covers this; the fix needs to land.

Global pipe normalization corrupts non-table content (medium)

See inline comment on line 118 for the full analysis and a suggested row-scoped rewrite that also solves the separator regex in one pass.

Test blind spot

not.toMatch(/\|--/) at L551 passes even when the separator survives (the leading | is stripped by steps 2–3). The test gives false confidence that separator removal works. Existing inline comment on L551 covers this.

Warning

Firewall blocked 1 domain

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

  • patchdiff.githubusercontent.com

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

network:
  allowed:
    - defaults
    - "patchdiff.githubusercontent.com"

See Network Configuration for more information.

🔎 Code quality review by PR Code Quality Reviewer · 133.3 AIC · ⌖ 9.68 AIC · ⊞ 5.1K

.replace(/^\|-+.*-+\|$/gm, "") // Remove table separator lines
.replace(/^\|/gm, "") // Remove leading pipe from table rows
.replace(/\|$/gm, "") // Remove trailing pipe from table rows
.replace(/\s*\|\s*/g, " | ") // Normalize remaining pipes to spaced separators

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.

Global pipe normalization is not scoped to table rows — it silently corrupts any | in legend text, inline code, or future non-table content.

💡 Details and suggested fix

After steps 2–3 strip only the outermost | from each table row, step 4's .replace(/\s*\|\s*/g, " | ") runs on the entire markdown string — including the Legend section and any inline code. If the legend (or any future content produced by generateTokenUsageSummary) ever contains a | character (e.g. Bitwise OR: \a | b``) it will be silently reformatted with no test catching the regression.

A safer approach scopes the transformation to table rows only, and also naturally fixes the separator regex bug (line 115) — a filter like /^\|[\|:\-\s]+\|$/ reliably matches any GFM alignment separator regardless of whether colons are present:

function renderTokenTableAsPlainText(title, markdown) {
  const plainText = markdown
    .split("\n")
    .filter(line => !/^\|[\|:\-\s]+\|$/.test(line.trim())) // drop GFM separator rows reliably
    .map(line =>
      line.startsWith("|") && line.endsWith("|")
        ? line.slice(1, -1).replace(/\s*\|\s*/g, " | ") // normalize only table rows
        : line
    )
    .join("\n")
    .replace(/\*\*(.*?)\*\*/g, "$1")
    .replace(/\n{3,}/g, "\n\n")
    .trim();
  return `${title}\n\n${plainText}`;
}

This also eliminates the need for three separate chained .replace() calls with global regexes over the whole string.

});

test("renderTokenTableAsPlainText prefixes output with title", () => {
const result = renderTokenTableAsPlainText("My Token Usage", "| A |\n|---|\n| 1 |");

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 |---| separator in this fixture accidentally exercises the broken regex path — the test doesn't prove the function handles real alignment-decorated separators.

💡 Details

|---| ends with -|, so it does match the current (buggy) regex /^\|-+.*-+\|$/gm. This means renderTokenTableAsPlainText prefixes output with title silently passes even with the broken separator regex, creating the illusion of correctness.

Every real separator produced by generateTokenUsageSummary ends with :|, e.g. |--:|-------|------:|-------:|. Fix the fixture to match:

const result = renderTokenTableAsPlainText(
  "My Token Usage",
  "| A | B |\n|---|---:|\n| 1 | 2 |"
);

With the current broken regex this test would then fail, correctly surfacing the bug in CI.

}
const markdown = generateTokenUsageSummary(summary);
if (markdown.length > 0) {
core.info(renderTokenTableAsPlainText(getSummaryTitle(), markdown));

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.

getSummaryTitle() is called twice in the same conditional block — store it in a const to avoid a redundant env read and a theoretical TOCTOU on process.env.

💡 Suggested fix
const markdown = generateTokenUsageSummary(summary);
if (markdown.length > 0) {
  const title = getSummaryTitle(); // single read
  core.info(renderTokenTableAsPlainText(title, markdown));
  await appendStepSummarySection(title, markdown);
}

In the current code, if process.env.GH_AW_TOKEN_USAGE_SUMMARY_TITLE is mutated between the two calls (e.g. by a test that doesn't reset env cleanly), the core.info title and the step-summary section title will diverge silently.

@github github deleted a comment from Jobayer-Q1 Jun 19, 2026
@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

…tion

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

Copilot AI commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

@copilot run pr-finisher skill

Done. Fixed both issues raised in the review:

  1. Separator regex (parse_token_usage.cjs): Changed /^\|-+.*-+\|$/gm/^\|(?:[-: ]+\|)+$/gm so it correctly matches separators with alignment colons like |--:|-------|------:| (commit Fix separator regex to handle alignment colons; strengthen test assertion).

  2. Test assertion (parse_token_usage.test.cjs): Replaced not.toMatch(/\|--/) with not.toMatch(/---/) — the old check was satisfied even when the separator leaked through (because the leading pipe was stripped by the next step in the chain), while the new check directly verifies no dash sequences from separator rows remain in the output.

All 23 JS tests pass locally.

@pelikhan pelikhan merged commit e004d90 into main Jun 19, 2026
14 checks passed
@pelikhan pelikhan deleted the copilot/render-token-table-to-core-info branch June 19, 2026 05:11
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.

3 participants