Skip to content

feat(contentunderstanding): Copilot skills for authoring custom analyzers#39137

Open
chienyuanchang wants to merge 19 commits into
mainfrom
cu-sdk/custom-analyzer-skills
Open

feat(contentunderstanding): Copilot skills for authoring custom analyzers#39137
chienyuanchang wants to merge 19 commits into
mainfrom
cu-sdk/custom-analyzer-skills

Conversation

@chienyuanchang

Copy link
Copy Markdown
Member

feat(contentunderstanding): Copilot skills for authoring custom analyzers

Summary

Adds two GitHub Copilot skills under sdk/contentunderstanding/ai-content-understanding/.github/skills/ that guide users through the iterative cycle of authoring custom Content Understanding analyzers directly inside VS Code:

Skill Use case
cu-sdk-author-analyzer Single-document-type authoring (e.g. invoices, receipts, contracts)
cu-sdk-author-analyzer-classify-route Classify-and-route pipelines for mixed-document packets (e.g. invoice + bank statement + loan application in one PDF)

Both skills delegate to a small cu-skill TypeScript tool under .github/skills/_shared/ that exposes three subcommands:

  • extract-layout — pulls document structure into .layout.{json,md} so Copilot has ground-truth context for schema drafting.
  • create-and-test — validates a schema locally, creates the analyzer, batch-tests inputs, prints a per-field fill-rate + avg-confidence summary, and (with --ephemeral) cleans up.
  • create-and-test-router — same loop for classify-and-route: creates N inner extractors + 1 outer classifier, wires contentCategories[*].analyzerId, runs, and prints a category-aware summary.

A pure-TypeScript SchemaValidator (native node:fs only, no @azure/* imports) catches structural mistakes (unknown baseAnalyzerId, missing fieldSchema, malformed contentCategories routes) before a service round-trip.

This mirrors the .NET skills shipped in Azure/azure-sdk-for-net#60394, the Java skills in Azure/azure-sdk-for-java#49672, and the Python skills in Azure/azure-sdk-for-python#47218.

Live test

Tested against a real CU resource with mixed_financial_docs.pdf (an invoice + bank statement + loan application):

$ (cd .github/skills/_shared && node_modules/.bin/tsx src/cli.ts \
    extract-layout --input mixed_financial_docs.pdf --output /tmp/layout/)
[RUN ] mixed_financial_docs.pdf -> /tmp/layout//mixed_financial_docs.layout.{json,md}
[DONE] 1 ok, 0 failed
$ (cd .github/skills/_shared && node_modules/.bin/tsx src/cli.ts \
    create-and-test --schema bank_statement.schema.json \
    --input mixed_financial_docs.pdf --output /tmp/results/ \
    --reuse --ephemeral)
[CREATE] analyzer_id=bank_statement.schema_62465204
[CREATE] bank_statement.schema_62465204 ready
[ANALYZE] mixed_financial_docs.pdf -> /tmp/results/mixed_financial_docs.json
[CLEANUP] delete analyzer bank_statement.schema_62465204

[SUMMARY]
category: (single)  (1 document)
  field                                    fill rate   avg conf
  accountHolder                            100.0%      0.962
  accountNumber                            100.0%      0.962
  beginningBalance                         100.0%      0.902
  endingBalance                            100.0%      0.902
  ...
  transactions[].deposit                    15.4%      0.853
lowest-confidence fields:
  0.587  transactions[].date  (mixed_financial_docs)

The router variant created 3 inner + 1 outer analyzer, classified 3 segments, extracted all fields, and cleaned up all 4 analyzers (~48 s end-to-end).

What's in the PR

Component LOC Notes
_shared/src/schemaValidator.ts 299 Pure TypeScript — no @azure/* deps; purity-guarded by a unit test
_shared/src/clientHelpers.ts 266 Client builder + raw-response capture policy + JS-SDK serializer translation
_shared/src/extractLayoutCommand.ts 138 Stage 1
_shared/src/createAndTestCommand.ts 553 Stage 2 (single-type)
_shared/src/createAndTestRouterCommand.ts 494 Stage 2 (classify-route)
_shared/src/cli.ts 59 Subcommand dispatcher
_shared/src/schemaValidator.test.ts 302 19 node:test cases, all passing
_shared/package.json + tsconfig.json + .gitignore + README.md 120 Standalone npm package; not part of any tshy / pnpm workspace, with its own .gitignore for node_modules
cu-sdk-author-analyzer/SKILL.md + template 482 Single-type SKILL
cu-sdk-author-analyzer-classify-route/SKILL.md + template 507 Classify-route SKILL
README.md +20 New "What's New" section + two new author-skill rows in the existing table
CHANGELOG.md +29 New 1.2.0-beta.3 (Unreleased) section
Total +3,269

Test results

$ npm test
# tests 19
# pass 19
# fail 0

Coverage:

  • Valid single-type and classify-route schemas
  • Classify-route allows category without analyzerId (catch-all "other" bucket)
  • Unknown baseAnalyzerId rejected (catches the prebuilt-documentAnalyzer typo class)
  • Missing fieldSchema on non-classifier
  • Empty fields object, unknown field type/method
  • Nested object.properties and array.items recursion
  • Classify-route + top-level fieldSchema → rejected
  • Classify-route without enableSegment: true → rejected
  • Empty category description → rejected
  • validateFile: missing file, invalid JSON, valid round-trip
  • validateString: invalid JSON
  • KNOWN_BASE_ANALYZER_IDS allow-list sanity check
  • Purity guard — fails if schemaValidator.ts ever pulls in @azure/* / node:http / node:net

Bugs fixed during live test

  1. Env var quoting.env values typically wrap in "…"; raw export keeps the quotes. Added a readEnv() helper that strips one layer of surrounding quotes (mirrors the .NET and Java fixes).
  2. DefaultAzureCredential IMDS hang on WSL / dev boxes — built a focused ChainedTokenCredential chain (EnvironmentCredentialAzureCliCredential) so the IMDS probe doesn't stall on dev boxes (~30 s wait on WSL).
  3. JS SDK serializer drops array itemsclient.createAnalyzer(id, ContentAnalyzer) runs the schema through a typed serializer that reads field.itemDefinition and writes field.items on the wire. Our raw schema files use the wire shape (items) directly, so the serializer would strip array-field item definitions and the service returned InvalidRequest. Fixed with a tiny translateForJsSerializer() helper that pre-renames itemsitemDefinition (and $refref) before handing the schema to the typed createAnalyzer().
  4. Output JSON shape parity — used a pipeline capture policy to grab the raw service response body, then unwrapped the LRO envelope {id,status,result,usage} to match Python's poller.pollUntilDone() output and the .NET / Java skill output.

Backward compatibility

No public API changes. The skills tree is opt-in tooling that lives under .github/skills/ and is only loaded by Copilot when a user explicitly asks. The cu-skill npm package is standalone — not part of any tshy / pnpm workspace, with its own .gitignore excluding node_modules and package-lock.json — so it doesn't affect normal package builds or the published @azure/ai-content-understanding artifact.

Checklist

…nalyzers

Add two GitHub Copilot skills under .github/skills/ that guide users
through the iterative cycle of authoring custom Content Understanding
analyzers in VS Code:

- cu-sdk-author-analyzer            — single-document-type authoring
- cu-sdk-author-analyzer-classify-route — classify-and-route pipelines

Both skills delegate to a small cu-skill TypeScript tool under
.github/skills/_shared/ that exposes three subcommands (extract-layout,
create-and-test, create-and-test-router) and a pure-TypeScript
SchemaValidator (no @azure/* deps) so structural mistakes are caught
before a service round-trip.

Mirrors the .NET skills shipped in Azure/azure-sdk-for-net#60394, the
Java skills in Azure/azure-sdk-for-java#49672, and the Python skills in
Azure/azure-sdk-for-python#47218.

Live-tested against a real CU resource with mixed_financial_docs.pdf:

- extract-layout produces .layout.{json,md}
- create-and-test reports per-field fill rate + avg confidence and
  cleans up the analyzer when --ephemeral is set
- create-and-test-router creates N inner + 1 outer analyzer, prints a
  category-aware summary, and cleans up all four

New unit tests:

- schemaValidator.test.ts (19 cases) using node:test, mirrors Python's
  test_skills_shared_schema_validator.py, the .NET
  SkillSchemaValidatorTests.cs, and the Java SchemaValidatorTest.java:
  valid single-type and classify-route schemas, every rejection path,
  validateFile error handling, KNOWN_BASE_ANALYZER_IDS allow-list
  sanity, and a purity guard that fails if the validator source ever
  pulls in @azure/* or HTTP modules.

README:

- New 'What's New' section and two new entries in the existing
  'GitHub Copilot Skills' table.

CHANGELOG:

- New 1.2.0-beta.3 (Unreleased) section describing the new skills.

Build:

- npm install: clean
- npm test: 19/19 tests pass

The npm tool package is intentionally standalone — not part of any
tshy / pnpm workspace, with its own .gitignore for node_modules — so it
has zero effect on the published @azure/ai-content-understanding
artifact.

Bug fix during live test: the JS SDK's typed createAnalyzer serializer
reads field.itemDefinition and writes field.items on the wire, while
our raw schema files already use the wire shape (items directly). Added
a translateForJsSerializer() helper that pre-translates items ->
itemDefinition (and $ref -> ref) before passing the schema to the typed
client.createAnalyzer(), so array fields no longer have their item
definitions stripped.
…ts from Python

Three changes for parity with Python PR #47218:

1. **summarizeRouted denominator bug fix** (was a port-time regression).
   The denominator for a per-field fill rate must be the per-category
   segment count, NOT the per-field row count. Two segments with one
   missing the field correctly reports 50% (1/2 segments), not 100%
   (1/1 rows). This matches Python and .NET; Java was wrong the same
   way and is being fixed in a parallel commit on the Java PR.

2. **New CLI-helper test files** — createAndTestCommand.test.ts and
   createAndTestRouterCommand.test.ts. Together they cover the 4
   pure-helper tests Python ships (summarize leaf-row flattening,
   summarizeRouted per-category denominator, summarizeRouted zero-fill,
   wireInnerIds alias substitution). The denom regression above was
   discovered by these tests, not a separate review.

   Python ships 10 CLI-helper tests total; the remaining 6 either test
   argparse-specific behaviour (`--help` smoke, monkey-patched
   client) or test code structured differently in JS (error-tuple
   API), so they don't translate 1:1. We cover the substantive
   behaviour.

3. **Cross-skill SKILL.md updates** that Python's PR #47218 also
   shipped: a two-stage-pipeline section + a baseAnalyzerId table in
   cu-sdk-common-knowledge, 'Next step' cross-refs in cu-sdk-sample-run,
   and a 'step numbering is contract' callout in cu-sdk-setup.
…r-skills

# Conflicts:
#	sdk/contentunderstanding/ai-content-understanding/CHANGELOG.md
…ygiene)

Java CI just caught 'prebuilts' as an unknown word in the same
cu-sdk-common-knowledge SKILL.md text we ported from Python.
Repro-check found the identical text (3 places in the new
SKILL.md, plus 4 pre-existing places in schemaValidator.ts and
its test) in the JS package. The JS CI hasn't tripped on it yet,
but same lint rule → fix all now before it does. Rephrase as
'prebuilt analyzers'.
Two failures on the last CI run:

1. **Verify-Links** flagged 22 broken sample refs in the two analyzer
   skill docs. The Python source skills referenced 'samples/*.py'
   files at the package root; the JS port kept the same shape but JS
   samples live at 'samples-dev/*.ts' (source-of-truth; 'samples/v1-beta'
   is generated on release and not stable across releases). Rewrite
   every '../../../samples/*.ts' -> '../../../samples-dev/*.ts'. All 10
   distinct sample files referenced were verified to exist on disk.

2. **Build** failed at cu-skill because TypeScript 6.0.3 (used by the
   repo) requires explicit 'rootDir' when 'outDir' is set. Our local
   dev env is on TS 5.7 which infers rootDir from the 'include' glob,
   so the file compiled locally but failed CI. Add 'rootDir: src' to
   _shared/tsconfig.json.

Local: npm run build succeeds cleanly on TS 6.0.3, 23/23 tests still
pass.
…r sources

Pure formatting cleanup: prettier re-wraps a couple long function calls
and prefers single quotes when the string contains a double quote.
No behavior change; the validator diff is line-joining plus quote
style, and the test diff is a single line-join and quote-style swap in
the purity-guard forbidden list.

Verified with 'git diff --ignore-all-space' — 3 bytes of semantic
diff per file, both quote-style changes in string literals whose value
is identical.

23/23 tests still pass.
The cu-skill authoring tool under
sdk/contentunderstanding/ai-content-understanding/.github/skills/_shared/
has its own package.json (so it can be developed as a standalone CLI),
but it is not part of the shipping SDK and should not be built as part
of turbo build.

Without this exclusion, pnpm's 'sdk/**' glob picks up _shared/package.json
as a workspace, but its dependencies are not in the top-level lockfile,
so CI reports 'not found in lockfile' warnings and then TS2307 errors
when turbo tries to build it without any installed node_modules.

Scoped narrowly to this package (like the existing
'!sdk/identity/identity/integration/**' and
'!sdk/servicebus/service-bus/test/stress/**' exclusions).
… template

The template comment referenced create_and_test.py — the Python
source-of-truth filename copied into the port. In this JS skill, the
concrete tool is the cu-skill TypeScript CLI under .github/skills/_shared/,
so the template now references the create-and-test skill script and points
readers at the tool directory.

Same drift being fixed in the parallel .NET (#60394) and Java (#49672) PRs.
No functional or public-API change.
Ports the correctness fix from the sibling Java PR (Azure/azure-sdk-for-java#49672,
bca631f00b4) to the JS skill.

Bug: wireInnerIds keyed the alias lookup on the *category name* instead
of on the *analyzerId value*, so a schema like

  contentCategories:
    loan_bucket:
      analyzerId: loan_application

silently left the placeholder in place because `aliasToId.has("loan_bucket")`
returned false even when `aliasToId.has("loan_application")` was true.
The cross-check in runCreateAndTestRouter had the same defect and also
rejected valid `prebuilt-*` values (Azure service prebuilts don't need
an --inner-schema).

New behavior mirrors Python (_patch_outer_analyzer_ids), .NET (WireInnerIds),
and Java (WireResult):

- Skip categories that omit analyzerId (classification-only "other" bucket).
- Skip analyzerId values that start with "prebuilt-" (service prebuilts).
- Any other value must match an alias in aliasToId; otherwise return an
  error naming the missing alias + category.
- Every alias in aliasToId must be referenced by some category (typo
  check); otherwise return an "unused" error.

Signature changed from `wireInnerIds(...) -> Record<string, unknown>` to
`wireInnerIds(...) -> WireResult` where WireResult carries both patched
schema and any errors. Caller in runCreateAndTestRouter now checks
errors before proceeding.

Test coverage: rewrote wireInnerIdsSubstitutesMatchingAliases with
category names deliberately different from analyzerId values, and added:
- keeps 'prebuilt-*' analyzerId values as-is
- returns error for unknown alias
- returns error for unused alias
- allows categories without analyzerId

Verified: `npm test` in .github/skills/_shared/ passes 27/27 (up from 23).
Revert the pnpm-workspace.yaml exclusion I added in 947c721 (which
required @Azure/azure-sdk-js-dev CODEOWNERS review), and instead prevent
pnpm from picking up cu-skill as a workspace member by renaming its
package.json to package.json.template. The tracked template is copied
into place by the skill scripts before `npm install`.

Why:

- pnpm's `sdk/**` glob only matches literal `package.json`. With the
  file renamed, the tool directory is not registered as a workspace
  member, and Turbo does not try to build it during CI.
- The tool is still fully runnable: SKILL.md and _shared/README.md
  install steps now do
    [ -f package.json ] || cp package.json.template package.json
    npm install
  before running the CLI.
- The runtime `package.json` is now `.gitignore`d, alongside the
  existing `node_modules/`, `package-lock.json`, `dist/`, and `.tshy/`
  entries.

This lifts the CODEOWNERS gate on the PR without changing shared repo
infrastructure. The alternative (keeping the workspace exclusion) would
have required approval from @jeremymeng / @Azure/azure-sdk-js-dev for
what is effectively a package-local build-config detail.

Verified: `cp package.json.template package.json && npm test` in
_shared/ passes 27/27.
@chienyuanchang chienyuanchang marked this pull request as ready for review July 7, 2026 20:33
Copilot AI review requested due to automatic review settings July 7, 2026 20:33

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 adds two GitHub Copilot skills under sdk/contentunderstanding/ai-content-understanding/.github/skills/ (cu-sdk-author-analyzer for single-document-type authoring and cu-sdk-author-analyzer-classify-route for mixed-document packets), plus a standalone cu-skill TypeScript CLI tool under .github/skills/_shared/ that backs them. The tool exposes three subcommands (extract-layout, create-and-test, create-and-test-router) wrapping the shipped ContentUnderstandingClient, plus a pure-TypeScript SchemaValidator that catches structural schema mistakes before a service round-trip. It's opt-in tooling — no changes to the published @azure/ai-content-understanding API. This mirrors equivalent skills already shipped for .NET, Java, and Python.

Changes:

  • New authoring skills (SKILL.md + JSON templates) that walk users through layout extraction → schema drafting → local validation → batch test → agent review.
  • New standalone cu-skill CLI package (_shared/) with schema validator, client helpers (raw-response capture + serializer translation), three commands, and node:test unit tests; kept out of the pnpm workspace via a package.json.template + .gitignore scheme.
  • README "What's New" + skill-table rows and a 1.2.0-beta.3 (Unreleased) CHANGELOG entry; cross-links added to existing cu-sdk-* skills.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
_shared/src/clientHelpers.ts Client builder, raw-response capture policy, JS-serializer key translation (has a broad items-rename bug)
_shared/src/schemaValidator.ts Pure-TS schema validator; no @azure/* imports
_shared/src/createAndTestCommand.ts Single-type create/test/summarize (WARN references a Python sample)
_shared/src/createAndTestRouterCommand.ts Classify-and-route command (unused type import)
_shared/src/extractLayoutCommand.ts Stage-1 layout extraction
_shared/src/cli.ts Subcommand dispatcher
_shared/src/*.test.ts node:test unit tests for validator and pure helpers
_shared/{tsconfig.json,package.json.template,.gitignore,README.md} Standalone tool config/docs
cu-sdk-author-analyzer/{SKILL.md,templates/schema_template.json} Single-type skill + template (DLL/.py/.NET-name/tree carryover issues)
cu-sdk-author-analyzer-classify-route/{SKILL.md,templates/classifier_template.json} Classify-route skill + template (DLL/.py/--schema-dir mismatch issues)
cu-sdk-{setup,sample-run,common-knowledge}/SKILL.md Cross-links and shared field/baseAnalyzerId/classify-route knowledge
README.md, CHANGELOG.md "What's New" section, skill-table rows, 1.2.0-beta.3 entry

Key findings: one latent bug (translateForJsSerializer rewrites any object key named items, which corrupts a user field literally named items), and several documentation/error-message inconsistencies carried over from the .NET/Python ports (references to a nonexistent sample_update_defaults.py, "built DLL by path", AnalysisResultExtensions.ToLlmInput, a duplicated package.json tree line, and a --schema-dir description that doesn't match the alias logic in the code).

Audit turned up 3 places in the JS port where the Python-source
filename `samples/sample_update_defaults.py` was left in place:

  - schema_template.json (`models._comment`)
  - classifier_template.json (`models._comment`)
  - createAndTestCommand.ts (a user-facing [WARN] on missing
    models.completion)

Replaced with `samples-dev/updateDefaults.ts`, which is the actual
JS sample file that configures resource-level model defaults (matching
the link convention already used in the SKILL.md files). Same drift
exists in the sibling .NET and Java PRs and is being fixed there too.

Verified: `npm test` in .github/skills/_shared/ still 27/27 passing.
…LL.md

Second audit turned up two more places where .NET-copied wording
survived in the JS port. The Prerequisites section in both SKILL.md
files said:

  Required: the `@azure/ai-content-understanding` SDK built locally
  (the skill tool references the built DLL by path), ...

Neither "SDK built locally" nor "built DLL by path" applies to the JS
port — the tool is a standalone TypeScript CLI that depends on the
published `@azure/ai-content-understanding` package from npm (as
_shared/README.md and the install step both already document). The
same drift was flagged by the Copilot review on the sibling Java PR
(#49672) and got fixed there; JS was missed.

Replaced with a JS-appropriate prereq: "Node.js 18+ and npm ... tool
depends on the published `@azure/ai-content-understanding` package
from npm".

Verified: `npm test` in .github/skills/_shared/ still 27/27 passing.
…scriptors

Addresses Copilot review comment #1 on PR #39137.

Before: translateForJsSerializer walked every object node and renamed
any key literally called 'items' to 'itemDefinition' (and '$ref' to
'ref'). Because fieldSchema.fields.<name>, properties.<name>, and
itemDefinition.properties.<name> are keyed by user-chosen field names,
a schema with a field literally named 'items' — extremely common for
line-item arrays — would end up being sent to the service under the
name 'itemDefinition' instead. Extraction for that field would silently
break.

After: the rename is scoped to field-descriptor objects only, detected
by the presence of a top-level 'type' string property. Children under
fieldSchema.fields / properties / (renamed) itemDefinition.properties
are traversed but their own keys are never rewritten.

Also exports the helper (as _translateForJsSerializer, underscore-
prefixed to mark it test-only) so we can pin down the rules without
mocking the Azure client.

Test coverage (clientHelpers.test.ts, 6 cases via node:test):
  - array-field `items` renamed to `itemDefinition`
  - user field literally named `items` survives untouched
  - user field literally named `$ref` survives untouched
  - array-field `$ref` renamed to `ref`
  - recursion into nested object.properties + array.items
  - primitives and arrays of primitives pass through unchanged

npm test in .github/skills/_shared/: 42/42 passing (was 27/27).
…outer schema

Addresses Copilot review comments #7 (schema-dir doc mismatch) and #8
(unused ContentUnderstandingClient import) on PR #39137.

Before: --schema-dir did readdirSync(dir).sort() and registered every
*.json file under its bare stem as an alias. Given the SKILL.md
recommendation to name files invoice_v1.json / invoice_v2.json, that
produced aliases like 'invoice_v1' and 'invoice_v2', neither of which
matched the 'invoice' category in the outer classifier — the tool
failed with 'no --inner-schema entry matches alias invoice' errors.

After: --schema-dir now discovers inner schemas the same way Python's
_discover_inner_from_dir and .NET's DiscoverInnerFromDir do:
  1. Read the outer classifier schema.
  2. For every category with a non-'prebuilt-' analyzerId, look for
     files whose stem is exactly <alias> or starts with '<alias>_'.
  3. Pick the alphabetically-last match, so invoice_v2.json beats
     invoice_v1.json.
  4. If any alias has no match, log every missing alias and return
     null (which surfaces as exit code 2 in runCreateAndTestRouter).

This matches the shortcut described in cu-sdk-author-analyzer-classify-
route/SKILL.md exactly, keeping the JS behaviour in lockstep with the
other three language ports.

Also drops the unused 'import type { ContentUnderstandingClient }' at
the top of the file — the value flows through buildClient() and the
imported service helpers, so the type import was dead code.

Test coverage (createAndTestRouterCommand.test.ts, +9 cases):
  - exact-stem alias resolution
  - suffix resolution + alphabetical-last tiebreak (v2 > v10 > v1)
  - exact match vs suffix variants tiebreak pinned
  - prebuilt-* aliases skipped (no file required)
  - categories with no analyzerId skipped (classification-only)
  - missing aliases → null + error message names every missing one
  - unrelated JSON files in schema-dir ignored
  - non-string analyzerId tolerated (defensive)
  - empty contentCategories → empty resolution map

npm test in .github/skills/_shared/: 42/42 passing.
…ckage.json in skill tree

Addresses Copilot review comments #6, #9, and #10 on PR #39137.

cu-sdk-author-analyzer/SKILL.md:
  - Directory tree listed `package.json` twice (with two different
    comments). The tracked file is `package.json.template`; the
    runtime `package.json` is git-ignored and generated on install
    (see _shared/.gitignore). Collapsed to a single accurate entry.
  - `AnalysisResultExtensions.ToLlmInput` is the .NET helper name;
    JS calls it `toLlmInput` and demonstrates it in
    samples-dev/toLlmInput.ts. Fixed both the name and the broken
    link (was pointing at samples/ directory instead of the file).

cu-sdk-author-analyzer-classify-route/SKILL.md:
  - Same `AnalysisResultExtensions.ToLlmInput` → `toLlmInput` fix
    on the classify-route SKILL's output-files section.
  - Prettier reformatting on tables (column padding), italics
    (`*x*` → `_x_`), bullets (`*` → `-`), and a multi-line
    JSON example. No content changes.

Skill tool prerequisites text (SKILL.md 'the skill tool references
the built DLL by path') was already fixed in c319157, and the
sample_update_defaults.py reference was already fixed in 0d5d75c.
…sort

Ports the .NET fix from PR #60394 (commit 61bda1abd16) to keep the
cross-language behaviour in lockstep.

Before: discoverInnerFromDir did '.sort()' on filenames and took the
last element as the 'newest version'. That breaks the moment any
alias reaches double digits: '1' < '9' char-by-char, so
'invoice_v10.json' sorts BEFORE 'invoice_v9.json' — 'alphabetical
last' silently picks v9 and the tool loads the older schema.

After: pick the file with the highest 'version key', defined as:
  - group 0 → bare '<alias>.json'                (no suffix; oldest)
  - group 1 → '<alias>_v<N>.json' or             (numeric suffix;
              '<alias>_<N>.json'                  sorted by N)
  - group 2 → any other '<alias>_<suffix>.json'  (lex tiebreaker)

So invoice_v10 beats invoice_v9 beats invoice, and 'invoice_draft'
still beats bare 'invoice.json' but loses to any versioned file.

Also exports 'versionSortKey' so tests can pin the key extractor
directly (mirrors the .NET pattern where VersionSortKey is internal).

Test updates (createAndTestRouterCommand.test.ts):
  - Added 'versionSortKey' describe block (4 cases: group 0 bare,
    group 1 numeric with v10>v9, group 1 bare-numeric _42, group 2
    non-numeric suffix).
  - Replaced 'picks alphabetically-last suffix match (v2 beats v1)'
    with 'picks natural version max, so _v10 beats _v9' — the old
    test was pinning the exact broken behaviour that Copilot flagged.
  - Kept the 'prefers versioned suffix over bare alias baseline' case.

npm test in .github/skills/_shared/: 46/46 passing (was 42; +4
versionSortKey, replaced 1 stale case).
…version sort

The prior SKILL.md description of --schema-dir was still tied to the
old alphabetical sort ("alphabetical sort, so invoice_v2.json wins
over invoice_v1.json"), which was the exact wording Copilot flagged
as inaccurate on PR #39137. After the natural-version-sort fix
(commit d2a463c) the resolution rule changed to 'highest
numeric version wins', so v10 beats v9, not the other way around.

Updated cu-sdk-author-analyzer-classify-route/SKILL.md to spell out
the actual matching rule:

  - stem == <alias> (bare baseline, group 0), OR
  - stem starts with <alias>_ (versioned; numeric _v<N>/_<N> suffixes
    sort by integer N so v10 > v9; non-numeric suffixes fall back to
    lexicographic order but still beat the bare baseline).

Also cleaned up the matching in-source comment at the top of
runCreateAndTestRouter in createAndTestRouterCommand.ts, which
described the same stale 'alphabetically-last' rule.

No code change — this is a pure doc fix to keep the SKILL and the
source comments consistent with what the tool actually does now.

npm test in .github/skills/_shared/: 46/46 still passing.

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

Copilot reviewed 23 out of 23 changed files in this pull request and generated 2 comments.

@azure/ai-content-understanding declares engines.node: ">=22.0.0" in its
package.json, so users on Node 18-21 hit EBADENGINE warnings on npm install.
Update the Prerequisites line and the failure-routing table in both skill
files (cu-sdk-author-analyzer and cu-sdk-author-analyzer-classify-route)
and cite the engines constraint so the source of truth is discoverable.
Also aligns with the repo-wide Node 22 baseline (see .github/skills/node-migration).
…NGELOG section

Two internal-consistency follow-ups after the earlier author-analyzer skill fix:

1. cu-sdk-setup/SKILL.md still recommended Node.js 20+ in four places
   (prereq line, decision table, ASK USER prompt, install-script step 1,
   troubleshooting row). Bumped all to 22+ so the whole skills family
   matches @azure/ai-content-understanding's engines.node: ">=22.0.0".

2. CHANGELOG entry moved from '### Features Added' to '### Other Changes'
   for parity with the Python PR (Azure/azure-sdk-for-python#47218): the
   Copilot skills are companion tooling, not new SDK API surface, so
   'Other Changes' is the more accurate bucket.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants