diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1dd02079..61544387 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,7 +57,7 @@ Prefer a small, consistent verb vocabulary: - `import` — push an artifact into a live environment. - `uninstall` — remove an artifact from a live environment. - `list` — enumerate artifacts of a noun, with lightweight filtering. -- `show` — render the details of a single artifact. +- `get` — render the details of a single artifact. - `validate` — check the workspace against the metamodel. - `describe` — render a human-readable description of a metamodel entity. - `patch` — reserved for incremental push workflows. @@ -66,13 +66,13 @@ When an artifact is installed into a live environment, `import` and `uninstall` --- -## `list` vs `show` +## `list` vs `get` Standard list-vs-detail pair. Both are always scoped by the owning noun: - `environment solution list` — many solutions, brief rows. - `environment deployment list` — many deployment runs, brief rows. -- `environment deployment show --latest` — one run, full detail. +- `environment deployment get --latest` — one run, full detail. If a command wants to render many things *and* full detail, it is doing two jobs — split it. @@ -80,9 +80,9 @@ If a command wants to render many things *and* full detail, it is doing two jobs ## Typed selectors, not generic `--id` -When a `show`-style lookup can resolve against multiple entity shapes, expose **one flag per entity shape** and let the user declare intent. Do **not** accept a generic `--id` that the command probes across entity types behind the user's back. +When a `get`-style lookup can resolve against multiple entity shapes, expose **one flag per entity shape** and let the user declare intent. Do **not** accept a generic `--id` that the command probes across entity types behind the user's back. -Example — `environment deployment show`: +Example — `environment deployment get`: - `--package-run-id ` — packagehistory record id. - `--solution-run-id ` — msdyn_solutionhistory record id. diff --git a/src/TALXIS.CLI.Features.Docs/Skills/component-creation.md b/src/TALXIS.CLI.Features.Docs/Skills/component-creation.md index a7d0c9cc..38eda305 100644 --- a/src/TALXIS.CLI.Features.Docs/Skills/component-creation.md +++ b/src/TALXIS.CLI.Features.Docs/Skills/component-creation.md @@ -7,7 +7,7 @@ Scaffolding creates **local files** in your workspace — it does NOT create com ## Workflow Chain 1. **`workspace_explain`** — understand what exists in the repo before creating anything -2. **`workspace_component_type_list`** — discover available component types +2. **`component_type_list`** — discover available component types 3. **`workspace_component_parameter_list`** — get required parameters for a component type 4. **`workspace_component_create`** — scaffold the component locally 5. **Build locally** to validate: `dotnet build` diff --git a/src/TALXIS.CLI.Features.Docs/Skills/data-querying.md b/src/TALXIS.CLI.Features.Docs/Skills/data-querying.md index b86b01ce..1ce8a544 100644 --- a/src/TALXIS.CLI.Features.Docs/Skills/data-querying.md +++ b/src/TALXIS.CLI.Features.Docs/Skills/data-querying.md @@ -7,8 +7,9 @@ | Analytics, joins, aggregation | SQL | `environment_data_query_sql` | | CRUD, filtering, lookup expansion | OData | `environment_data_query_odata` | | Complex Dataverse-native queries (linked entities, conditions) | FetchXML | `environment_data_query_fetchxml` | -| Quick record listing with filters | — | `environment_data_record_list` | -| Record counts | — | `environment_data_record_count` | +| Single record by primary key | — | `environment_data_record_get` | +| Quick record listing with filters | OData | `environment_data_query_odata` (use `$top` to cap results) | +| Record counts | SQL | `environment_data_query_sql` (`SELECT COUNT(*) FROM `) | ## OData Query Patterns diff --git a/src/TALXIS.CLI.Features.Docs/Skills/deployment-workflow.md b/src/TALXIS.CLI.Features.Docs/Skills/deployment-workflow.md index 10c7d9f9..9909b7a1 100644 --- a/src/TALXIS.CLI.Features.Docs/Skills/deployment-workflow.md +++ b/src/TALXIS.CLI.Features.Docs/Skills/deployment-workflow.md @@ -26,7 +26,7 @@ Creates a `.zip` solution file from your local project. This is still a local op ``` Tool: environment_solution_import ``` -Uploads the solution package to the target Dataverse environment. By default returns immediately with an `asyncOperationId`. **Do NOT use `--wait`** — solution imports take minutes and will time out the MCP request. Instead, monitor progress with `environment_deployment_show --solution-name ` until status is `Completed` or `Failed`. +Uploads the solution package to the target Dataverse environment. By default returns immediately with an `asyncOperationId`. **Do NOT use `--wait`** — solution imports take minutes and will time out the MCP request. Instead, monitor progress with `environment_deployment_get --async-operation-id ` (the id is printed by `environment_solution_import`) until status is `Completed` or `Failed`. Never query the `asyncoperation` table directly via SQL — `deployment_get` parses the findings for you. ### 5. Publish ``` @@ -36,7 +36,7 @@ Publishes all customizations. Without this step, changes to forms, views, and ot ### 6. Verify ``` -Tool: environment_deployment_show --latest +Tool: environment_deployment_get --latest ``` Check the deployment status and review any findings (warnings, errors, informational messages). @@ -47,8 +47,8 @@ Before deploying, validate: | Check | Tool | |---|---| | Auth/connection is valid | `config_profile_validate` | -| Connected to correct environment | `config_profile_show` | -| Solution can be safely updated/removed | `environment_solution_uninstall_check` | +| Connected to correct environment | `config_profile_get` | +| Solution can be safely updated/removed | `environment_solution_uninstall-check` | ## Changeset Workflow @@ -65,7 +65,7 @@ Changesets let you group multiple imports and verify before committing. ## Troubleshooting Deployments If import fails: -1. `environment_deployment_show --latest` — check error findings +1. `environment_deployment_get --latest` — check error findings (or `--async-operation-id ` from the import output) 2. `environment_component_layer_list` — look for conflicting layers 3. `environment_component_dependency_required` — find missing dependencies 4. Fix locally, rebuild, and retry @@ -75,6 +75,6 @@ If import fails: - ❌ Don't skip the local build step — XML errors are much faster to catch locally than at import time - ❌ Don't deploy unmanaged solutions to production — use managed for proper versioning and clean uninstall - ❌ Don't skip `environment_solution_publish` after import — UI changes (forms, views) remain invisible without it -- ❌ Don't retry a failed import without first checking `environment_deployment_show --latest` — you'll repeat the same error +- ❌ Don't retry a failed import without first checking `environment_deployment_get --latest` — you'll repeat the same error See also: [troubleshooting](troubleshooting.md), [solution-layering](solution-layering.md) diff --git a/src/TALXIS.CLI.Features.Docs/Skills/environment-management.md b/src/TALXIS.CLI.Features.Docs/Skills/environment-management.md index b0f46e59..5616423e 100644 --- a/src/TALXIS.CLI.Features.Docs/Skills/environment-management.md +++ b/src/TALXIS.CLI.Features.Docs/Skills/environment-management.md @@ -38,7 +38,7 @@ Tests the full chain: profile → connection → auth → environment. Confirms | Tool | Purpose | |---|---| -| `config_profile_show` | Display current profile details (URL, auth method) | +| `config_profile_get` | Display current profile details (URL, auth method) | | `config_profile_validate` | Test that the profile can connect | | `config_profile_list` | Show all configured profiles | @@ -75,13 +75,13 @@ Profiles can be pinned to a workspace directory, so `txc` automatically uses the ### Switching Between Environments ``` config_profile_list — see available profiles -config_profile_show { profileName: "test" } — verify target before switching +config_profile_get { profileName: "test" } — verify target before switching ``` ## Troubleshooting Auth If `config_profile_validate` fails: -1. Check the environment URL is correct (`config_profile_show`) +1. Check the environment URL is correct (`config_profile_get`) 2. Verify the service principal exists in the target environment's Azure AD 3. Confirm the service principal has a Dataverse security role assigned 4. Check if credentials (secret/certificate) have expired diff --git a/src/TALXIS.CLI.Features.Docs/Skills/project-structure.md b/src/TALXIS.CLI.Features.Docs/Skills/project-structure.md index 624018e1..00d5432f 100644 --- a/src/TALXIS.CLI.Features.Docs/Skills/project-structure.md +++ b/src/TALXIS.CLI.Features.Docs/Skills/project-structure.md @@ -87,7 +87,7 @@ All auto-generation happens during `dotnet build` — no manual registration ste | Tool | Purpose | |---|---| | `workspace_explain` | Understand the current repo structure, solutions, and components | -| `workspace_component_type_list` | List available component types for scaffolding | +| `component_type_list` | List available component types for scaffolding | | `workspace_component_create` | Scaffold new components into a solution project | ## Naming Conventions diff --git a/src/TALXIS.CLI.Features.Docs/Skills/schema-management.md b/src/TALXIS.CLI.Features.Docs/Skills/schema-management.md index edd98c01..aa7671eb 100644 --- a/src/TALXIS.CLI.Features.Docs/Skills/schema-management.md +++ b/src/TALXIS.CLI.Features.Docs/Skills/schema-management.md @@ -14,7 +14,7 @@ Use `guide_environment` to discover environment schema tools and their parameter ## Key Rules -- Always run `environment_component_dependency_delete_check` before deleting any component +- Always run `environment_component_dependency_delete-check` before deleting any component - Always run `environment_solution_publish` after schema changes — they won't take effect without it - Environment schema changes are **not tracked in source control** — codify locally afterward - Changes in managed layers can't be undone — only overridden by a new managed import @@ -24,12 +24,12 @@ Use `guide_environment` to discover environment schema tools and their parameter - **Routine development** → use `workspace_component_create` (local, source-controlled) - **Inspecting what's deployed** → use `environment_entity_*` tools - **Emergency non-production fix** → use environment tools, then codify locally -- **Comparing deployed vs local** → use `environment_entity_attribute_list` against the table +- **Comparing deployed vs local** → use `environment_entity_describe ` to see the entity's attributes in the live env ## What NOT to Do - ❌ Don't use these tools for routine development — changes bypass source control -- ❌ Don't delete tables/columns without running `environment_component_dependency_delete_check` first +- ❌ Don't delete tables/columns without running `environment_component_dependency_delete-check` first - ❌ Don't forget to `environment_solution_publish` after schema changes ## Metadata Propagation diff --git a/src/TALXIS.CLI.Features.Docs/Skills/solution-layering.md b/src/TALXIS.CLI.Features.Docs/Skills/solution-layering.md index c082f410..58955184 100644 --- a/src/TALXIS.CLI.Features.Docs/Skills/solution-layering.md +++ b/src/TALXIS.CLI.Features.Docs/Skills/solution-layering.md @@ -55,20 +55,20 @@ Separate concerns into dedicated solutions: | Tool | Purpose | |---|---| | `environment_solution_list` | List all solutions in the environment | -| `environment_solution_show` | Details of a specific solution (version, publisher, etc.) | +| `environment_solution_get` | Details of a specific solution (version, publisher, etc.) | | `environment_solution_component_list` | All components in a solution | | `environment_component_layer_list` | Layer stack for a specific component | -| `environment_component_layer_show` | Details of a specific layer entry | +| `environment_component_layer_get` | Details of a specific layer entry | ## Common Workflows ### "Which solution owns this component?" 1. `environment_component_layer_list` with the component ID 2. Review the layer stack — the topmost managed layer is the effective owner -3. `environment_component_layer_show` for details on specific layers +3. `environment_component_layer_get` for details on specific layers ### "What's in this solution?" -1. `environment_solution_show` for solution metadata +1. `environment_solution_get` for solution metadata 2. `environment_solution_component_list` for the full component inventory ### Publisher Rules @@ -80,7 +80,7 @@ Separate concerns into dedicated solutions: - ❌ Don't deploy unmanaged solutions to production — always use managed for proper lifecycle management - ❌ Don't put all components in a single solution — segment by concern for independent deployment cycles -- ❌ Don't uninstall solutions without running `environment_solution_uninstall_check` — may cascade-delete data +- ❌ Don't uninstall solutions without running `environment_solution_uninstall-check` — may cascade-delete data - ❌ Don't ignore layer conflicts — the topmost layer always wins, and unmanaged active layers override everything See also: [deployment-workflow](deployment-workflow.md), [troubleshooting](troubleshooting.md) diff --git a/src/TALXIS.CLI.Features.Docs/Skills/troubleshooting.md b/src/TALXIS.CLI.Features.Docs/Skills/troubleshooting.md index 08533e48..4815d7ea 100644 --- a/src/TALXIS.CLI.Features.Docs/Skills/troubleshooting.md +++ b/src/TALXIS.CLI.Features.Docs/Skills/troubleshooting.md @@ -2,7 +2,9 @@ ## Deployment Failures -**Start here:** `environment_deployment_show --latest` +**Start here:** `environment_deployment_get --latest` (or `--async-operation-id ` from the `environment_solution_import` output) + +> Never query the `asyncoperation` table directly via `environment_data_query_sql` — it returns raw status codes and unparsed error XML. `environment_deployment_get` gives you structured findings with human-readable error messages. ``` Import failed @@ -27,7 +29,7 @@ Import failed When a component behaves unexpectedly or modifications don't take effect: 1. **Inspect layers**: `environment_component_layer_list` — see all solutions that customize this component -2. **Examine specific layer**: `environment_component_layer_show` — understand what each solution contributes +2. **Examine specific layer**: `environment_component_layer_get` — understand what each solution contributes 3. **Resolution**: The topmost layer wins. Either: - Update the topmost managed solution - Remove the unmanaged active layer @@ -38,8 +40,8 @@ When a component behaves unexpectedly or modifications don't take effect: ``` Commands fail with 401/403 or connection errors └─→ config_profile_validate - ├─→ "Invalid" → config_profile_show to check URL - │ └─→ config_connection_show to verify auth credentials + ├─→ "Invalid" → config_profile_get to check URL + │ └─→ config_connection_get to verify auth credentials │ └─→ Re-create auth: config_auth_add-service-principal │ └─→ "Valid" but still failing @@ -51,7 +53,7 @@ Commands fail with 401/403 or connection errors ### Can't Delete a Component ``` -Tool: environment_component_dependency_delete_check +Tool: environment_component_dependency_delete-check ``` Shows what depends on the component you're trying to delete. Remove or update those dependencies first. @@ -64,7 +66,7 @@ Shows what components are required by a given component. Ensure all dependencies ## Wrong Environment If data or schema doesn't match expectations: -1. `config_profile_show` — verify the environment URL +1. `config_profile_get` — verify the environment URL 2. Confirm you're connected to dev/test/prod as intended 3. Switch profiles if needed @@ -72,12 +74,12 @@ If data or schema doesn't match expectations: | Symptom | First Tool to Run | |---|---| -| Import failed | `environment_deployment_show --latest` | +| Import failed | `environment_deployment_get --latest` | | Component conflict | `environment_component_layer_list` | -| Can't delete something | `environment_component_dependency_delete_check` | +| Can't delete something | `environment_component_dependency_delete-check` | | Missing dependency | `environment_component_dependency_required` | | Auth errors | `config_profile_validate` | -| Wrong data showing | `config_profile_show` | +| Wrong data showing | `config_profile_get` | | Changes not visible | `environment_solution_publish` | ## Common Dataverse Error Codes diff --git a/src/TALXIS.CLI.Features.Environment/Package/PackageImportCliCommand.cs b/src/TALXIS.CLI.Features.Environment/Package/PackageImportCliCommand.cs index b9c79431..1162b5b8 100644 --- a/src/TALXIS.CLI.Features.Environment/Package/PackageImportCliCommand.cs +++ b/src/TALXIS.CLI.Features.Environment/Package/PackageImportCliCommand.cs @@ -142,6 +142,14 @@ protected override async Task ExecuteAsync() { Logger.LogInformation("Package Deployer log: {LogPath}", Path.GetFullPath(LogFile)); } + // Next-step hint — points agents at the structured deployment-get path instead of + // raw asyncoperation SQL when they want to inspect import findings. Only emitted + // for NuGet-resolved packages because local-file imports don't have a stable name + // to query packagehistory by. + if (!string.IsNullOrWhiteSpace(nugetPackageName)) + { + Logger.LogInformation("Next: txc env deployment get --package-name {PackageName}", nugetPackageName); + } return ExitSuccess; } } diff --git a/src/TALXIS.CLI.Features.Environment/Solution/SolutionImportCliCommand.cs b/src/TALXIS.CLI.Features.Environment/Solution/SolutionImportCliCommand.cs index b2288b13..fc01511e 100644 --- a/src/TALXIS.CLI.Features.Environment/Solution/SolutionImportCliCommand.cs +++ b/src/TALXIS.CLI.Features.Environment/Solution/SolutionImportCliCommand.cs @@ -153,6 +153,14 @@ protected override async Task ExecuteAsync() OutputWriter.WriteLine($"Started (UTC): {result.StartedAtUtc:O}"); if (result.CompletedAtUtc is { } completed) OutputWriter.WriteLine($"Completed (UTC): {completed:O}"); + + // Next-step hint — keeps AI agents from inventing raw SQL queries against the + // asyncoperation table when they want to check import status. The structured + // deployment-get path returns parsed findings, the SQL path returns raw codes. + if (result.AsyncOperationId is { } hintAsyncId) + OutputWriter.WriteLine($"Next: txc env deployment get --async-operation-id {hintAsyncId}"); + else + OutputWriter.WriteLine($"Next: txc env deployment get --solution-name {result.Source.UniqueName}"); #pragma warning restore TXC003 }); diff --git a/src/TALXIS.CLI.MCP/Skills/Internal/deployment-sequence.md b/src/TALXIS.CLI.MCP/Skills/Internal/deployment-sequence.md index f5946cf7..7f6c18dd 100644 --- a/src/TALXIS.CLI.MCP/Skills/Internal/deployment-sequence.md +++ b/src/TALXIS.CLI.MCP/Skills/Internal/deployment-sequence.md @@ -10,7 +10,7 @@ 3. environment_solution_pack — local operation, creates .zip 4. environment_solution_import (--wait) — blocks until complete 5. environment_solution_publish — required for UI changes to take effect -6. environment_deployment_show --latest — verify success +6. environment_deployment_get --latest — verify success ``` ## Pre-Flight Decision Tree @@ -21,7 +21,7 @@ Before deploying: │ └─ NO → fix build errors FIRST, never deploy broken builds ├─→ Is the profile validated? │ ├─ YES → proceed - │ └─ NO → config_profile_validate, then config_profile_show to confirm target + │ └─ NO → config_profile_validate, then config_profile_get to confirm target └─→ Dev or prod target? ├─ DEV → unmanaged import is fine └─ PROD/UAT → managed import ONLY, never unmanaged @@ -30,7 +30,7 @@ Before deploying: ## Failure Recovery Sequence ``` Import failed - └─→ environment_deployment_show --latest + └─→ environment_deployment_get --latest (or --async-operation-id from solution_import output) ├─→ Component error → environment_component_layer_list → resolve conflict ├─→ Missing dependency → environment_component_dependency_required → import dependency first ├─→ Version conflict → increment solution version → retry @@ -51,4 +51,5 @@ Import failed - ❌ Deploying without building first → XML errors only caught at import (slow feedback) - ❌ Skipping `environment_solution_publish` → UI changes invisible to users - ❌ Deploying unmanaged to production → can't cleanly uninstall, no version tracking -- ❌ Retrying failed imports without checking `environment_deployment_show` → repeating the same error +- ❌ Retrying failed imports without checking `environment_deployment_get` → repeating the same error +- ❌ Querying `asyncoperation` table directly via `environment_data_query_sql` to check import status → use `environment_deployment_get --async-operation-id ` instead — it returns structured findings, not raw status codes diff --git a/src/TALXIS.CLI.MCP/Skills/Internal/local-first-philosophy.md b/src/TALXIS.CLI.MCP/Skills/Internal/local-first-philosophy.md index fba93964..ee8fb5a4 100644 --- a/src/TALXIS.CLI.MCP/Skills/Internal/local-first-philosophy.md +++ b/src/TALXIS.CLI.MCP/Skills/Internal/local-first-philosophy.md @@ -10,7 +10,7 @@ ## User wants to "check what tables/columns exist" → IF building/developing: `workspace_explain` (reads local project structure) -→ IF troubleshooting/comparing with live: `environment_entity_list` or `environment_entity_attribute_list` (needs profile) +→ IF troubleshooting/comparing with live: `environment_entity_list` (all entities) or `environment_entity_describe ` (one entity with its attributes) — both need a profile ## User wants to "deploy" or "push changes" → REQUIRED ORDER: build locally → `environment_solution_pack` → `environment_solution_import` → `environment_solution_publish` @@ -19,11 +19,11 @@ ## User wants to "delete a table/column" → IF local: delete/edit the XML files directly -→ IF live env: `environment_component_dependency_delete_check` FIRST, then `environment_entity_delete` +→ IF live env: `environment_component_dependency_delete-check` FIRST, then `environment_entity_delete` → NEVER delete in live without checking dependencies ## User wants to "see solution layers" or "check conflicts" -→ ALWAYS live environment tools: `environment_component_layer_list` → `environment_component_layer_show` +→ ALWAYS live environment tools: `environment_component_layer_list` → `environment_component_layer_get` → These are inspection-only — local workspace has no layer concept ## User wants to "query data" or "migrate data" diff --git a/src/TALXIS.CLI.MCP/Skills/Internal/schema-workflow.md b/src/TALXIS.CLI.MCP/Skills/Internal/schema-workflow.md index 64f2cee5..c28a0dc9 100644 --- a/src/TALXIS.CLI.MCP/Skills/Internal/schema-workflow.md +++ b/src/TALXIS.CLI.MCP/Skills/Internal/schema-workflow.md @@ -46,7 +46,7 @@ ## When to Use Workspace vs Environment Schema Tools → Creating/modifying schema for development: `workspace_component_create` (local) -→ Inspecting what's deployed: `environment_entity_list`, `environment_entity_attribute_list` (live) +→ Inspecting what's deployed: `environment_entity_list` (all entities), `environment_entity_describe ` (one entity with attributes) (live) → Emergency fix in non-prod: `environment_entity_attribute_create` (live, acceptable) → Prototyping before codifying: environment tools acceptable, but codify locally afterward diff --git a/src/TALXIS.CLI.MCP/Skills/Internal/solution-management.md b/src/TALXIS.CLI.MCP/Skills/Internal/solution-management.md index a3ec69b7..334bd619 100644 --- a/src/TALXIS.CLI.MCP/Skills/Internal/solution-management.md +++ b/src/TALXIS.CLI.MCP/Skills/Internal/solution-management.md @@ -27,7 +27,7 @@ What is the user creating? ## Uninstall Safety Decision ``` User wants to remove/uninstall a solution: - → ALWAYS: environment_solution_uninstall_check BEFORE uninstalling + → ALWAYS: environment_solution_uninstall-check BEFORE uninstalling → IF dependencies found: resolve dependencies first → IF data loss warning: confirm with user explicitly → NEVER uninstall in production without checking first @@ -45,5 +45,5 @@ Component behaves unexpectedly: ## Anti-Patterns - ❌ Deploying unmanaged to production → can't track versions, can't cleanly uninstall - ❌ All components in one mega-solution → slow deployments, merge conflicts, unclear ownership -- ❌ Uninstalling without `environment_solution_uninstall_check` → cascade failures or data loss +- ❌ Uninstalling without `environment_solution_uninstall-check` → cascade failures or data loss - ❌ Moving unmanaged solutions between environments → use managed for transport diff --git a/src/TALXIS.CLI.MCP/Skills/Internal/troubleshooting-patterns.md b/src/TALXIS.CLI.MCP/Skills/Internal/troubleshooting-patterns.md index cbfa57d2..bd3195dd 100644 --- a/src/TALXIS.CLI.MCP/Skills/Internal/troubleshooting-patterns.md +++ b/src/TALXIS.CLI.MCP/Skills/Internal/troubleshooting-patterns.md @@ -8,12 +8,12 @@ Match the user's symptom to the correct FIRST tool to run: ``` -"import failed" / "deployment error" → environment_deployment_show --latest +"import failed" / "deployment error" → environment_deployment_get --latest (or --async-operation-id ) "component won't update" / "conflict" → environment_component_layer_list -"can't delete" → environment_component_dependency_delete_check +"can't delete" → environment_component_dependency_delete-check "missing dependency" → environment_component_dependency_required "auth error" / "401" / "403" → config_profile_validate -"wrong data" / "stale" → config_profile_show (verify target env) +"wrong data" / "stale" → config_profile_get (verify target env) "changes not visible" → environment_solution_publish (was publish skipped?) ``` → ALWAYS run the first-response tool BEFORE asking the user for more details @@ -23,8 +23,8 @@ Match the user's symptom to the correct FIRST tool to run: ### Deployment failure escalation ``` -environment_deployment_show → findings? - ├─ Component error → environment_component_layer_list → environment_component_layer_show +environment_deployment_get → findings? + ├─ Component error → environment_component_layer_list → environment_component_layer_get ├─ Missing dependency → environment_component_dependency_required → import missing solution ├─ Version conflict → increment version locally → rebuild → retry └─ Generic/timeout → retry once with --wait → if still fails, check env health @@ -33,22 +33,23 @@ environment_deployment_show → findings? ### Auth failure escalation ``` config_profile_validate → result? - ├─ Invalid → config_profile_show → config_connection_show → fix credentials + ├─ Invalid → config_profile_get → config_connection_get → fix credentials └─ Valid but failing → check security roles in Power Platform admin center (outside txc) ``` ## Diagnostic Priority Order When unsure where to start: 1. `config_profile_validate` — eliminate auth issues first (cheapest check) -2. `config_profile_show` — confirm correct environment -3. `environment_deployment_show --latest` — check last deployment +2. `config_profile_get` — confirm correct environment +3. `environment_deployment_get --latest` — check last deployment (or `--async-operation-id ` if a specific import is in flight) 4. `environment_component_layer_list` — inspect component ownership -5. `environment_component_dependency_required` or `_delete_check` — dependency issues +5. `environment_component_dependency_required` or `environment_component_dependency_delete-check` — dependency issues → STOP as soon as you find the root cause — don't run all tools prophylactically ## Anti-Patterns - ❌ Asking the user "what error did you get?" before running diagnostic tools → run the tool first - ❌ Retrying imports without checking deployment findings → repeats the same error +- ❌ Querying the `asyncoperation` table via `environment_data_query_sql` to check import status → call `environment_deployment_get --async-operation-id ` instead — raw SQL gives unstructured statuscodes, the proper tool returns parsed findings and human-readable errors - ❌ Jumping to layer inspection before checking basic auth/connectivity - ❌ Using environment schema tools to "fix" what should be fixed locally and redeployed diff --git a/tests/TALXIS.CLI.Tests/MCP/SkillToolNameConsistencyTests.cs b/tests/TALXIS.CLI.Tests/MCP/SkillToolNameConsistencyTests.cs new file mode 100644 index 00000000..69574e35 --- /dev/null +++ b/tests/TALXIS.CLI.Tests/MCP/SkillToolNameConsistencyTests.cs @@ -0,0 +1,181 @@ +#pragma warning disable MCPEXP001 + +using System.Reflection; +using System.Text.RegularExpressions; +using TALXIS.CLI.MCP; +using Xunit; + +namespace TALXIS.CLI.Tests.MCP; + +/// +/// Regression tests guarding against the failure mode from issue #114: skills (internal +/// reasoning markdown injected into LLM sampling prompts, and public skills served via +/// get_skill_details) drifted out of sync with the actual MCP tool catalog after a CLI +/// command rename (commit 409513a, show -> get). The LLM trusted the stale skills, called +/// non-existent tools, then fell back to raw SQL against asyncoperation. +/// +/// These tests scan every skill markdown file for tool-name-shaped tokens and assert each +/// one exists in the live tool catalog. +/// +public class SkillToolNameConsistencyTests +{ + // Tool names are derived from CliCommand hierarchies. Top-level command groups in the + // catalog (workspace, environment, config, data, docs) plus the standalone "component_*" + // and "guide_*" tools. Hyphenated MCP-specific tools (copilot-instructions) don't match + // this shape and are skipped — they have no rename risk because they aren't generated + // from CLI attributes. + // Tool names follow `_(_)+` where each segment may itself contain + // hyphens (e.g. `environment_solution_uninstall-check`, `config_auth_add-service-principal`). + // Both underscore and hyphen act as intra-name separators in real catalog names. + private static readonly Regex ToolNamePattern = new( + @"\b(workspace|environment|config|data|docs|component|guide)(?:_[a-z0-9]+(?:-[a-z0-9]+)*)+\b", + RegexOptions.Compiled); + + private readonly HashSet _validToolNames; + + // MCP-specific tools registered directly in Program.cs (not via McpToolRegistry.Catalog). + // Keep this list in sync with the IsGuideTool helper and the get_skill_details/execute_operation + // registrations in Program.cs. + private static readonly string[] McpSpecificToolNames = + { + "guide", + "guide_workspace", + "guide_environment", + "guide_deployment", + "guide_data", + "guide_config", + "execute_operation", + "get_skill_details", + }; + + public SkillToolNameConsistencyTests() + { + var registry = new McpToolRegistry(); + _validToolNames = registry.Catalog + .GetAllEntries() + .Select(e => e.Descriptor.Name) + .Concat(McpSpecificToolNames) + .ToHashSet(StringComparer.Ordinal); + } + + [Fact] + public void InternalSkills_AllReferencedToolNames_ExistInCatalog() + { + var skills = LoadEmbeddedMarkdown( + typeof(GuideReasoningEngine).Assembly, + "TALXIS.CLI.MCP.Skills.Internal."); + + AssertAllReferencesResolve(skills, "internal"); + } + + [Fact] + public void PublicSkills_AllReferencedToolNames_ExistInCatalog() + { + // Trigger Docs assembly load via PublicSkillLoader (uses the same lookup pattern as production). + var loader = new PublicSkillLoader(); + loader.LoadIndex(); + + var docsAssembly = AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => a.GetName().Name == "TALXIS.CLI.Features.Docs"); + + // If the Docs assembly isn't loaded, there are no public skills to check — + // that's a different bug, covered by other tests. + if (docsAssembly is null) return; + + var skills = LoadEmbeddedMarkdown(docsAssembly, "TALXIS.CLI.Features.Docs.Skills."); + + AssertAllReferencesResolve(skills, "public"); + } + + private void AssertAllReferencesResolve( + IReadOnlyDictionary skills, + string skillCategory) + { + Assert.NotEmpty(skills); + + var stale = new List(); + foreach (var (skillId, content) in skills) + { + var referencedNames = ToolNamePattern.Matches(content) + .Select(m => m.Value) + .Distinct(StringComparer.Ordinal); + + foreach (var name in referencedNames) + { + if (!_validToolNames.Contains(name)) + { + var suggestion = SuggestNearest(name); + stale.Add($" {skillCategory}/{skillId}.md → '{name}'{(suggestion is null ? "" : $" (did you mean '{suggestion}'?)")}"); + } + } + } + + Assert.True( + stale.Count == 0, + "Skills reference tool names that do not exist in the MCP tool catalog. " + + "This usually means a CLI command was renamed and the skill was not updated. " + + "Stale references:\n" + + string.Join("\n", stale)); + } + + /// + /// Returns the catalog name with the smallest case-insensitive edit distance from the + /// referenced name. Useful for nudging contributors toward the right tool after a rename + /// (e.g. "_show" → "_get", "_check" → "-check"). Returns null when no candidate is within + /// a reasonable distance. + /// + private string? SuggestNearest(string referenced) + { + string? best = null; + int bestDistance = int.MaxValue; + int threshold = Math.Max(3, referenced.Length / 3); + + foreach (var candidate in _validToolNames) + { + // Cheap shape filter — same first segment, similar length + if (Math.Abs(candidate.Length - referenced.Length) > threshold) continue; + int d = LevenshteinDistance(referenced, candidate); + if (d < bestDistance) + { + bestDistance = d; + best = candidate; + } + } + + return bestDistance <= threshold ? best : null; + } + + private static int LevenshteinDistance(string a, string b) + { + var dp = new int[a.Length + 1, b.Length + 1]; + for (int i = 0; i <= a.Length; i++) dp[i, 0] = i; + for (int j = 0; j <= b.Length; j++) dp[0, j] = j; + for (int i = 1; i <= a.Length; i++) + for (int j = 1; j <= b.Length; j++) + { + int cost = a[i - 1] == b[j - 1] ? 0 : 1; + dp[i, j] = Math.Min(Math.Min(dp[i - 1, j] + 1, dp[i, j - 1] + 1), dp[i - 1, j - 1] + cost); + } + return dp[a.Length, b.Length]; + } + + private static IReadOnlyDictionary LoadEmbeddedMarkdown( + Assembly assembly, + string resourcePrefix) + { + var result = new Dictionary(StringComparer.Ordinal); + foreach (var resourceName in assembly.GetManifestResourceNames()) + { + if (!resourceName.StartsWith(resourcePrefix) || !resourceName.EndsWith(".md")) + continue; + + var skillId = resourceName[resourcePrefix.Length..^3]; + using var stream = assembly.GetManifestResourceStream(resourceName); + if (stream is null) continue; + + using var reader = new StreamReader(stream); + result[skillId] = reader.ReadToEnd(); + } + return result; + } +}