Skip to content

Inline agentic commands router JavaScript - #52986

Closed
pelikhan with Copilot wants to merge 2 commits into
mainfrom
copilot/update-agentic-commands-compiler-support
Closed

Inline agentic commands router JavaScript#52986
pelikhan with Copilot wants to merge 2 commits into
mainfrom
copilot/update-agentic-commands-compiler-support

Conversation

Copilot AI commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

The generated agentic commands router previously required checkout and gh-aw-actions/setup steps before routing. This change embeds the tested JavaScript modules in the compiler and emits them directly into the github-script step for faster startup.

Changes

  • Compiler

    • Embed the router’s CommonJS dependency closure.
    • Bundle modules deterministically with scoped loading and caching.
    • Fail compilation when an embedded dependency is missing.
  • Generated workflow

    • Remove repository checkout and setup-action steps.
    • Inline the router in the existing actions/github-script step.
steps:
  - name: Route slash command
    uses: actions/github-script@…
    with:
      script: |
        const __ghAwModules = { /* embedded modules */ };
        const { main } = __ghAwRequire("./route_slash_command.cjs");
        await main();
  • Coverage
    • Update workflow assertions for inline execution.
    • Add coverage for bundling, dependency resolution, and missing modules.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI requested a review from pelikhan August 15, 2026 23:41
@pelikhan
pelikhan marked this pull request as ready for review August 15, 2026 23:42
Copilot AI balanced review requested due to automatic review settings August 15, 2026 23:42

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

Embeds the agentic command router directly into generated workflows, removing checkout/setup steps. The resulting script currently exceeds Linux’s per-environment-variable limit.

Changes:

  • Adds deterministic CommonJS dependency bundling.
  • Inlines the router into actions/github-script.
  • Updates generation tests and generated workflow output.
Show a summary per file
File Description
pkg/workflow/central_slash_command_workflow.go Emits the bundled router inline.
pkg/workflow/central_slash_command_workflow_test.go Updates generation assertions.
pkg/workflow/agentic_commands_script.go Implements module collection and bundling.
pkg/workflow/agentic_commands_script_test.go Tests bundling and missing dependencies.
actions/setup/js/agentic_commands_embed.go Embeds router modules.
.github/workflows/agentic_commands.yml Regenerates the inline router workflow.

Review details

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 5/6 changed files
  • Comments generated: 1
  • Review effort level: Balanced

const { main } = require('` + SetupActionDestination + `/route_slash_command.cjs');
await main();
`)
WriteJavaScriptToYAML(&b, agenticCommandsScript)
@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

🔎 Code quality review by PR Code Quality Reviewer

@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

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

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

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

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Ponytail Reviewer completed successfully!

Generated by Ponytail Reviewer for #52986

@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

Test Quality Sentinel skipped because pre-fetch PR data was unavailable: unable to fetch test file diff

🧪 Test quality analysis by Test Quality Sentinel

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot create integration tests that "render" the JavaScript and run it.

@github-actions

Copy link
Copy Markdown
Contributor

Comment Memory

reviewed_at: 2026-08-16T00:00:00Z
review_event: REQUEST_CHANGES
top_themes:
  - oversized inline github-script payload risks runner input/env limits
files_reviewed:
  - actions/setup/js/agentic_commands_embed.go
  - pkg/workflow/agentic_commands_script.go
  - pkg/workflow/agentic_commands_script_test.go
  - pkg/workflow/central_slash_command_workflow.go
  - pkg/workflow/central_slash_command_workflow_test.go
comment_count: 0

Note

This comment is managed by comment memory.

It stores persistent context for this thread in the code block at the top of this comment.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

🔎 Code quality review by PR Code Quality Reviewer · gpt54 · 9.77 AIC · ⌖ 6.23 AIC · ⊞ 6.9K ·
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.

REQUEST_CHANGES

The inline router approach still introduces a blocking runtime risk: it moves several thousand lines of JavaScript into the actions/github-script with.script input, which is passed through step input/environment plumbing with hard size limits on hosted runners. This has already produced a ~215 KB generated workflow, so the design is still too close to platform limits to be safe.

Blocking theme
  • Inlining the full CommonJS closure into github-script trades checkout/setup latency for a much larger workflow payload and step input.
  • The current tests only assert textual embedding, not whether the generated step remains within GitHub Actions' practical input/env limits.
  • A safer design needs either a much smaller bootstrap script or an execution path that does not depend on shoving the whole router through with.script.

🔎 Code quality review by PR Code Quality Reviewer · gpt54 · 9.77 AIC · ⌖ 6.23 AIC · ⊞ 6.9K
Comment /review to run again

…avaScript

Documents the architectural decision to embed JS modules via Go's embed
directive and inline the router bundle into the generated github-script step,
eliminating the checkout + setup-action steps from generated workflows.

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 (181 new lines in pkg/ and actions/) but did not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/52986-inline-agentic-commands-router-javascript.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 couldn't 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-52986: Inline Agentic Commands Router JavaScript

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'll 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., 0042-use-postgresql.md for PR #42).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · sonnet46 · 96.6 AIC · ⌖ 24.9 AIC · ⊞ 9K ·
Comment /review to run again

@pelikhan pelikhan closed this Aug 16, 2026
Copilot stopped work on behalf of pelikhan due to an error August 16, 2026 00:04

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

Ponytail pass: one double-pass of comment stripping and a redundant line-trim loop can go.

net: -8 lines possible.

Generated by ✂️ Ponytail Reviewer for #52986 · auto · 42.9 AIC · ⌖ 6 AIC · ⊞ 7.2K
Comment /ponytail to run again

script.WriteString(`
await __ghAwMain();
`)
return trimAgenticCommandsScript(removeJavaScriptComments(script.String())), nil

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.

L89: delete: bundleAgenticCommandsScript strips comments via removeJavaScriptComments, then WriteJavaScriptToYAML (central_slash_command_workflow.go) strips comments again on the same string. Drop the inner call, comment removal happens once at the YAML-write step.

return trimAgenticCommandsScript(removeJavaScriptComments(script.String())), nil
}

func trimAgenticCommandsScript(script string) 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.

L92-99: yagni: trimAgenticCommandsScript re-walks every line to trim trailing whitespace, but WriteJavaScriptToYAML already skips/rewrites each line when inlining to YAML. Drop this pass.

@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 /codebase-design and /tdd — requesting changes on two correctness risks and two test gaps.

⚠️ The diff was truncated at 3000 lines (the PR adds 3,506 lines to agentic_commands.yml alone). This review focuses on the Go source files, which were read directly from the repository. The generated YAML was not reviewed in detail.

📋 Key Themes & Highlights

Issues Found

  1. Circular dependency → infinite recursion (agentic_commands_script.go:100) — collectAgenticCommandsModules has no cycle guard. Two mutually-requiring modules will overflow the stack.
  2. sync.Once caches failures (agentic_commands_script.go:24) — a transient error on the first call permanently poisons getAgenticCommandsScript() for the process lifetime. Add a comment if this is intentional (i.e., the only failure mode is a build-time missing embed).
  3. (go/redacted):embed explicit list is fragile (agentic_commands_embed.go:8) — new .cjs modules added to the directory won't be embedded automatically; consider *.cjs wildcard.
  4. TestGetAgenticCommandsScript shares package-level Once state — call bundleAgenticCommandsScript(setupjs.AgenticCommandsScripts, ...) directly to avoid test-ordering dependencies.

Positive Highlights

  • ✅ Deterministic module ordering via sort.Strings — reproducible generated YAML.
  • ✅ The __ghAwRequire shim is clean and well-scoped; module caching via __ghAwModuleCache avoids redundant execution.
  • ✅ Path traversal guard in collectAgenticCommandsModules (.. prefix check) is a good defensive touch.
  • removeJavaScriptComments applied before output keeps the inlined bundle readable and compact.
  • ✅ Good test coverage for the happy path and missing-dependency error.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 64.2 AIC · ⌖ 9.54 AIC · ⊞ 7.7K
Comment /matt to run again

)

func getAgenticCommandsScript() (string, error) {
agenticCommandsScriptOnce.Do(func() {

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] sync.Once caches both success and failure permanently — if bundling fails once (e.g., due to a missing embedded file in a test build), all subsequent calls in the same process return the same error, with no recovery path.

💡 Suggested fix

Either use sync.OnceValues (Go 1.21+) which has the same semantics but is cleaner, or — if one-time caching of errors is undesirable — protect with a sync.Mutex and only cache on success:

var (
    agenticCommandsScriptOnce sync.Once
    agenticCommandsScript     string
    agenticCommandsScriptErr  error
)

The current design is fine if the only failure mode is a broken binary (missing (go/redacted):embed file), which would fail at compile time anyway. If that's the guarantee, add a comment explaining that the Once caches errors intentionally because a missing embed is always a fatal build error.

@copilot please address this.

}
return trimmed.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.

[/codebase-design] collectAgenticCommandsModules has no circular dependency guard. If module A requires module B and module B requires module A, this recurses infinitely and panics with a stack overflow.

💡 Suggested fix

Add an in-progress set alongside the modules map:

func collectAgenticCommandsModules(scripts fs.FS, moduleName string, modules map[string]string) error {
    moduleName = path.Clean(strings.TrimPrefix(moduleName, "./"))
    // ... path validation ...
    if _, exists := modules[moduleName]; exists {
        return nil // already collected
    }
    // Mark as in-progress *before* recursing to break cycles
    modules[moduleName] = "" // placeholder
    content, err := fs.ReadFile(scripts, moduleName)
    if err != nil { ... }
    modules[moduleName] = string(content)
    // ... recurse into dependencies ...
}

The placeholder "" entry in the map will short-circuit any back-edge before the real content is assigned. This is a minimal change that eliminates the cycle risk without additional data structures.

@copilot please address this.


_, err := bundleAgenticCommandsScript(scripts, "main.cjs")
require.ErrorContains(t, err, `failed to read agentic commands module "missing.cjs"`)
}

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] No test covers circular dependency detection. The current implementation will infinitely recurse if two bundled modules require each other — a test should document this expectation before the guard is (or isn't) added.

💡 Suggested test
func TestBundleAgenticCommandsScriptFailsForCircularDependency(t *testing.T) {
    scripts := fstest.MapFS{
        "a.cjs": {Data: []byte(`require("./b.cjs");`)},
        "b.cjs": {Data: []byte(`require("./a.cjs");`)},
    }
    _, err := bundleAgenticCommandsScript(scripts, "a.cjs")
    // Either succeeds (if cycles are harmless by design) or returns a
    // descriptive error — either way the behaviour is explicit.
    _ = err
}

Even if circular deps cannot appear in practice with the current JS modules, the test pins the contract and prevents future regressions.

@copilot please address this.

// AgenticCommandsScripts contains the JavaScript modules used by the generated
// agentic commands router.
//
//go:embed route_slash_command.cjs add_reaction.cjs add_workflow_run_comment.cjs

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] The (go/redacted):embed directives in agentic_commands_embed.go list every .cjs file explicitly. If a new module is added to actions/setup/js/ but forgotten here, the compiler silently produces a broken bundle at runtime (missing module error). An explicit (go/redacted):embed *.cjs wildcard would auto-include all current and future modules.

💡 Suggested fix
(go/redacted):embed *.cjs
var AgenticCommandsScripts embed.FS

This is safe because the bundler's collectAgenticCommandsModules only loads files reachable from the entrypoint — unused .cjs files in the FS are simply ignored. The wildcard makes the embed self-maintaining.

The explicit listing does have one advantage: it documents intent. If the explicit list is intentional (to restrict which files are embedded), add a comment explaining the policy.

@copilot please address this.


func TestGetAgenticCommandsScript(t *testing.T) {
script, err := getAgenticCommandsScript()
require.NoError(t, err)

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] TestGetAgenticCommandsScript uses getAgenticCommandsScript() which is memoised with sync.Once at package level. If this test runs after a previous test that caused the Once to fire (e.g., in the same process), the result is already cached and the test isn't exercising a fresh call. Consider either resetting the once between tests or testing bundleAgenticCommandsScript directly (passing setupjs.AgenticCommandsScripts explicitly) to keep the test deterministic and isolated.

@copilot please address this.

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

The inlined JS bundle faithfully mirrors the source modules in actions/setup/js/. No blocking issues found:

  • removeXmlComments depth-tracking is intentional security hardening (prevents bypass via <!-- <!-- --> PAYLOAD -->) and matches the documented behavior in the source file.
  • neutralizeAllMentions is defined in sanitize_content_core.cjs and called internally by sanitizeContentCore, which is invoked from sanitize_content.cjs when no allowedAliases are configured — it is not dead code.
  • The 220 KB workflow file size is fine; github-script reads the script: block from the checked-out file, not from an environment variable, so there is no size constraint here.

The approach (removing the checkout + setup dependency by inlining the CommonJS bundle) is clean and the bundler output is deterministic.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 124.3 AIC · ⌖ 7.92 AIC · ⊞ 5.6K

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