Skip to content
Closed
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
4 changes: 2 additions & 2 deletions .github/workflows/impeccable-skills-reviewer.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions .github/workflows/mattpocock-skills-reviewer.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions actions/setup/js/add_reaction_and_edit_comment.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -280,9 +280,7 @@ async function addCommentWithWorkflowLink(endpoint, runUrl, eventName, invocatio
const { id: discussionId } = await getDiscussionId(eventRepo.owner, eventRepo.repo, discussionNumber);
// For discussion_comment events, thread the reply under the triggering comment.
// GitHub Discussions only supports two nesting levels, so resolve the top-level parent node ID.
const replyToId = eventName === "discussion_comment"
? await resolveTopLevelDiscussionCommentId(github, eventPayload?.comment?.node_id)
: null;
const replyToId = eventName === "discussion_comment" ? await resolveTopLevelDiscussionCommentId(github, eventPayload?.comment?.node_id) : null;
const mutation = replyToId
? `mutation($dId: ID!, $body: String!, $replyToId: ID!) {
addDiscussionComment(input: { discussionId: $dId, body: $body, replyToId: $replyToId }) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/compiler_yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func (c *Compiler) generateWorkflowHeader(yaml *strings.Builder, data *WorkflowD
// Embed the gh-aw-manifest immediately after gh-aw-metadata for easy machine parsing.
// The manifest records all secrets, external actions, and container images detected at
// compile time so that subsequent compilations can perform safe update enforcement.
manifest := NewGHAWManifest(secrets, actions, data.ActionResolutionFailures, data.DockerImagePins, data.Redirect)
manifest := NewGHAWManifest(secrets, actions, data.ActionResolutionFailures, data.DockerImagePins, data.Redirect, data.Skills)
if manifestJSON, err := manifest.ToJSON(); err == nil {
fmt.Fprintf(yaml, "# gh-aw-manifest: %s\n", manifestJSON)
} else {
Expand Down
4 changes: 2 additions & 2 deletions pkg/workflow/lock_schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ func TestGenerateLockMetadataStrict(t *testing.T) {
metadata := GenerateLockMetadata(LockHashInfo{FrontmatterHash: hash}, stopTime, true, AgentMetadataInfo{})

assert.NotNil(t, metadata, "Metadata should be created")
assert.Equal(t, LockSchemaV4, metadata.SchemaVersion, "Should use v4 schema version")
assert.Equal(t, LockSchemaV4, metadata.SchemaVersion, "Should use v5 schema version")
assert.Equal(t, hash, metadata.FrontmatterHash, "Should preserve frontmatter hash")
assert.Equal(t, stopTime, metadata.StopTime, "Should preserve stop time")
assert.True(t, metadata.Strict, "Strict build should have Strict=true")
Expand Down Expand Up @@ -638,7 +638,7 @@ func TestGenerateLockMetadataWithAgentInfo(t *testing.T) {
metadata := GenerateLockMetadata(LockHashInfo{FrontmatterHash: hash}, "", false, agentInfo)

assert.NotNil(t, metadata, "Metadata should be created")
assert.Equal(t, LockSchemaV4, metadata.SchemaVersion, "Should use v4 schema version")
assert.Equal(t, LockSchemaV4, metadata.SchemaVersion, "Should use v5 schema version")
assert.Equal(t, "copilot", metadata.AgentID, "Should preserve agent ID")
assert.Equal(t, "gpt-5", metadata.AgentModel, "Should preserve agent model")
assert.Equal(t, "copilot", metadata.DetectionAgentID, "Should preserve detection agent ID")
Expand Down
7 changes: 5 additions & 2 deletions pkg/workflow/safe_update_manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,11 @@ type GHAWManifest struct {
ResolutionFailures []GHAWManifestResolutionFailure `json:"resolution_failures,omitempty"` // unresolved action-ref pinning failures
Containers []GHAWManifestContainer `json:"containers,omitempty"` // container images used, with digest when available
Redirect string `json:"redirect,omitempty"` // frontmatter redirect target for moved workflows
Skills []string `json:"skills,omitempty"` // skill dependencies declared in frontmatter
}

// NewGHAWManifest builds a GHAWManifest from the raw secret names, action reference
// strings, and container image references produced at compile time.
// strings, container image references, and skill dependencies produced at compile time.
//
// secretNames entries may include or omit the "secrets." prefix; the prefix is always
// stripped before storage so the manifest contains plain names (e.g. "GITHUB_TOKEN").
Expand All @@ -68,7 +69,8 @@ type GHAWManifest struct {
// "actions/checkout@abc1234 # v4"
//
// containers is the list of container image entries with full digest info (when available).
func NewGHAWManifest(secretNames []string, actionRefs []string, failures []GHAWManifestResolutionFailure, containers []GHAWManifestContainer, redirect string) *GHAWManifest {
// skills is the list of skill dependency references declared in frontmatter.
func NewGHAWManifest(secretNames []string, actionRefs []string, failures []GHAWManifestResolutionFailure, containers []GHAWManifestContainer, redirect string, skills []string) *GHAWManifest {
safeUpdateManifestLog.Printf("Building gh-aw-manifest: raw_secrets=%d, raw_actions=%d, containers=%d", len(secretNames), len(actionRefs), len(containers))

// Normalize secret names to full "secrets.NAME" form and deduplicate.
Expand Down Expand Up @@ -120,6 +122,7 @@ func NewGHAWManifest(secretNames []string, actionRefs []string, failures []GHAWM
ResolutionFailures: resolutionFailures,
Containers: sortedContainers,
Redirect: strings.TrimSpace(redirect),
Skills: skills,
}
}

Expand Down
37 changes: 35 additions & 2 deletions pkg/workflow/safe_update_manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func TestNewGHAWManifest(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := NewGHAWManifest(tt.secretNames, tt.actionRefs, tt.resolutionFailures, tt.containers, tt.redirect)
m := NewGHAWManifest(tt.secretNames, tt.actionRefs, tt.resolutionFailures, tt.containers, tt.redirect, nil)
require.NotNil(t, m, "manifest should not be nil")
assert.Equal(t, tt.wantVersion, m.Version, "manifest version")
if tt.wantSecrets != nil {
Expand Down Expand Up @@ -177,7 +177,7 @@ func TestNewGHAWManifestContainerDigest(t *testing.T) {
Image: "alpine:3.14", // no digest
},
}
m := NewGHAWManifest(nil, nil, nil, containers, "")
m := NewGHAWManifest(nil, nil, nil, containers, "", nil)
require.Len(t, m.Containers, 2, "should have two containers")

// Sorted: alpine before node
Expand Down Expand Up @@ -388,3 +388,36 @@ func TestParseActionRefs(t *testing.T) {
})
}
}

func TestNewGHAWManifestWithSkills(t *testing.T) {
skills := []string{
"githubnext/skills@1f181b37d3fe5862ab590648f25a292e345b5de6",
"githubnext/skills/review/security@1f181b37d3fe5862ab590648f25a292e345b5de6",
}
m := NewGHAWManifest(nil, nil, nil, nil, "", skills)
require.NotNil(t, m, "manifest should not be nil")
assert.Equal(t, skills, m.Skills, "skills should be preserved")

jsonStr, err := m.ToJSON()
require.NoError(t, err)
assert.Contains(t, jsonStr, `"skills":["githubnext/skills@1f181b37d3fe5862ab590648f25a292e345b5de6","githubnext/skills/review/security@1f181b37d3fe5862ab590648f25a292e345b5de6"]`)
}

func TestNewGHAWManifestSkillsOmittedWhenEmpty(t *testing.T) {
m := NewGHAWManifest(nil, nil, nil, nil, "", nil)
require.NotNil(t, m, "manifest should not be nil")
assert.Empty(t, m.Skills, "skills should be empty")

jsonStr, err := m.ToJSON()
require.NoError(t, err)
assert.NotContains(t, jsonStr, `"skills"`, "empty skills should be omitted")
}

func TestExtractGHAWManifestWithSkills(t *testing.T) {
content := `# gh-aw-manifest: {"version":1,"secrets":[],"actions":[],"skills":["githubnext/skills@1f181b37d3fe5862ab590648f25a292e345b5de6"]}`
m, err := ExtractGHAWManifestFromLockFile(content)
require.NoError(t, err)
require.NotNil(t, m)
require.Len(t, m.Skills, 1, "should extract one skill")
assert.Equal(t, "githubnext/skills@1f181b37d3fe5862ab590648f25a292e345b5de6", m.Skills[0])
}
2 changes: 1 addition & 1 deletion pkg/workflow/stale_check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ This is the body of the workflow. It contains the agent instructions.
require.NoError(t, err, "Lock metadata should be parseable")
require.NotNil(t, metadata, "Lock metadata should exist")

assert.Equal(t, LockSchemaV4, metadata.SchemaVersion, "Should use v4 schema with body hash")
assert.Equal(t, LockSchemaV4, metadata.SchemaVersion, "Should use v5 schema with body hash")
assert.NotEmpty(t, metadata.BodyHash, "Body hash should be stored in lock metadata")
assert.Len(t, metadata.BodyHash, 64, "Body hash should be a 64-character hex string")

Expand Down
Loading