Skip to content

Commit f071e74

Browse files
huimiuCopilotCopilotwbreza
authored
feat(skills): add azd ai skill command group (#8224)
* feat(skills): add `azd ai skill` command group (preview) Introduces a new standalone `azure.ai.skills` extension that exposes `azd ai skill create | update | show | list | download | delete` for managing Foundry Skills from any directory. Implements the design in PR #8204 / issue #8142: - New extension under `cli/azd/extensions/azure.ai.skills` (namespace `ai.skill`, id `azure.ai.skills`, version `0.0.1-preview`). - Typed Foundry Skills data-plane client in `internal/pkg/skill_api` with SKILL.md YAML front matter parser and two-phase safe gzip-tar extractor (zip-slip guard, no symlinks / hard links, 10,000-entry / 512 MB caps, staging + atomic copy, `--force` for collisions). - Three mutually exclusive `create` modes: inline (`--description` + `--instructions`), `--file SKILL.md` (parsed locally), and `--file *.tar.gz` / `*.tgz` (streamed as `application/gzip` to `POST /skills:import`). `--force` does delete-then-create. - `update` accepts inline flags or `--file *.md` only; gzip is rejected with a structured suggestion to use `create --force`. - `download` extracts by default into `./.agents/skills/<name>/` and supports `--raw` to write the unmodified archive. - `delete` confirms by default; `--force` skips, and `--no-prompt` without `--force` errors. Interactive `n` returns exit 0. - Endpoint resolution shares the 5-level cascade with `azure.ai.agents` via a read-only fallback to `extensions.ai-agents.project.context.endpoint` when the new `extensions.ai-skills.project.context.endpoint` key is unset. - Bearer scope `https://ai.azure.com/.default`, `Foundry-Features: Skills=V1Preview` header, API version `2025-11-15-preview`. Debug log file `azd-ai-skills-<date>.log`. `IncludeBody` opt-out on the HTTP pipeline until a sanitizer for `description` / `instructions` lands. - Skill name regex aligned with `agent_yaml.ValidateAgentName`: `^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\$`. Closes #8142. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(azure.ai.skills): endpoint scheme validation + symlink escape in SafeExtract P1 - Endpoint validation (Finding 1): - Add validateEndpoint() to endpoint.go that rejects non-https URLs and empty hosts before sending Azure bearer tokens to any resolved endpoint. - Called at all five resolution levels (flag, azd env, global config x2, host env var) so misconfigured endpoints fail with a clear message rather than an opaque SDK error. - Host-suffix validation intentionally deferred to HTTP layer per design. - Add TestResolveProjectEndpoint_InvalidScheme covering http/ftp/no-scheme/empty-host cases. P1 - Symlink escape in SafeExtract (Finding 2): - archive.go copy phase was vulnerable: if OutputDir contained a pre-existing symlink to a directory outside OutputDir, MkdirAll + copyFile would follow it silently, writing extracted files outside the intended destination. - Fix: resolve OutputDir with EvalSymlinks once; before each copyFile resolve the destination directory and assert it is under the real output dir. - Add isUnder() helper for path containment check. - Add TestSafeExtract_RejectsSymlinkParentEscape (skipped on Windows). * fix(skills): use api-version=v1 for skills surface The Skills data-plane lives under api-version=v1; the preview opt-in is communicated via the Foundry-Features: Skills=V1Preview header. Using 2025-11-15-preview as the api-version returns 400 UnsupportedApiVersion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(skills): pre-check has_blob before download for clearer error Skills created from inline JSON or SKILL.md have no downloadable package; the server returns an opaque 404 'does not have an associated package'. Pre-flight Get the skill so the download command can return a structured CodeSkillNoPackage validation error with an actionable suggestion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(skills): switch from gzip+tar to ZIP for package upload/download The live Foundry Skills service implements POST /skills:import and GET /skills/{name}:download with application/zip, not application/gzip as the upstream TypeSpec declares. Verified via 415 Unsupported Media Type on gzip uploads. Public docs confirm: https://learn.microsoft.com/azure/foundry/agents/how-to/tools/skills Changes: - skill_api: replace archive/tar+compress/gzip with archive/zip - skill_api: Download now returns []byte (archive/zip needs io.ReaderAt) - skill_api: rename ContentTypeGzip -> ContentTypeZip, ErrInvalidGzip -> ErrInvalidZip - cmd: accept '.zip' for --file; reject '.tar.gz'/'.tgz' - cmd: writeRaw now writes '<name>.zip' - tests: rewrite archive_test.go and archive_peek_test.go for ZIP - docs (AGENTS.md, README.md, CHANGELOG.md): s/gzip,tar.gz/zip/g The design spec (PR #8204) will need a follow-up to match. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(skills): auto-detect ZIP vs gzip on download (service is asymmetric) The Foundry Skills surface is asymmetric on archive format: - POST /skills:import requires application/zip (gzip yields 415) - GET /skills/{name}:download returns application/gzip Make SafeExtract sniff magic bytes (PK or 1f 8b) and dispatch to either the zip or the gzip+tar handler. Download() now accepts both Content-Type values. The raw download filename uses .zip or .tar.gz based on the detected format. Also: - Add DetectArchiveFormat() public helper for callers that need the format (e.g. the --raw filename picker). - Add gitignore for local test artifacts (zips, tar.gz, debug logs). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor(skills): trim unnecessary comments and doc blocks Remove restating-the-code comments, narrative section headers, and verbose struct-field docs. Keep design-rationale notes (TypeSpec/docs mismatch on archive format, --force destructive sequence, defensive path validation) and important safety annotations. Net: -396 lines of comments, no behavior change. * fix(azure.ai.skills): drop unused scanner, stream archive peek, reject symlinked copy destinations, use errors.AsType * fix(skills): address PR feedback on download/update help text - skill_download: drop 're-create with create --force' hint from the no-package error; users downloading the skill don't have it locally to re-create. - skill_update: in 'update' long help, route ZIP package updates to 'create --force' and call out that skills are not versioned so it's a destructive (delete-then-recreate) path. * fix(skills): restore context.go and metadata.go; rename skill_context.go Restore the original context.go and metadata.go files that were deleted, to respect the existing template structure. Rename skill_context.go to skill_client.go to avoid naming overlap with the restored context command. * test(skills): cover archive ext, extract error mapping, delete preflight, exterrors Add unit tests for previously uncovered pure-logic helpers: - cmd/skill_download_test.go: archiveExtension format mapping, classifyExtractError sentinel-to-LocalError translation (unsafe/limit/collision/invalid + pass-through), and downloadAction.Run name validation short-circuit. - cmd/skill_delete_test.go: deleteAction.Run rejects invalid names early and surfaces CodeMissingForceFlag when --no-prompt is set without --force. - cmd/skill_validate_test.go: extend TestIsNotFound to cover real *azcore.ResponseError (404 vs 500, wrapped). - exterrors/errors_test.go: validation/dependency/auth factories and ServiceFromAzure (wraps *azcore.ResponseError, pass-through, nil); package now at 100% coverage. * fix(skills): use slices.Contains for traversal segment check go fix -diff was reporting two conflicting suggestions on the same loop (slicescontains and stringsseq), causing the analyzer to exit non-zero. With bash -e in the lint-go workflow that non-zero exit short-circuited the script, breaking CI even though there was no real diff to print. Replacing the manual loop with slices.Contains resolves the conflict so go fix -diff exits 0. * fix(skills): address PR feedback - bugs, security hardening, and tests Co-authored-by: wbreza <6540159+wbreza@users.noreply.github.com> * feat(skills): materialize SKILL.md for blob-less downloads Address PR #8224 review feedback from @therealjohn: `azd ai skill download` previously errored when a skill had no uploaded archive (`!hasBlob`). The expected behavior is for `download` to always produce a file on disk for a single skill. When the skill has no archive blob, materialize a `SKILL.md` file into --output-dir from the metadata returned by `GET /skills/{name}` (name, description, metadata, instructions). The existing archive path is unchanged. `--raw` is rejected for blob-less skills since there is no archive to write. - Add `Instructions` field to the `Skill` model so it round-trips through the wire decode. - Add `MarshalSkillMd` with stable metadata ordering and a round-trip test. - Remove the now-unused `CodeSkillNoPackage` error code. * refactor(skills): rename skill_client.go to skill_context.go Address PR #8224 review feedback from @trangevi: the file contains the `skillContext` struct and `resolveSkillContext` helper, so `skill_context.go` is a more descriptive name than `skill_client.go`. The existing `context.go` template command is unaffected — Go only requires file names within a package to be unique, which they are. * refactor(skills): align with versioned Skills API spec Update the azure.ai.skills extension to the versioned Skills API introduced in azure-rest-api-specs#43283. API surface changes: - Skill: { id, name, description, default_version, latest_version, created_at } (no more inline instructions/metadata/has_blob on the skill itself). - SkillVersion / SkillInlineContent: new types backing the new POST /skills/{name}/versions create endpoint (JSON or multipart). - Routes: - create : POST /skills/{name}/versions (was POST /skills, POST /skills:import) - update : POST /skills/{name}/versions for new default version, POST /skills/{name} for repoint via --set-default-version (was POST /skills/{name} with full payload) - delete : DELETE /skills/{name} (response now includes id) - list : GET /skills (envelope unchanged) - show : GET /skills/{name} (envelope changed) - download: GET /skills/{name}/content (was /skills/{name}:download); new --version flag → /skills/{name}/versions/{version}/content - Name validation: lowercase-only, ^[a-z0-9]([a-z0-9-]*[a-z0-9])?\$, max 64 (agentskills.io spec via SkillName scalar). - Preview opt-in (Foundry-Features: Skills=V1Preview) and api-version=v1 remain unchanged. CLI surface preserved: create / update / show / list / download / delete. Inline + SKILL.md modes wrap content in inline_content JSON; ZIP create uses multipart/form-data with a single files[] part. Update --file .zip is still rejected with a pointer to create --force. Drop the inline-skill → SKILL.md materialization fallback in download (MarshalSkillMd) since the server always returns application/zip now. * fix(skills): address PR review findings - CHANGELOG: remove duplicated # Release History header block. - ci-build.ps1: read version.txt from the extension directory; the parent `cli/azd/extensions/version.txt` does not exist, so the default `Get-Content` call would fail when `-Version` is not passed explicitly. - skill_create: extend `--force` name-mismatch refusal to SKILL.md inputs. Previously only ZIP packages were peeked before the destructive delete; a typo with `--file SKILL.md --force` could delete an unrelated skill, then warn after the fact. Now both modes share a `verifyFileNameMatches` pre-check. - skill_api/client: cap `downloadContent` at 512 MiB on the wire to bound memory before extraction enforces its own uncompressed cap. Fast-fails on `Content-Length` and also bounds streaming reads via `io.LimitReader` so a malicious or runaway server cannot exhaust process memory. - Tests: cover SKILL.md `--force` mismatch, `--force` allow on no-name SKILL.md, inline mode bypass, and three new client-level cases (oversize Content-Length, oversize streaming body, acceptance at the limit). --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: wbreza <6540159+wbreza@users.noreply.github.com>
1 parent b429308 commit f071e74

46 files changed

Lines changed: 4749 additions & 53 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Local test artifacts
2+
SKILL.md
3+
test-skill/
4+
test-min/
5+
test-*/
6+
*.zip
7+
*.tar.gz
8+
azd-ai-skills-*.log
9+
bin/
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
version: "2"
2+
3+
linters:
4+
default: none
5+
enable:
6+
- gosec
7+
- lll
8+
- unused
9+
- errorlint
10+
settings:
11+
lll:
12+
line-length: 220
13+
tab-width: 4
14+
15+
formatters:
16+
enable:
17+
- gofmt
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Azure AI Skills Extension - Agent Instructions
2+
3+
Use this file together with `cli/azd/AGENTS.md`. This guide supplements the root azd instructions with the conventions that are specific to this extension.
4+
5+
## Overview
6+
7+
`azure.ai.skills` is a first-party azd extension under `cli/azd/extensions/azure.ai.skills/`. It runs as a separate Go binary and talks to the azd host over gRPC. It exposes the `azd ai skill <verb>` command group for managing Foundry Skills.
8+
9+
Useful places to start:
10+
11+
- `internal/cmd/`: Cobra commands and top-level orchestration
12+
- `internal/pkg/skill_api/`: typed Foundry Skills REST client, models, SKILL.md parser, and safe ZIP extractor
13+
- `internal/exterrors/`: structured error factories and extension-specific codes
14+
15+
## Relationship to `azure.ai.agents`
16+
17+
This extension is intentionally separate from `azure.ai.agents`. It shares no code symbols but cooperates with it via the global-config endpoint key:
18+
19+
- This extension writes to `extensions.ai-skills.project.context.endpoint` (none yet — read-only today).
20+
- This extension reads `extensions.ai-skills.project.context.endpoint` first, then falls back to `extensions.ai-agents.project.context.endpoint` so users who already configured the endpoint via the agents extension are not forced to re-run `set`.
21+
22+
`AgentCardSkill` (in `azure.ai.agents`) is unrelated to the `Skill` resource managed here and lives in a different Go module.
23+
24+
## Build and test
25+
26+
From `cli/azd/extensions/azure.ai.skills`:
27+
28+
```bash
29+
# Build using developer extension (for local development)
30+
azd x build
31+
32+
# Or build using Go directly
33+
go build
34+
```
35+
36+
If extension work depends on a new azd core change, plan for two PRs:
37+
38+
1. Land the core change in `cli/azd` first.
39+
2. Land the extension change after that, updating this module to the newer azd dependency with `go get github.com/azure/azure-dev/cli/azd && go mod tidy`.
40+
41+
For local development, draft work, or validating both sides together before the core PR is merged, you may temporarily add:
42+
43+
```go
44+
replace github.com/azure/azure-dev/cli/azd => ../../
45+
```
46+
47+
That `replace` points this extension at your local `cli/azd` checkout instead of the version in `go.mod`. Do not merge the extension with that `replace` still present.
48+
49+
## Error handling
50+
51+
This extension uses `internal/exterrors` so the azd host can show a useful message, attach an optional suggestion, and emit stable telemetry. See `cli/azd/extensions/azure.ai.agents/AGENTS.md` "Error handling" section for the full conventions — they apply here unchanged.
52+
53+
Skill-specific error codes live in `internal/exterrors/codes.go`:
54+
55+
- `CodeInvalidSkillName` — name fails the alphanumeric-with-hyphens regex
56+
- `CodeInvalidSkillFile` — SKILL.md front matter unparsable, or `--file` extension unsupported
57+
- `CodeSkillArchiveUnsafe``download` rejected an archive entry (zip-slip, symlink, oversized, etc.)
58+
- `CodeSkillOutputCollision``download` would overwrite an existing file without `--force`
59+
60+
## Debug logging
61+
62+
Each `--debug` run writes to `azd-ai-skills-<date>.log` in the current working directory. The `skill_api` client deliberately opts out of `IncludeBody` request/response logging until a sanitizer is in place that redacts user-authored `description` and `instructions` fields. Do not enable body logging without that sanitizer.
63+
64+
## File handling
65+
66+
- `--file` is **not** a manifest. It is read at invocation time only; the CLI does not track or re-read it after the command returns.
67+
- `create`: accepts `.md` or `.zip`. Mode is inferred from extension; conflicting modes (inline + `--file`) are rejected. `.md` and inline modes send `inline_content` JSON; `.zip` is uploaded as `multipart/form-data` with a single `files[]` part.
68+
- `update`: accepts `.md` only. `.zip` is rejected with a structured suggestion to use `create --force` (which deletes the skill and all its versions before re-creating). Pass `--set-default-version <ver>` to repoint `default_version` at an existing immutable version without uploading new content.
69+
- `download`: writes either an extracted directory (default) or the unmodified zip archive (`--raw`). Pass `--version <ver>` to download a non-default version. The server always returns `application/zip` (from `GET /skills/{name}/content` or `GET /skills/{name}/versions/{version}/content`).
70+
71+
## Versioning
72+
73+
Skill versions are immutable. The Skill resource itself only carries
74+
`id`, `name`, `description`, `default_version`, `latest_version`, and
75+
`created_at`; per-version content lives in `inline_content` (or uploaded
76+
files) on each `SkillVersion`.
77+
78+
## Release preparation
79+
80+
Follows the same two-PR convention as `azure.ai.agents`: a version-bump PR that touches only `version.txt`, `extension.yaml`, and `CHANGELOG.md`, followed by a registry-update PR generated by `azd x publish` against the released artifacts.
Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
11
# Release History
22

3-
## 0.0.1-preview - Initial Version
3+
## 0.0.1-preview (Unreleased)
4+
5+
- Initial preview release of the `azure.ai.skills` extension.
6+
- Adds the `azd ai skill` command group on top of the versioned Foundry
7+
Skills API (`Foundry-Features: Skills=V1Preview`):
8+
- `azd ai skill create <name>` — creates a skill and uploads its first
9+
default version via `POST /skills/{name}/versions`. Modes: inline
10+
(`--description` + `--instructions`), SKILL.md file (`--file ./SKILL.md`),
11+
or ZIP package via `multipart/form-data` (`--file ./skill.zip`).
12+
- `azd ai skill update <name>` — uploads a new default version using the
13+
same inline / SKILL.md modes; ZIP is rejected with a pointer to
14+
`create --force`. Pass `--set-default-version <ver>` to repoint
15+
`default_version` at an existing version without uploading new content
16+
(`POST /skills/{name}`).
17+
- `azd ai skill show <name>` — returns `Skill { id, name, description,
18+
default_version, latest_version, created_at }`.
19+
- `azd ai skill list` — paginated, supports `--top` and `--orderby`.
20+
- `azd ai skill download <name>` — downloads the zip content from
21+
`GET /skills/{name}/content`, or `GET /skills/{name}/versions/{version}/content`
22+
when `--version` is passed. Extracts into `./.agents/skills/<name>/` by
23+
default; `--raw` writes the unmodified zip archive.
24+
- `azd ai skill delete <name>` — confirmation by default, `--force` to skip.
25+
- Skill names follow the agentskills.io spec:
26+
`^[a-z0-9]([a-z0-9\-]*[a-z0-9])?$`, max 64 chars (lowercase only).
27+
- Shares the Foundry project-endpoint resolution cascade with `azure.ai.agents`,
28+
reading `extensions.ai-skills.project.context.endpoint` first and falling
29+
back to `extensions.ai-agents.project.context.endpoint`.
Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,82 @@
1-
# Foundry Skills
1+
# Azure Developer CLI (azd) Skills Extension
22

3-
Manage Microsoft Foundry Skills from your terminal. (Preview)
3+
Manage [Microsoft Foundry](https://learn.microsoft.com/azure/ai-services/) **skills**
4+
(reusable behavioral guidelines an agent can attach at runtime) directly from your
5+
terminal.
6+
7+
## Commands
8+
9+
```bash
10+
azd ai skill create <name> [--description "..." --instructions "..."]
11+
azd ai skill create <name> --file ./SKILL.md
12+
azd ai skill create <name> --file ./skill.zip
13+
14+
azd ai skill update <name> [--description "..."] [--instructions "..."] [--file ./SKILL.md]
15+
azd ai skill update <name> --set-default-version <version>
16+
azd ai skill show <name>
17+
azd ai skill list [--top N] [--orderby <field>]
18+
azd ai skill download <name> [--version <ver>] [--output-dir <path>] [--raw] [--force]
19+
azd ai skill delete <name> [--force]
20+
```
21+
22+
Skills are **versioned and immutable**. `create` uploads the first default
23+
version; `update` uploads a new default version (or, with
24+
`--set-default-version`, just repoints `default_version` at an existing
25+
version). Names follow the agentskills.io spec
26+
(`^[a-z0-9]([a-z0-9\-]*[a-z0-9])?$`, max 64 chars).
27+
28+
All commands accept the standard cross-cutting flags: `-p` / `--project-endpoint`,
29+
`--output table|json`, `--no-prompt`, and `--debug`.
30+
31+
## Project endpoint resolution
32+
33+
The Foundry project endpoint is resolved in this order:
34+
35+
1. `-p` / `--project-endpoint` flag on the command.
36+
2. Active azd env value `AZURE_AI_PROJECT_ENDPOINT`.
37+
3. Global config `extensions.ai-skills.project.context.endpoint`
38+
(falls back to `extensions.ai-agents.project.context.endpoint` so users who
39+
configured the endpoint via the agents extension are not forced to re-run `set`).
40+
4. Host environment variable `FOUNDRY_PROJECT_ENDPOINT`.
41+
5. Structured error with an actionable suggestion.
42+
43+
## Local Development
44+
45+
### Prerequisites
46+
47+
1. **Install developer kit extension** (if not already installed):
48+
49+
```bash
50+
azd ext install microsoft.azd.extensions
51+
```
52+
53+
### Building and installing locally
54+
55+
1. **Navigate to the extension directory**:
56+
57+
```bash
58+
cd cli/azd/extensions/azure.ai.skills
59+
```
60+
61+
2. **Initial setup** (first time only):
62+
63+
```bash
64+
azd x build
65+
azd x pack
66+
azd x publish
67+
```
68+
69+
3. **Install the extension**:
70+
71+
```bash
72+
azd ext install azure.ai.skills
73+
```
74+
75+
4. **For subsequent development** (after initial setup):
76+
77+
```bash
78+
azd x watch
79+
```
80+
81+
This automatically watches for file changes, rebuilds, and installs updates
82+
locally.

cli/azd/extensions/azure.ai.skills/build.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ else {
4141
)
4242
}
4343

44-
$APP_PATH = "$env:EXTENSION_ID/internal/cmd"
44+
$VERSION_PATH = "azureaiskills/internal/version"
4545

4646
# Loop through platforms and build
4747
foreach ($PLATFORM in $PLATFORMS) {
@@ -65,7 +65,7 @@ foreach ($PLATFORM in $PLATFORMS) {
6565
$env:GOARCH = $ARCH
6666

6767
go build `
68-
-ldflags="-X '$APP_PATH.Version=$env:EXTENSION_VERSION' -X '$APP_PATH.Commit=$COMMIT' -X '$APP_PATH.BuildDate=$BUILD_DATE'" `
68+
-ldflags="-X '$VERSION_PATH.Version=$env:EXTENSION_VERSION' -X '$VERSION_PATH.Commit=$COMMIT' -X '$VERSION_PATH.BuildDate=$BUILD_DATE'" `
6969
-o $OUTPUT_NAME
7070

7171
if ($LASTEXITCODE -ne 0) {

cli/azd/extensions/azure.ai.skills/build.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ else
3333
)
3434
fi
3535

36-
APP_PATH="$EXTENSION_ID/internal/cmd"
36+
VERSION_PATH="azureaiskills/internal/version"
3737

3838
# Loop through platforms and build
3939
for PLATFORM in "${PLATFORMS[@]}"; do
@@ -53,7 +53,7 @@ for PLATFORM in "${PLATFORMS[@]}"; do
5353

5454
# Set environment variables for Go build
5555
GOOS=$OS GOARCH=$ARCH go build \
56-
-ldflags="-X '$APP_PATH.Version=$EXTENSION_VERSION' -X '$APP_PATH.Commit=$COMMIT' -X '$APP_PATH.BuildDate=$BUILD_DATE'" \
56+
-ldflags="-X '$VERSION_PATH.Version=$EXTENSION_VERSION' -X '$VERSION_PATH.Commit=$COMMIT' -X '$VERSION_PATH.BuildDate=$BUILD_DATE'" \
5757
-o "$OUTPUT_NAME"
5858

5959
if [ $? -ne 0 ]; then

cli/azd/extensions/azure.ai.skills/ci-build.ps1

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ param(
66
[string] $MSYS2Shell, # path to msys2_shell.cmd
77
[string] $OutputFileName
88
)
9-
109
$PSNativeCommandArgumentPassing = 'Legacy'
1110

1211
# Remove any previously built binaries
@@ -19,24 +18,50 @@ if ($LASTEXITCODE) {
1918

2019
# Run `go help build` to obtain detailed information about `go build` flags.
2120
$buildFlags = @(
21+
# remove all file system paths from the resulting executable.
22+
# Instead of absolute file system paths, the recorded file names
23+
# will begin either a module path@version (when using modules),
24+
# or a plain import path (when using the standard library, or GOPATH).
2225
"-trimpath",
26+
27+
# Use buildmode=pie (Position Independent Executable) for enhanced security across platforms
28+
# against memory corruption exploits across all major platforms.
29+
#
30+
# On Windows, the -buildmode=pie flag enables Address Space Layout
31+
# Randomization (ASLR) and automatically sets DYNAMICBASE and HIGH-ENTROPY-VA flags in the PE header.
2332
"-buildmode=pie"
2433
)
2534

2635
if ($CodeCoverageEnabled) {
2736
$buildFlags += "-cover"
2837
}
2938

39+
# Build constraint tags
40+
# cfi: Enable Control Flow Integrity (CFI),
41+
# cfg: Enable Control Flow Guard (CFG),
42+
# osusergo: Optimize for OS user accounts
3043
$tagsFlag = "-tags=cfi,cfg,osusergo"
3144

32-
$ldFlag = "-ldflags=-s -w -X azure.ai.skills/internal/cmd.Version=$Version -X azure.ai.skills/internal/cmd.Commit=$SourceVersion -X azure.ai.skills/internal/cmd.BuildDate=$(Get-Date -Format o) "
45+
# ld linker flags
46+
# -s: Omit symbol table and debug information
47+
# -w: Omit DWARF symbol table
48+
# -X: Set variable at link time. Used to set the version in source.
49+
50+
$ldFlag = "-ldflags=-s -w -X 'azureaiskills/internal/version.Version=$Version' -X 'azureaiskills/internal/version.Commit=$SourceVersion' -X 'azureaiskills/internal/version.BuildDate=$(Get-Date -Format o)' "
3351

3452
if ($IsWindows) {
3553
$msg = "Building for Windows"
3654
Write-Host $msg
3755
}
3856
elseif ($IsLinux) {
3957
Write-Host "Building for linux"
58+
59+
# Disable cgo in the x64 Linux build. This will also statically
60+
# link the resulting binary which increases backwards
61+
# compatibility with older versions of Linux.
62+
if ($env:GOARCH -ne "arm64") {
63+
$env:CGO_ENABLED = "0"
64+
}
4065
}
4166
elseif ($IsMacOS) {
4267
Write-Host "Building for macOS"
@@ -57,13 +82,17 @@ function PrintFlags() {
5782
[string] $flags
5883
)
5984

85+
# Attempt to format flags so that they are easily copy-pastable to be ran inside pwsh
6086
$i = 0
6187
foreach ($buildFlag in $buildFlags) {
88+
# If the flag has a value, wrap it in quotes. This is not required when invoking directly below,
89+
# but when repasted into a shell for execution, the quotes can help escape special characters such as ','.
6290
$argWithValue = $buildFlag.Split('=', 2)
6391
if ($argWithValue.Length -eq 2 -and !$argWithValue[1].StartsWith("`"")) {
6492
$buildFlag = "$($argWithValue[0])=`"$($argWithValue[1])`""
6593
}
6694

95+
# Write each flag on a newline with '`' acting as the multiline separator
6796
if ($i -eq $buildFlags.Length - 1) {
6897
Write-Host " $buildFlag"
6998
}
@@ -75,6 +104,8 @@ function PrintFlags() {
75104
}
76105

77106
$oldGOEXPERIMENT = $env:GOEXPERIMENT
107+
# Enable the loopvar experiment, which makes the loop variaible for go loops like `range` behave as most folks would expect.
108+
# the go team is exploring making this default in the future, and we'd like to opt into the behavior now.
78109
$env:GOEXPERIMENT = "loopvar"
79110

80111
try {
@@ -87,6 +118,7 @@ try {
87118
}
88119

89120
if ($BuildRecordMode) {
121+
# Modify build tags to include record
90122
$recordTagPatched = $false
91123
for ($i = 0; $i -lt $buildFlags.Length; $i++) {
92124
if ($buildFlags[$i].StartsWith("-tags=")) {
@@ -97,6 +129,7 @@ try {
97129
if (-not $recordTagPatched) {
98130
$buildFlags += "-tags=record"
99131
}
132+
# Add output file flag for record mode
100133
$recordOutput = "-o=$OutputFileName-record"
101134
if ($IsWindows) { $recordOutput += ".exe" }
102135
$buildFlags += $recordOutput
Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,16 @@
11
import: ../../.vscode/cspell.yaml
2-
words: []
2+
words:
3+
# Skill commands
4+
- agentskills
5+
- azureaiskills
6+
- exterrors
7+
- foundry
8+
- foundrysdk
9+
- orderby
10+
- repoint
11+
- repoints
12+
- tarball
13+
- zipslip
14+
- gzip
15+
- skill
16+
- skills

0 commit comments

Comments
 (0)