Skip to content
Merged
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
13 changes: 13 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"docfx": {
"version": "2.78.5",
"commands": [
"docfx"
],
"rollForward": false
}
}
}
38 changes: 38 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Bug report
description: Report a reproducible SharpClaw Code problem.
title: "[Bug]: "
labels: [bug]
body:
- type: textarea
id: description
attributes:
label: What happened?
description: Include the expected and actual behavior.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Reproduction
description: Provide the smallest safe sequence that reproduces the issue.
validations:
required: true
- type: input
id: version
attributes:
label: SharpClaw Code version or commit
validations:
required: true
- type: dropdown
id: operating-system
attributes:
label: Operating system
options: [Windows, macOS, Linux, Other]
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant logs
description: Remove prompts, tokens, keys, personal data, and machine-specific paths first.
render: shell
25 changes: 25 additions & 0 deletions .github/branch-protection.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"required_status_checks": {
"strict": true,
"contexts": [
"build-and-test (ubuntu-latest)",
"build-and-test (windows-latest)",
"build-and-test (macos-latest)",
"package-smoke (ubuntu-latest)",
"package-smoke (windows-latest)",
"package-smoke (macos-latest)",
"vscode-extension",
"documentation"
]
},
"enforce_admins": true,
"required_pull_request_reviews": null,
"restrictions": null,
"required_linear_history": true,
"allow_force_pushes": false,
"allow_deletions": false,
"block_creations": false,
"required_conversation_resolution": true,
"lock_branch": false,
"allow_fork_syncing": true
Comment thread
Telli marked this conversation as resolved.
}
17 changes: 17 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: 2
updates:
- package-ecosystem: nuget
directory: /
schedule:
interval: weekly
groups:
microsoft-dotnet:
patterns: ["Microsoft.*", "System.*"]
- package-ecosystem: npm
directory: /extensions/vscode
schedule:
interval: weekly
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
15 changes: 15 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## Summary

Describe the user-visible or architectural outcome.

## Validation

- [ ] `dotnet build SharpClawCode.sln --configuration Release --warnaserror`
- [ ] `dotnet test SharpClawCode.sln --configuration Release`
- [ ] Relevant package, CLI, MCP, plugin, and cross-platform checks were run

## Risk and compatibility

- [ ] Durable JSON/session formats remain compatible or include a migration
- [ ] Dangerous file, shell, and network operations still pass through permissions
- [ ] No credentials, local state, or generated secrets are included
33 changes: 33 additions & 0 deletions .github/scripts/Set-RepositorySecurity.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
param(
[string]$Branch = "main"
)

$ErrorActionPreference = "Stop"
$repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot "../..")).Path
$branchProtectionPath = Join-Path $repositoryRoot ".github/branch-protection.json"
$securitySettingsPath = Join-Path $repositoryRoot ".github/security-settings.json"

Push-Location $repositoryRoot
try {
$repository = (& gh repo view --json nameWithOwner --jq .nameWithOwner).Trim()
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($repository)) {
throw "Could not resolve the GitHub repository. Authenticate gh and run this script from the checkout."
}

& gh api --method PUT "repos/$repository/branches/$Branch/protection" --input $branchProtectionPath --silent
if ($LASTEXITCODE -ne 0) { throw "Could not apply branch protection to '$Branch'." }

& gh api --method PATCH "repos/$repository" --input $securitySettingsPath --silent
if ($LASTEXITCODE -ne 0) { throw "Could not apply repository security settings." }

& gh api --method PUT "repos/$repository/vulnerability-alerts" --silent
if ($LASTEXITCODE -ne 0) { throw "Could not enable vulnerability alerts." }

& gh api --method PUT "repos/$repository/automated-security-fixes" --silent
if ($LASTEXITCODE -ne 0) { throw "Could not enable Dependabot security updates." }

Write-Host "Applied branch protection and repository security settings to $repository ($Branch)."
}
finally {
Pop-Location
}
24 changes: 24 additions & 0 deletions .github/scripts/Test-Coverage.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
param(
[Parameter(Mandatory = $true)]
[string]$CoverageRoot,
[double]$MinimumLineCoverage = 38
)

$ErrorActionPreference = "Stop"
$reports = @(Get-ChildItem $CoverageRoot -Filter "coverage.cobertura.xml" -Recurse)
if ($reports.Count -eq 0) { throw "No Cobertura reports were found under '$CoverageRoot'." }

$linesCovered = 0L
$linesValid = 0L
foreach ($report in $reports) {
[xml]$coverage = Get-Content $report.FullName
$linesCovered += [long]$coverage.coverage.'lines-covered'
$linesValid += [long]$coverage.coverage.'lines-valid'
}

if ($linesValid -eq 0) { throw "Coverage reports did not contain any measurable lines." }
$percentage = 100.0 * $linesCovered / $linesValid
Write-Host ("Line coverage: {0:N2}% ({1}/{2})" -f $percentage, $linesCovered, $linesValid)
if ($percentage -lt $MinimumLineCoverage) {
throw ("Line coverage {0:N2}% is below the required {1:N2}%." -f $percentage, $MinimumLineCoverage)
}
52 changes: 52 additions & 0 deletions .github/scripts/Test-Packages.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
param(
[string]$PackageVersion = "0.1.0-preview.1",
[string]$Configuration = "Release"
)

$ErrorActionPreference = "Stop"
$repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot "../..")).Path
$scratchRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("sharpclaw-package-smoke-" + [Guid]::NewGuid().ToString("N"))
$packageOutput = Join-Path $scratchRoot "packages"
$toolPath = Join-Path $scratchRoot "tool"
$consumerPath = Join-Path $scratchRoot "consumer"
$nugetConfigPath = Join-Path $scratchRoot "NuGet.Config"

try {
New-Item -ItemType Directory -Path $packageOutput -Force | Out-Null
& dotnet pack (Join-Path $repositoryRoot "SharpClawCode.Packages.slnf") --configuration $Configuration --output $packageOutput -p:PackageVersion=$PackageVersion
if ($LASTEXITCODE -ne 0) { throw "Package creation failed." }

$packages = @(Get-ChildItem $packageOutput -Filter "*.nupkg" | Where-Object { $_.Name -notlike "*.symbols.nupkg" })
$unexpected = @($packages | Where-Object { $_.BaseName -notlike "SharpClaw.Code*" })
if ($packages.Count -ne 21) { throw "Expected 21 production packages, found $($packages.Count)." }
if ($unexpected.Count -gt 0) { throw "Unexpected packages: $($unexpected.Name -join ', ')." }

& dotnet new console --framework net10.0 --output $consumerPath --no-restore
if ($LASTEXITCODE -ne 0) { throw "Could not create package smoke consumer." }
& dotnet add (Join-Path $consumerPath "consumer.csproj") package SharpClaw.Code --version $PackageVersion --no-restore
if ($LASTEXITCODE -ne 0) { throw "Could not add the aggregate SDK package." }

[System.IO.File]::WriteAllText(
$nugetConfigPath,
'<?xml version="1.0" encoding="utf-8"?><configuration><packageSources><clear /></packageSources></configuration>')
& dotnet nuget add source $packageOutput --name sharpclaw-local --configfile $nugetConfigPath
if ($LASTEXITCODE -ne 0) { throw "Could not configure the local package source." }
& dotnet nuget add source "https://api.nuget.org/v3/index.json" --name nuget.org --configfile $nugetConfigPath
if ($LASTEXITCODE -ne 0) { throw "Could not configure the NuGet.org package source." }

& dotnet restore (Join-Path $consumerPath "consumer.csproj") --configfile $nugetConfigPath
if ($LASTEXITCODE -ne 0) { throw "Could not restore the aggregate SDK package." }
& dotnet build (Join-Path $consumerPath "consumer.csproj") --configuration $Configuration --no-restore
if ($LASTEXITCODE -ne 0) { throw "The aggregate SDK package failed to build in a clean consumer." }

& dotnet tool install --tool-path $toolPath SharpClaw.Code.Cli --version $PackageVersion --add-source $packageOutput --ignore-failed-sources
if ($LASTEXITCODE -ne 0) { throw "The CLI tool package failed to install." }
$toolExecutable = if ($IsWindows) { Join-Path $toolPath "sharpclaw.exe" } else { Join-Path $toolPath "sharpclaw" }
& $toolExecutable version
if ($LASTEXITCODE -ne 0) { throw "The installed CLI tool failed its version smoke test." }
}
finally {
if (Test-Path $scratchRoot) {
Remove-Item $scratchRoot -Recurse -Force
}
}
33 changes: 33 additions & 0 deletions .github/scripts/Test-VulnerablePackages.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
param(
[string]$Target = "SharpClawCode.sln"
)

$ErrorActionPreference = "Stop"
$reportJson = (& dotnet list $Target package --vulnerable --include-transitive --format json) -join [Environment]::NewLine
if ($LASTEXITCODE -ne 0) { throw "NuGet vulnerability inspection failed." }

$report = $reportJson | ConvertFrom-Json
$vulnerablePackages = @(
foreach ($project in @($report.projects)) {
foreach ($framework in @($project.frameworks)) {
foreach ($package in @($framework.topLevelPackages) + @($framework.transitivePackages)) {
if ($null -ne $package -and @($package.vulnerabilities).Count -gt 0) {
[PSCustomObject]@{
Project = $project.path
Framework = $framework.framework
Package = $package.id
ResolvedVersion = $package.resolvedVersion
Vulnerabilities = @($package.vulnerabilities)
}
}
}
}
}
)

if ($vulnerablePackages.Count -gt 0) {
$vulnerablePackages | ConvertTo-Json -Depth 8 | Write-Error
throw "NuGet reported $($vulnerablePackages.Count) vulnerable package occurrence(s)."
}

Write-Host "NuGet vulnerability audit passed for $($report.projects.Count) projects."
10 changes: 10 additions & 0 deletions .github/security-settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"security_and_analysis": {
"secret_scanning": {
"status": "enabled"
},
"secret_scanning_push_protection": {
"status": "enabled"
}
}
}
47 changes: 46 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- name: Restore
run: dotnet restore SharpClawCode.sln
- name: Build
run: dotnet build SharpClawCode.sln --no-restore --configuration Release
run: dotnet build SharpClawCode.sln --no-restore --configuration Release --warnaserror
- name: Build examples
run: |
dotnet build examples/WebApiAgent/WebApiAgent.csproj --no-restore --configuration Release
Expand All @@ -43,3 +43,48 @@ jobs:
with:
name: coverage-report
path: ./coverage/**/coverage.cobertura.xml
- name: Enforce coverage floor
if: matrix.os == 'ubuntu-latest'
shell: pwsh
run: ./.github/scripts/Test-Coverage.ps1 -CoverageRoot ./coverage -MinimumLineCoverage 38

package-smoke:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Pack and install production artifacts
shell: pwsh
run: ./.github/scripts/Test-Packages.ps1

vscode-extension:
runs-on: ubuntu-latest
defaults:
run:
working-directory: extensions/vscode
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
cache-dependency-path: extensions/vscode/package-lock.json
- run: npm ci
- run: npm run compile

documentation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- run: dotnet tool restore
- run: dotnet docfx docs/docfx.json --warningsAsErrors
51 changes: 51 additions & 0 deletions .github/workflows/provider-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Live provider smoke

on:
workflow_dispatch:
inputs:
provider:
description: Provider to validate
required: true
default: openai-compatible
type: choice
options:
- openai-compatible
- anthropic
model:
description: Provider model id
required: true
default: gpt-4.1-mini
type: string

permissions:
contents: read

jobs:
prompt:
runs-on: ubuntu-latest
timeout-minutes: 10
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- run: dotnet build src/SharpClaw.Code.Cli/SharpClaw.Code.Cli.csproj --configuration Release --warnaserror
- name: Select credential reference
shell: pwsh
env:
PROVIDER: ${{ inputs.provider }}
run: |
$provider = $env:PROVIDER
$variableName = if ($provider -eq 'anthropic') { 'ANTHROPIC_API_KEY' } else { 'OPENAI_API_KEY' }
if ([string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable($variableName))) {
throw "The $variableName repository secret is required for this smoke test."
}
dotnet run --project src/SharpClaw.Code.Cli/SharpClaw.Code.Cli.csproj --no-build --configuration Release -- auth set-key --provider $provider --env-var $variableName
- name: Run real provider prompt
env:
PROVIDER: ${{ inputs.provider }}
MODEL: ${{ inputs.model }}
run: dotnet run --project src/SharpClaw.Code.Cli/SharpClaw.Code.Cli.csproj --no-build --configuration Release -- --output-format json --model "$PROVIDER/$MODEL" prompt 'Reply with exactly SHARPCLAW_PROVIDER_OK.'
Loading
Loading