diff --git a/plugins/mobile-apps/shared/references/dataverse-offline-api.md b/plugins/mobile-apps/shared/references/dataverse-offline-api.md index d9423620b..a9499eb4b 100644 --- a/plugins/mobile-apps/shared/references/dataverse-offline-api.md +++ b/plugins/mobile-apps/shared/references/dataverse-offline-api.md @@ -308,7 +308,7 @@ node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST \ |---|---|---| | `204` | Published | Continue | | `400 / 0x80071141` "circular relationship" | Profile's association graph contains a cycle (e.g., `account => task => account`) | Parse the path from the error message, remove ONE association (prompt user at this point), re-attempt publish | -| `400 / 0x80071140` "no relationships are specified" for a Related-only table | A profile item has `recorddistributioncriteria=0` (Related rows only) but no `mobileofflineprofileitemassociation` rows targeting it. Without those, the runtime would sync zero rows. **Empirical 2026-05-24**: server enforces this at publish time β€” and the validation seems to look at associations published in an earlier `publishedon` snapshot, not the current uncommitted ones. So for profiles being **retrofitted** (existing publish + late association additions), publish can fail even after associations are added. | Parse the table name from the error message. Two recoveries: (a) read and execute `${CLAUDE_SKILL_DIR}/../add-table-to-offline-profile/SKILL.md` with `--table ` to add ALL relevant inbound associations, not just one; (b) read and execute `${CLAUDE_SKILL_DIR}/../edit-offline-profile/SKILL.md` with `--table --scope 2 --me` to change scope away from Related-only. Prompt user to pick. | +| `400 / 0x80071140` "no relationships are specified" for a Related-only table | A profile item has `recorddistributioncriteria=0` (Related rows only) but no `mobileofflineprofileitemassociation` rows targeting it. Without those, the runtime would sync zero rows. **Empirical 2026-05-24**: server enforces this at publish time β€” and the validation seems to look at associations published in an earlier `publishedon` snapshot, not the current uncommitted ones. So for profiles being **retrofitted** (existing publish + late association additions), publish can fail even after associations are added. | Parse the table name from the error message. Two recoveries: (a) read and execute `${PLUGIN_ROOT}/skills/add-table-to-offline-profile/SKILL.md` with `--table ` to add ALL relevant inbound associations, not just one; (b) read and execute `${PLUGIN_ROOT}/skills/edit-offline-profile/SKILL.md` with `--table --scope 2 --me` to change scope away from Related-only. Prompt user to pick. | | `429` (rate limit) | Concurrent publish in progress on shared env | Back off via `dataverse-request.js` automatic retry; if still failing, fall through to the targeted-then-fallback pattern below | | Client timeout, but server-side committed | Shared envs can take longer than `dataverse-request.js`'s 4-retry budget allows | Verify by re-GET β€” if `componentstate=0` AND `publishedon` is non-null + recent, treat as success | diff --git a/plugins/mobile-apps/shared/references/offline-profile-reconciliation.md b/plugins/mobile-apps/shared/references/offline-profile-reconciliation.md index ebb22994d..410206d18 100644 --- a/plugins/mobile-apps/shared/references/offline-profile-reconciliation.md +++ b/plugins/mobile-apps/shared/references/offline-profile-reconciliation.md @@ -90,10 +90,10 @@ default = update now): On **Yes**, apply in this order (both are non-interactive when the target is explicit): 1. For each `missingTables[]` entry, read and execute - `${CLAUDE_SKILL_DIR}/../add-table-to-offline-profile/SKILL.md` with + `${PLUGIN_ROOT}/skills/add-table-to-offline-profile/SKILL.md` with `--table ` (or `--all-new` to add them all at once). 2. For each `tablesWithNewColumns[]` entry, read and execute - `${CLAUDE_SKILL_DIR}/../edit-offline-profile/SKILL.md` with + `${PLUGIN_ROOT}/skills/edit-offline-profile/SKILL.md` with `--table --columns add:`. Re-run the delta check afterward; expect `in-sync`. Surface any remaining `delta` as a diff --git a/plugins/mobile-apps/skills/add-connector/SKILL.md b/plugins/mobile-apps/skills/add-connector/SKILL.md index cfe606f1b..6578f459d 100644 --- a/plugins/mobile-apps/skills/add-connector/SKILL.md +++ b/plugins/mobile-apps/skills/add-connector/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion, Skill model: sonnet --- -**πŸ“‹ Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)** | **Connector reference: [connector-reference.md](${CLAUDE_SKILL_DIR}/../../shared/connector-reference.md)** β€” read both first. +**πŸ“‹ Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** | **Connector reference: [connector-reference.md](${PLUGIN_ROOT}/shared/connector-reference.md)** β€” read both first. # Add Connector (Generic) @@ -27,7 +27,7 @@ The native host runtime (`@microsoft/power-apps-native-host`) handles connector ### Step 1 β€” Check Memory Bank -Check for `memory-bank.md` per [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md). +Check for `memory-bank.md` per [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md). Also confirm we're inside a Power Apps mobile app: @@ -76,7 +76,7 @@ After `add-flow`, continue at Step 4 and inspect the generated service/model fil ### Step 3 β€” Add Connector -**First, get the connection ID or connection reference** (see [connector-reference.md](${CLAUDE_SKILL_DIR}/../../shared/connector-reference.md)): +**First, get the connection ID or connection reference** (see [connector-reference.md](${PLUGIN_ROOT}/shared/connector-reference.md)): Run the `/list-connections` skill with the connector API ID (for example `shared_office365users`). Capture the exact `connectionId` from `create-connection`, or the `connectionRef` from `list-connection-references` if the caller is solution-aware. If creation cannot complete in the CLI, direct the user to create one using the environment-specific Connections URL β€” construct it from the active environment ID in context (from `power.config.json` `environmentId` or a prior step): `https://make.powerapps.com/environments//connections` β†’ **+ New connection** β†’ search for the connector β†’ Create. diff --git a/plugins/mobile-apps/skills/add-datasource/SKILL.md b/plugins/mobile-apps/skills/add-datasource/SKILL.md index 2d70917ff..37255472a 100644 --- a/plugins/mobile-apps/skills/add-datasource/SKILL.md +++ b/plugins/mobile-apps/skills/add-datasource/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Grep, Glob, AskUserQuestion, Skill model: sonnet --- -**πŸ“‹ Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)** β€” read first. +**πŸ“‹ Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** β€” read first. # Add Data Source @@ -16,7 +16,7 @@ Router skill that understands the user's goal and connects them to the right dat ### Check Memory Bank -Check for `memory-bank.md` per [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md). +Check for `memory-bank.md` per [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md). ### Understand the Goal diff --git a/plugins/mobile-apps/skills/add-dataverse/SKILL.md b/plugins/mobile-apps/skills/add-dataverse/SKILL.md index 257135793..7e5621841 100644 --- a/plugins/mobile-apps/skills/add-dataverse/SKILL.md +++ b/plugins/mobile-apps/skills/add-dataverse/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion, EnterPlanMo model: opus --- -**πŸ“‹ Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)** β€” read first. +**πŸ“‹ Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** β€” read first. # Add Dataverse @@ -27,7 +27,7 @@ Confirm Power Apps mobile app: ```bash test -f power.config.json && test -f app.config.js -node "${CLAUDE_SKILL_DIR}/../../scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")" +node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")" ``` Capture the **environment URL** (`https://orgXXX.crm.dynamics.com`), **environment ID**, and **tenant ID** from `resolve-environment.js` β€” needed for Step 3. If only the environment URL is available, pass that URL instead of the ID. @@ -146,7 +146,7 @@ Required only if creating or extending tables. Skip to Step 5 for read-only `add `npx power-apps` and `az` authenticate independently β€” they can point to different accounts. Verify `power.config.json` resolves and `az` can token for the target tenant before making any Dataverse API calls: ```bash -ENV_JSON=$(node "${CLAUDE_SKILL_DIR}/../../scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")") +ENV_JSON=$(node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")") echo "$ENV_JSON" az account show --query "{user: user.name, tenant: tenantId}" -o json ``` @@ -176,7 +176,7 @@ If empty, instruct `az login` and stop. **Script invocation contract β€” read this once, all subsequent calls in this skill follow it:** ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" \ [--body ''] [--include-headers] \ --tenant-id '' ``` @@ -193,7 +193,7 @@ If the tenant is unknown, omit `--tenant-id` β€” discovery still works, it is ju Acquire a Dataverse access token and verify connectivity: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET WhoAmI \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET WhoAmI \ --tenant-id '' ``` @@ -203,7 +203,7 @@ priority over shell environment variables and Azure account discovery. `WhoAmI` is the Dataverse identity endpoint β€” capital W/A/I (case-sensitive). The response gives `UserId`, `BusinessUnitId`, `OrganizationId` but **does NOT include the publisher prefix**. To get the publisher prefix, query the solution's publisher (defaults to `Default`; pass a different solution name if the env uses a custom solution): ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/detect-publisher-prefix.js" [solutionName] \ +node "${PLUGIN_ROOT}/scripts/detect-publisher-prefix.js" [solutionName] \ --tenant-id '' # solutionName defaults to "Default" if omitted ``` @@ -220,7 +220,7 @@ the resolved environment, tenant (when available), publisher, solution, current plan bytes, structured-schema bytes, and fresh reconciliation bytes: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/build-dataverse-operation-manifest.js" \ +node "${PLUGIN_ROOT}/scripts/build-dataverse-operation-manifest.js" \ --validate "" \ --contract "" \ --approval-receipt "" \ @@ -273,7 +273,7 @@ summary. Do not use the custom-table list as the source of truth, and do not issue one request per table. Fetch **every** plan entry (`Reuse`, `Extend`, or `Create`) β€” including standard and managed dependencies β€” in a **single** filtered query that also expands their columns: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET \ "EntityDefinitions?\$select=MetadataId,LogicalName,SchemaName,IsCustomEntity,IsManaged,IsCustomizable,CanCreateAttributes,PrimaryIdAttribute,PrimaryNameAttribute&\$filter=LogicalName eq '' or LogicalName eq ''&\$expand=Attributes(\$select=MetadataId,LogicalName,AttributeType,AttributeTypeName,RequiredLevel,IsManaged,IsCustomizable,IsPrimaryId,IsPrimaryName,SourceType,SourceTypeMask)" \ --tenant-id '' ``` @@ -325,7 +325,7 @@ semantics. Before classifying any such existing column as compatible: `/.tmp/derived-metadata-operations.json`, then run: ```bash - node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" \ + node "${PLUGIN_ROOT}/scripts/dataverse-request.js" \ BATCH-METADATA derived-reconciliation \ --operations "$(cat /.tmp/derived-metadata-operations.json)" \ --tenant-id '' @@ -406,7 +406,7 @@ EXECUTION_JOURNAL="/.tmp/dataverse-metadata-execution-journal.json" ALL_MANIFEST_OPERATIONS="/.tmp/dataverse-operation-all.json" # Write the flattened operations from every manifest phase to ALL_MANIFEST_OPERATIONS once. -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" \ BATCH-METADATA "manifest-" \ --operations "$(cat /.tmp/dataverse-operation-phase-.json)" \ --solution "" \ @@ -474,7 +474,7 @@ Bind the contract through that pre-existing receipt, then roll the existing publish checkpoint forward: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/build-dataverse-operation-manifest.js" \ +node "${PLUGIN_ROOT}/scripts/build-dataverse-operation-manifest.js" \ --roll-forward-checkpoint "$PUBLISH_CHECKPOINT" \ --previous-manifest "$OPERATION_MANIFEST" \ --journal "$EXECUTION_JOURNAL" \ @@ -524,7 +524,7 @@ issues requests strictly one at a time in array order, stopping on the first non-2xx response by default. ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" \ BATCH-METADATA schema-writes \ --operations '' \ --solution '' \ @@ -555,7 +555,7 @@ Before each create, confirm the target name is actually free: name-prefix collis Only re-probe a single name when Step 4's batch did not cover it (for example a rename candidate generated later in this step): ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET \ "EntityDefinitions(LogicalName='_')?\$select=MetadataId,LogicalName,IsCustomEntity,IsManaged,IsCustomizable,CanCreateAttributes" \ --tenant-id '' ``` @@ -689,7 +689,7 @@ For each `Create` decision, in **tier order** (Tier 0 β†’ Tier 1 β†’ Tier 2 β†’ **Scratch files:** When writing request body JSON to disk (e.g. table definitions, column metadata, relationship payloads), always write to `/.tmp/`, never to `/tmp/`. Keeping request bodies project-local prevents cross-project writes and makes cleanup deterministic. Create the folder if it doesn't exist: `mkdir -p /.tmp`. ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" POST EntityDefinitions \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST EntityDefinitions \ --body '' \ --solution '' \ --tenant-id '' @@ -769,7 +769,7 @@ For each `Extend` decision, POST a new column to the existing table. > **⚠️ Table-level pre-flight (HARD β€” required for idempotent re-runs).** Reuse the complete attribute snapshot fetched for this table in Step 4. If the table was discovered only during collision recovery, or no current snapshot exists, fetch all attributes exactly once: > > ```bash -> node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET \ +> node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET \ > "EntityDefinitions(LogicalName='
')/Attributes?\$select=MetadataId,LogicalName,SchemaName,AttributeType,AttributeTypeName,RequiredLevel,IsManaged,IsCustomizable,IsPrimaryId,IsPrimaryName" \ > --tenant-id '' > ``` @@ -787,7 +787,7 @@ For each `Extend` decision, POST a new column to the existing table. After the complete comparison passes, POST the missing-column queue **one column at a time, sequentially** (no `$batch`; always pass `--solution`): ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" POST \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST \ "EntityDefinitions(LogicalName='
')/Attributes" \ --body '' \ --solution '' \ @@ -837,7 +837,7 @@ Column shapes that have non-obvious gotchas (handle carefully): Invocation (apiPath is `RelationshipDefinitions`, body via `--body`, always pass `--solution`): ```bash - node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" POST \ + node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST \ RelationshipDefinitions \ --body '' \ --solution '' \ @@ -959,7 +959,7 @@ https://learn.microsoft.com/power-apps/developer/data-platform/specialized-colum **Do NOT use the `CreateEntityKey` action route.** In practice it can return 404 depending on route shape / environment. The reliable metadata route is POSTing to the table's `Keys` navigation collection: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" POST \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST \ "EntityDefinitions(LogicalName='
')/Keys" \ --body '' \ --solution '' \ @@ -980,7 +980,7 @@ Body skeleton: **Pre-flight each key before POST** so re-runs are idempotent. A key can only pre-exist on a table that already existed at Step 4, so for a table created in this run, skip straight to the POST. Otherwise read `Keys` from that table's Step 4 snapshot. Query it directly only when the snapshot did not cover that table: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET \ "EntityDefinitions(LogicalName='
')?\$select=LogicalName&\$expand=Keys(\$select=SchemaName,KeyAttributes,EntityKeyIndexStatus)" \ --tenant-id '' ``` @@ -1038,7 +1038,7 @@ publish retry. Only after **every** Step 5 metadata POST and **every** Step 6 `npx power-apps add-data-source` has returned successfully, publish so the new tables and columns are available to the runtime. `PublishXml` takes the same exclusive metadata lock as the create/extend calls β€” do not run it concurrently with anything from Steps 5 or 6. ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" POST \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST \ "PublishXml" \ --body "{\"ParameterXml\":\"cr123_table1cr123_table2\"}" \ --tenant-id '' @@ -1053,7 +1053,7 @@ If the publish call returns a non-2xx status, report the error and stop β€” do n Confirm every created or extended table is queryable after publish with **one** filtered query, not one request per table: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET \ "EntityDefinitions?\$select=LogicalName,DisplayName&\$filter=LogicalName eq '' or LogicalName eq ''" \ --tenant-id '' ``` @@ -1175,16 +1175,16 @@ A schema change here (new table or new column) can leave an existing Mobile Offl Otherwise (manual `/add-dataverse`), run the local, no-network delta check: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/offline-profile-delta.js" +node "${PLUGIN_ROOT}/scripts/offline-profile-delta.js" ``` -Branch on the JSON `status` per [offline-profile-reconciliation.md](${CLAUDE_SKILL_DIR}/../../shared/references/offline-profile-reconciliation.md): +Branch on the JSON `status` per [offline-profile-reconciliation.md](${PLUGIN_ROOT}/shared/references/offline-profile-reconciliation.md): | `status` | Action | |---|---| | `no-manifest` / `no-profile` / `in-sync` | Continue to Step 9 silently. For `no-profile` (no offline profile exists) do not nag β€” the app may not use offline. | | `error` | `offline-profile.json` is unreadable β€” the script prints `status: error` and **exits non-zero**. Do NOT treat this as an `/add-dataverse` failure (the tables are already created): surface the `error` string, **skip reconciliation** (never drive the update workflows against a corrupt file), and finish with `DONE_WITH_CONCERNS` telling the user to fix `offline-profile.json`. | -| `delta` | Prompt the user (one `AskUserQuestion`, default = update now) to add the missing tables / new columns. For `missingTables[]`, read and execute `${CLAUDE_SKILL_DIR}/../add-table-to-offline-profile/SKILL.md`; for `tablesWithNewColumns[]`, read and execute `${CLAUDE_SKILL_DIR}/../edit-offline-profile/SKILL.md` with `--table --columns add:`. Re-run the delta check; it should read `in-sync`. Follow the exact prompt + ordering in the reconciliation reference. | +| `delta` | Prompt the user (one `AskUserQuestion`, default = update now) to add the missing tables / new columns. For `missingTables[]`, read and execute `${PLUGIN_ROOT}/skills/add-table-to-offline-profile/SKILL.md`; for `tablesWithNewColumns[]`, read and execute `${PLUGIN_ROOT}/skills/edit-offline-profile/SKILL.md` with `--table --columns add:`. Re-run the delta check; it should read `in-sync`. Follow the exact prompt + ordering in the reconciliation reference. | ### Step 9 β€” Summary diff --git a/plugins/mobile-apps/skills/add-native/SKILL.md b/plugins/mobile-apps/skills/add-native/SKILL.md index baeef2906..44952dbd8 100644 --- a/plugins/mobile-apps/skills/add-native/SKILL.md +++ b/plugins/mobile-apps/skills/add-native/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion model: sonnet --- -**πŸ“‹ Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)** β€” read first. +**πŸ“‹ Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** β€” read first. # Add Native Capability @@ -23,7 +23,7 @@ Generate a one-file typed wrapper under `src/native/` for a native device capabi Some capabilities have a dedicated implementation helper that does more than a plain wrapper (camera writes scanner/upload helpers; PDF report/viewer helpers enforce local-vs-HTTPS boundaries; pen has native-control-specific validation). Users should still call `/add-native ` for native controls. When a dedicated implementation exists, **run it internally and do not ask the user to run that helper directly**. -**Lookup convention:** after normalizing the capability, first check the internal-helper map below. For `camera`, `image-picker`, `barcode-scanner`, and `qr-scanner`, read and execute `${CLAUDE_SKILL_DIR}/add-camera/SKILL.md` inside this `/add-native` invocation. For `pdf-report`, read and execute `${CLAUDE_SKILL_DIR}/add-pdf-report/SKILL.md`. For `pdf-viewer`, read and execute `${CLAUDE_SKILL_DIR}/add-pdf-viewer/SKILL.md`. For `pen-input`, read and execute `${CLAUDE_SKILL_DIR}/add-pen-input/SKILL.md`. For `geolocation`, read and execute `${CLAUDE_SKILL_DIR}/add-geolocation/SKILL.md`. If no helper exists, fall through to this skill's inline wrapper flow. +**Lookup convention:** after normalizing the capability, first check the internal-helper map below. For `camera`, `image-picker`, `barcode-scanner`, and `qr-scanner`, read and execute `${PLUGIN_ROOT}/skills/add-native/add-camera/SKILL.md` inside this `/add-native` invocation. For `pdf-report`, read and execute `${PLUGIN_ROOT}/skills/add-native/add-pdf-report/SKILL.md`. For `pdf-viewer`, read and execute `${PLUGIN_ROOT}/skills/add-native/add-pdf-viewer/SKILL.md`. For `pen-input`, read and execute `${PLUGIN_ROOT}/skills/add-native/add-pen-input/SKILL.md`. For `geolocation`, read and execute `${PLUGIN_ROOT}/skills/add-native/add-geolocation/SKILL.md`. If no helper exists, fall through to this skill's inline wrapper flow. Current dedicated implementations: @@ -199,11 +199,11 @@ For normalized `camera`, `image-picker`, `barcode-scanner`, `qr-scanner`, `pdf-r ```bash case "" in - camera|image-picker|barcode-scanner|qr-scanner) test -f "${CLAUDE_SKILL_DIR}/add-camera/SKILL.md" && echo "INTERNAL_HELPER:add-camera" ;; - pdf-report) test -f "${CLAUDE_SKILL_DIR}/add-pdf-report/SKILL.md" && echo "INTERNAL_HELPER:add-pdf-report" ;; - pdf-viewer) test -f "${CLAUDE_SKILL_DIR}/add-pdf-viewer/SKILL.md" && echo "INTERNAL_HELPER:add-pdf-viewer" ;; - pen-input) test -f "${CLAUDE_SKILL_DIR}/add-pen-input/SKILL.md" && echo "INTERNAL_HELPER:add-pen-input" ;; - geolocation) test -f "${CLAUDE_SKILL_DIR}/add-geolocation/SKILL.md" && echo "INTERNAL_HELPER:add-geolocation" ;; + camera|image-picker|barcode-scanner|qr-scanner) test -f "${PLUGIN_ROOT}/skills/add-native/add-camera/SKILL.md" && echo "INTERNAL_HELPER:add-camera" ;; + pdf-report) test -f "${PLUGIN_ROOT}/skills/add-native/add-pdf-report/SKILL.md" && echo "INTERNAL_HELPER:add-pdf-report" ;; + pdf-viewer) test -f "${PLUGIN_ROOT}/skills/add-native/add-pdf-viewer/SKILL.md" && echo "INTERNAL_HELPER:add-pdf-viewer" ;; + pen-input) test -f "${PLUGIN_ROOT}/skills/add-native/add-pen-input/SKILL.md" && echo "INTERNAL_HELPER:add-pen-input" ;; + geolocation) test -f "${PLUGIN_ROOT}/skills/add-native/add-geolocation/SKILL.md" && echo "INTERNAL_HELPER:add-geolocation" ;; *) echo "INLINE" ;; esac ``` @@ -321,4 +321,4 @@ Sample usage: - This skill never modifies `package.json`, `app.config.js`, `src/playerConfig.ts`, `src/generated/`, or any screen file. - For capabilities not in the supported table (`expo-notifications`, Bluetooth, NFC, BLE, AR β€” until the template adds them), tell the user the template doesn't ship them yet β€” file a request at the upstream template repo. Do NOT attempt to install or configure anything yourself. -- Pure-JavaScript libraries are out of scope for this skill. `/create-mobile-app` or `/edit-app` selects and installs them through [`shared/references/javascript-dependency-planning.md`](${CLAUDE_SKILL_DIR}/../../shared/references/javascript-dependency-planning.md); no native wrapper or Android/iOS rebuild is needed. The prohibition above applies only to packages with native source/config. +- Pure-JavaScript libraries are out of scope for this skill. `/create-mobile-app` or `/edit-app` selects and installs them through [`shared/references/javascript-dependency-planning.md`](${PLUGIN_ROOT}/shared/references/javascript-dependency-planning.md); no native wrapper or Android/iOS rebuild is needed. The prohibition above applies only to packages with native source/config. diff --git a/plugins/mobile-apps/skills/add-native/add-camera/SKILL.md b/plugins/mobile-apps/skills/add-native/add-camera/SKILL.md index 4e67548a7..2cbb44fc0 100644 --- a/plugins/mobile-apps/skills/add-native/add-camera/SKILL.md +++ b/plugins/mobile-apps/skills/add-native/add-camera/SKILL.md @@ -7,11 +7,11 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion model: sonnet --- -**Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../../shared/shared-instructions.md)** β€” read first. +**Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** β€” read first. **References:** -- [dataverse-reference.md](${CLAUDE_SKILL_DIR}/../../add-dataverse/references/dataverse-reference.md) β€” File/image column upload patterns (Step 7–8) +- [dataverse-reference.md](${PLUGIN_ROOT}/skills/add-dataverse/references/dataverse-reference.md) β€” File/image column upload patterns (Step 7–8) # Add Camera diff --git a/plugins/mobile-apps/skills/add-native/add-geolocation/SKILL.md b/plugins/mobile-apps/skills/add-native/add-geolocation/SKILL.md index 719e80b14..a4eb6ce75 100644 --- a/plugins/mobile-apps/skills/add-native/add-geolocation/SKILL.md +++ b/plugins/mobile-apps/skills/add-native/add-geolocation/SKILL.md @@ -7,7 +7,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion model: sonnet --- -**Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../../shared/shared-instructions.md)** - read first. +**Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** - read first. # Add Geolocation @@ -42,11 +42,11 @@ msdyn_locationrecords Do not ask the user for a table name and do not invent a custom table. `msdyn_locationrecords` must already exist before the control can be used. ```bash -ENV_JSON=$(node "${CLAUDE_SKILL_DIR}/../../../scripts/resolve-environment.js" "$(node -e "console.log(require('./power.config.json').environmentId)")") +ENV_JSON=$(node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$(node -e "console.log(require('./power.config.json').environmentId)")") ENV_URL=$(node -e "const j=JSON.parse(process.argv[1]); process.stdout.write(j.environmentUrl || '')" "$ENV_JSON") TABLE="msdyn_locationrecords" -node "${CLAUDE_SKILL_DIR}/../../../scripts/dataverse-request.js" "$ENV_URL" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" "$ENV_URL" GET \ "EntityDefinitions?\$filter=EntitySetName eq '$TABLE'&\$select=LogicalName,EntitySetName" ``` @@ -58,7 +58,7 @@ Required result: When the table exists, verify every mapped column exists: ```bash -node "${CLAUDE_SKILL_DIR}/../../../scripts/dataverse-request.js" "$ENV_URL" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" "$ENV_URL" GET \ "EntityDefinitions(LogicalName='')/Attributes?\$select=LogicalName,AttributeType" ``` diff --git a/plugins/mobile-apps/skills/add-native/add-pdf-report/SKILL.md b/plugins/mobile-apps/skills/add-native/add-pdf-report/SKILL.md index 98ea612b3..c0c79c697 100644 --- a/plugins/mobile-apps/skills/add-native/add-pdf-report/SKILL.md +++ b/plugins/mobile-apps/skills/add-native/add-pdf-report/SKILL.md @@ -7,7 +7,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion model: sonnet --- -**Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../../shared/shared-instructions.md)** - read first. +**Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** - read first. # Add PDF Report diff --git a/plugins/mobile-apps/skills/add-native/add-pdf-viewer/SKILL.md b/plugins/mobile-apps/skills/add-native/add-pdf-viewer/SKILL.md index 8eca75797..4461d6c4a 100644 --- a/plugins/mobile-apps/skills/add-native/add-pdf-viewer/SKILL.md +++ b/plugins/mobile-apps/skills/add-native/add-pdf-viewer/SKILL.md @@ -7,7 +7,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion model: sonnet --- -**πŸ“‹ Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../../shared/shared-instructions.md)** β€” read first. +**πŸ“‹ Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** β€” read first. # Add PDF Viewer diff --git a/plugins/mobile-apps/skills/add-native/add-pen-input/SKILL.md b/plugins/mobile-apps/skills/add-native/add-pen-input/SKILL.md index f33f7a867..2c06d3eb3 100644 --- a/plugins/mobile-apps/skills/add-native/add-pen-input/SKILL.md +++ b/plugins/mobile-apps/skills/add-native/add-pen-input/SKILL.md @@ -7,7 +7,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion model: sonnet --- -**πŸ“‹ Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../../shared/shared-instructions.md)** β€” read first. +**πŸ“‹ Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** β€” read first. # Add Pen Input diff --git a/plugins/mobile-apps/skills/add-sample-data/SKILL.md b/plugins/mobile-apps/skills/add-sample-data/SKILL.md index 8a0921e3e..b2beb33ef 100644 --- a/plugins/mobile-apps/skills/add-sample-data/SKILL.md +++ b/plugins/mobile-apps/skills/add-sample-data/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion model: sonnet --- -**πŸ“‹ Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)** β€” read first. +**πŸ“‹ Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** β€” read first. # Add Sample Data @@ -17,7 +17,7 @@ Populate Dataverse tables with realistic sample records so a freshly-scaffolded - **Coverage over volume β€” every table in the manifest gets seeded.** The #1 failure mode of a freshly-scaffolded code app is a home / dashboard / list screen that renders an empty state on first launch because its source table has zero rows. An empty downstream table is **worse than a 3-row table.** Default to minimal-but-complete: small counts everywhere, no table left empty. Volume is a secondary knob β€” coverage is the contract. - **Insertion order matters.** Parent / referenced tables must be inserted before child / referencing tables so lookup IDs are available. - **Contextual data, not Lorem Ipsum.** Generate values that match column names + types. A `cr3e9_sitename` column in an inspection app gets "Westside Construction Site", not "Sample Name 1". -- **Scenario-aware rows.** Read `native-app-plan.md`, especially `### Shared Conventions` and per-screen `Operational pattern` values defined in [screen-templates.md](${CLAUDE_SKILL_DIR}/../../shared/references/screen-templates.md). Seed rows should exercise the app's actual workflow: statuses, dates, relationships, priority/severity, media metadata, and edge cases that make the planned first viewport light up. +- **Scenario-aware rows.** Read `native-app-plan.md`, especially `### Shared Conventions` and per-screen `Operational pattern` values defined in [screen-templates.md](${PLUGIN_ROOT}/shared/references/screen-templates.md). Seed rows should exercise the app's actual workflow: statuses, dates, relationships, priority/severity, media metadata, and edge cases that make the planned first viewport light up. - **Fail gracefully.** On insertion failure, log the error and continue with remaining records β€” never auto-rollback. The user can re-run after fixing the issue. - **Idempotent re-runs.** If a previous run partially completed, the second run reads `memory-bank.md`'s seeded-data table and skips records already inserted. - **Solution-scoped inserts.** Always pass `--solution ` so records land in our solution, not the default. @@ -51,7 +51,7 @@ If a seed file cannot be mapped safely, fall back to generated contextual sample ```bash test -f power.config.json && test -f app.config.js -node "${CLAUDE_SKILL_DIR}/../../scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")" +node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")" ``` Capture the **environment URL** for subsequent script calls. If resolution fails, instruct `az login --tenant ` or ask for the environment URL directly, then stop. @@ -85,14 +85,14 @@ Skip Step 2b. If `.datamodel-manifest.json` is missing, discover custom tables via the script: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET \ "EntityDefinitions?\$select=LogicalName,DisplayName,EntitySetName&\$filter=IsCustomEntity eq true" ``` For each table the project uses, fetch its custom columns: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET \ "EntityDefinitions(LogicalName='
')/Attributes?\$select=LogicalName,DisplayName,AttributeType,RequiredLevel&\$filter=IsCustomAttribute eq true" ``` @@ -107,7 +107,7 @@ All tables from the manifest are evaluated β€” including reused ones β€” because For each table, query its current record count using the entity set name from the manifest (or derive it by appending `s` to the logical name as a fallback): ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET \ "?\$top=5&\$select=" ``` @@ -224,7 +224,7 @@ For each selected table, generate N rows. Match values to column names + types: For every choice column in the selected tables, query its option set before generating rows: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET \ "EntityDefinitions(LogicalName='
')/Attributes(LogicalName='')/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?\$expand=OptionSet" ``` @@ -268,7 +268,7 @@ Insert in the tier order from Step 3c. **Within a tier, parallelize across both For each table, get its `EntitySetName` (the URL-path name, usually plural β€” e.g. `cr3e9_jobsites`). Read from `.datamodel-manifest.json` if it carries this; otherwise: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET \ "EntityDefinitions(LogicalName='
')?\$select=EntitySetName" ``` @@ -296,7 +296,7 @@ For each tier from 0 β†’ N: 2. **Fire BATCH-RECORDS once per tier.** The script handles concurrency, retry, GUID extraction, and adaptive throttling internally: ```bash - node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" BATCH-RECORDS \ + node "${PLUGIN_ROOT}/scripts/dataverse-request.js" BATCH-RECORDS \ "Tier " \ --operations '' \ --concurrency 5 \ @@ -334,7 +334,7 @@ For Tier 1+ tables, build each row's body with `@odata.bind` referencing the par Then: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" BATCH-RECORDS \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" BATCH-RECORDS \ "Tier 1" \ --operations '' \ --concurrency 5 \ diff --git a/plugins/mobile-apps/skills/add-sharepoint/SKILL.md b/plugins/mobile-apps/skills/add-sharepoint/SKILL.md index a44c27a33..712c69f05 100644 --- a/plugins/mobile-apps/skills/add-sharepoint/SKILL.md +++ b/plugins/mobile-apps/skills/add-sharepoint/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion, EnterPlanMo model: sonnet --- -**πŸ“‹ Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)** β€” read first. +**πŸ“‹ Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** β€” read first. **References:** @@ -27,7 +27,7 @@ Two paths: **existing lists** (skip to Step 6) or **new lists** (full workflow). ### Step 1: Check Memory Bank -Check for `memory-bank.md` per [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md). +Check for `memory-bank.md` per [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md). Also confirm this is a mobile app: @@ -94,7 +94,7 @@ Get explicit confirmation before creating. Use safe functions from [list-managem ### Step 6: Get Connection ID -Get the SharePoint Online connection ID (see [connector-reference.md](${CLAUDE_SKILL_DIR}/../../shared/connector-reference.md)): +Get the SharePoint Online connection ID (see [connector-reference.md](${PLUGIN_ROOT}/shared/connector-reference.md)): ```bash npx power-apps create-connection --api-id shared_sharepointonline --json @@ -151,7 +151,7 @@ Run once per list or document library. **Read [sharepoint-reference.md](./references/sharepoint-reference.md) before writing any SharePoint code** β€” column encoding, choice fields, and lookups have critical gotchas. -Use `Grep` to find methods in `src/generated/services/SharePointOnlineService.ts` (generated files can be very large β€” see [connector-reference.md](${CLAUDE_SKILL_DIR}/../../shared/connector-reference.md#inspecting-large-generated-files)). +Use `Grep` to find methods in `src/generated/services/SharePointOnlineService.ts` (generated files can be very large β€” see [connector-reference.md](${PLUGIN_ROOT}/shared/connector-reference.md#inspecting-large-generated-files)). Sample usage: diff --git a/plugins/mobile-apps/skills/add-table-to-offline-profile/SKILL.md b/plugins/mobile-apps/skills/add-table-to-offline-profile/SKILL.md index 207946591..3117305f0 100644 --- a/plugins/mobile-apps/skills/add-table-to-offline-profile/SKILL.md +++ b/plugins/mobile-apps/skills/add-table-to-offline-profile/SKILL.md @@ -7,12 +7,12 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion model: sonnet --- -**Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)** β€” read first. +**Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** β€” read first. **References:** -- [dataverse-offline-api.md](${CLAUDE_SKILL_DIR}/../../shared/references/dataverse-offline-api.md) Β§4 + Β§7 β€” POST item + PATCH selectedcolumns -- [offline-profile-reconciliation.md](${CLAUDE_SKILL_DIR}/../../shared/references/offline-profile-reconciliation.md) β€” the `schemaColumns` baseline written in Step 7 +- [dataverse-offline-api.md](${PLUGIN_ROOT}/shared/references/dataverse-offline-api.md) Β§4 + Β§7 β€” POST item + PATCH selectedcolumns +- [offline-profile-reconciliation.md](${PLUGIN_ROOT}/shared/references/offline-profile-reconciliation.md) β€” the `schemaColumns` baseline written in Step 7 # Add Table to Offline Profile @@ -30,7 +30,7 @@ Equivalent to running `/setup-offline-profile` and seeing the existing profile ( ```bash test -f power.config.json -node "${CLAUDE_SKILL_DIR}/../../scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")" +node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")" ``` Manifest path (dual-location): @@ -62,7 +62,7 @@ For bulk `--all-new` mode, loop through Steps 3-6 for each missing table; each r Query the target table's `EntityMetadata`: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET \ "EntityDefinitions(LogicalName='
')?\$select=IsAvailableOffline,ChangeTrackingEnabled,OwnershipType,IsCustomizable" ``` @@ -94,7 +94,7 @@ Recommendation in the question body should be derived from the architect's heuri #### Step 5a β€” POST profile item ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" POST \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST \ "mobileofflineprofileitems" \ --body '{ "name": "", @@ -116,10 +116,10 @@ Capture `itemId` from `OData-EntityId` response header. After the new item is created, walk the new table's relationships (from the manifest's `lookups[]` + a `EntityDefinitions(LogicalName='
')/ManyToOneRelationships` query) and POST one `mobileofflineprofileitemassociation` per relationship whose target is ALREADY in the profile. -Recipe per [shared/references/dataverse-offline-api.md Β§5–§6](${CLAUDE_SKILL_DIR}/../../shared/references/dataverse-offline-api.md): +Recipe per [shared/references/dataverse-offline-api.md Β§5–§6](${PLUGIN_ROOT}/shared/references/dataverse-offline-api.md): ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" POST \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST \ "mobileofflineprofileitemassociations" \ --body '{ "name": "", @@ -136,10 +136,10 @@ For new tables that are `recorddistributioncriteria: 0` (Related rows only), at #### Step 5c β€” PATCH selectedcolumns -Build `selectedcolumns` using the deterministic union from [offline-profile-architect.md](${CLAUDE_SKILL_DIR}/../../agents/offline-profile-architect.md) Step 6 (always-include βˆͺ lookups βˆͺ screen-grep'd, dedupe, sort). +Build `selectedcolumns` using the deterministic union from [offline-profile-architect.md](${PLUGIN_ROOT}/agents/offline-profile-architect.md) Step 6 (always-include βˆͺ lookups βˆͺ screen-grep'd, dedupe, sort). ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" PATCH \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" PATCH \ "mobileofflineprofileitems()" \ --body '{"selectedcolumns":"{\"Columns\":[...]}"}' ``` @@ -147,17 +147,17 @@ node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" PATCH \ ### Step 6 β€” Publish (targeted PublishXml) ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" POST \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST \ "PublishXml" --body '{ "ParameterXml": "'"$PROFILE_ID"'" }' ``` -Publishes only this profile, not the entire org's customizations. See [shared/references/dataverse-offline-api.md Β§9](${CLAUDE_SKILL_DIR}/../../shared/references/dataverse-offline-api.md) for `0x80071141` circular-relationship handling + `PublishAllXml` fallback. +Publishes only this profile, not the entire org's customizations. See [shared/references/dataverse-offline-api.md Β§9](${PLUGIN_ROOT}/shared/references/dataverse-offline-api.md) for `0x80071141` circular-relationship handling + `PublishAllXml` fallback. ### Step 7 β€” Update artifacts -Append the new table's entry to `offline-profile.json` `tables[]`. The entry MUST include a `schemaColumns` array β€” the added table's full set of schema column logical names from `.datamodel-manifest.json` at this moment. This is the schema-reconciliation baseline that `scripts/offline-profile-delta.js` diffs future manifest changes against; omitting it makes the lifecycle delta check report this table under `columnBaselineMissing` until it is re-reconciled. It is distinct from `selectedColumns` (the curated sync set) β€” see [offline-profile-reconciliation.md](${CLAUDE_SKILL_DIR}/../../shared/references/offline-profile-reconciliation.md). Match the canonical entry shape in [`/setup-offline-profile` Step 9a](../setup-offline-profile/SKILL.md). +Append the new table's entry to `offline-profile.json` `tables[]`. The entry MUST include a `schemaColumns` array β€” the added table's full set of schema column logical names from `.datamodel-manifest.json` at this moment. This is the schema-reconciliation baseline that `scripts/offline-profile-delta.js` diffs future manifest changes against; omitting it makes the lifecycle delta check report this table under `columnBaselineMissing` until it is re-reconciled. It is distinct from `selectedColumns` (the curated sync set) β€” see [offline-profile-reconciliation.md](${PLUGIN_ROOT}/shared/references/offline-profile-reconciliation.md). Match the canonical entry shape in [`/setup-offline-profile` Step 9a](../setup-offline-profile/SKILL.md). Append to `memory-bank.md` `## Offline profile` block: diff --git a/plugins/mobile-apps/skills/assign-offline-profile/SKILL.md b/plugins/mobile-apps/skills/assign-offline-profile/SKILL.md index 079b5d12f..0b66b3b8d 100644 --- a/plugins/mobile-apps/skills/assign-offline-profile/SKILL.md +++ b/plugins/mobile-apps/skills/assign-offline-profile/SKILL.md @@ -6,12 +6,12 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion model: sonnet --- -**Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)** β€” read first. +**Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** β€” read first. **References:** -- [offline-profile-schema.md](${CLAUDE_SKILL_DIR}/../../shared/references/offline-profile-schema.md) β€” `usermobileofflineprofilemembership` / `teammobileofflineprofilemembership` entity field map -- [dataverse-offline-api.md](${CLAUDE_SKILL_DIR}/../../shared/references/dataverse-offline-api.md) β€” Web API recipe (Β§12 β€” membership POSTs) +- [offline-profile-schema.md](${PLUGIN_ROOT}/shared/references/offline-profile-schema.md) β€” `usermobileofflineprofilemembership` / `teammobileofflineprofilemembership` entity field map +- [dataverse-offline-api.md](${PLUGIN_ROOT}/shared/references/dataverse-offline-api.md) β€” Web API recipe (Β§12 β€” membership POSTs) # Assign Offline Profile @@ -29,7 +29,7 @@ Per the maker portal's UX (the "Assign profile to user" dialog under env setting ```bash test -f power.config.json -node "${CLAUDE_SKILL_DIR}/../../scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")" +node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")" ``` Profile ID resolution (in order): @@ -79,11 +79,11 @@ For idempotency: ```bash # Existing user memberships for this profile -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET \ "usermobileofflineprofilememberships?\$filter=_mobileofflineprofileid_value eq &\$select=usermobileofflineprofilemembershipid,_systemuserid_value&\$expand=systemuserid_systemuser(\$select=domainname)" # Existing team memberships for this profile -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET \ "teammobileofflineprofilememberships?\$filter=_mobileofflineprofileid_value eq &\$select=teammobileofflineprofilemembershipid,_teamid_value&\$expand=teamid_team(\$select=name)" ``` @@ -92,10 +92,10 @@ Build the set of `already-bound` UPNs and team names. For each candidate user/team from Step 2, look up their `systemuserid` / `teamid` (skip if already in `already-bound`): ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET \ "systemusers?\$filter=domainname eq ''&\$select=systemuserid,fullname,domainname&\$top=1" -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET \ "teams?\$filter=name eq '' and teamtype eq 0&\$select=teamid,name&\$top=1" ``` @@ -146,7 +146,7 @@ For each in `to_add`, POST sequentially (parallel POSTs occasionally return 429) **User membership:** ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" POST \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST \ "usermobileofflineprofilememberships" \ --body '{ "MobileOfflineProfileId@odata.bind": "/mobileofflineprofiles()", @@ -160,7 +160,7 @@ Expected 204 with `OData-EntityId` β†’ capture membership GUID. **Team membership:** ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" POST \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST \ "teammobileofflineprofilememberships" \ --body '{ "MobileOfflineProfileId@odata.bind": "/mobileofflineprofiles()", @@ -172,7 +172,7 @@ node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" POST \ For each in `to_remove`, DELETE: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" DELETE \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" DELETE \ "usermobileofflineprofilememberships()" ``` diff --git a/plugins/mobile-apps/skills/create-mobile-app/SKILL.md b/plugins/mobile-apps/skills/create-mobile-app/SKILL.md index 4df8cc170..7175f1e10 100644 --- a/plugins/mobile-apps/skills/create-mobile-app/SKILL.md +++ b/plugins/mobile-apps/skills/create-mobile-app/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion, Task, Enter model: opus --- -**πŸ“‹ Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)** β€” read first. Covers safety guardrails, memory bank usage, preferred-environment policy, connector-first rule, Windows CLI compat, command-failure handling. +**πŸ“‹ Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** β€” read first. Covers safety guardrails, memory bank usage, preferred-environment policy, connector-first rule, Windows CLI compat, command-failure handling. # Create Power Apps Code App (Native) @@ -90,7 +90,7 @@ After the resume check, run the **fresh-template gate** from the section above. After the fresh-template gate succeeds, or after the user confirms a resume, initialize the app identity before continuing: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/lib/app-identity.js" "" +node "${PLUGIN_ROOT}/scripts/lib/app-identity.js" "" ``` `app-identity.js` mints `app.json` `expo.extra.telemetry.appInstanceId` β€” a random per-project ID that lets usage telemetry tell this app apart from other apps built in the same session, and recognize it again in later sessions. It is idempotent, so a resume or re-run keeps the original ID. It contains no app name, path, or environment data. Commit it: it is the app's identity, not a per-machine cache. @@ -117,7 +117,7 @@ az account show --query "user.name" -o tsv # Azure CLI logged in (neede git --version # optional ``` -**Do NOT probe Xcode, Java, Android Studio, or CocoaPods here.** This plugin's flow is plan β†’ scaffold β†’ code β†’ local Expo dev server. Build + deploy (`npm run build` / `npx power-apps push`) is a separate user-driven step via the `/deploy` skill. Local native compile is the user's choice and lives outside this skill (run the platform-specific native command directly when needed). See [`shared/version-check.md`](${CLAUDE_SKILL_DIR}/../../shared/version-check.md) β€” only the **Always required** tier matters here. +**Do NOT probe Xcode, Java, Android Studio, or CocoaPods here.** This plugin's flow is plan β†’ scaffold β†’ code β†’ local Expo dev server. Build + deploy (`npm run build` / `npx power-apps push`) is a separate user-driven step via the `/deploy` skill. Local native compile is the user's choice and lives outside this skill (run the platform-specific native command directly when needed). See [`shared/version-check.md`](${PLUGIN_ROOT}/shared/version-check.md) β€” only the **Always required** tier matters here. | Missing | Action | |---|---| @@ -144,7 +144,7 @@ if [ -z "$TARGET_ENV" ] && [ -f power.config.json ]; then TARGET_ENV=$(node -e "try { const id=require('./power.config.json').environmentId || ''; console.log(id); } catch { console.log(''); }") fi test -n "$TARGET_ENV" || { echo "βœ— Environment missing. Provide an environment ID."; exit 2; } -ENV_JSON=$(node "${CLAUDE_SKILL_DIR}/../../scripts/resolve-environment.js" "$TARGET_ENV") +ENV_JSON=$(node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$TARGET_ENV") printf '%s\n' "$ENV_JSON" > .resolved-environment.json ACTIVE_ENV_ID=$(node -e "const j=JSON.parse(process.argv[1]); console.log(j.environmentId || '')" "$ENV_JSON") ACTIVE_ENV_NAME=$(node -e "const j=JSON.parse(process.argv[1]); console.log(j.displayName || j.environmentUrl || '')" "$ENV_JSON") @@ -172,7 +172,7 @@ execute the block below. Connector-only runs set `$DETECTED_PUBLISHER_PREFIX = ""` and make no Dataverse prefix query. ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/detect-publisher-prefix.js" "$ACTIVE_ENV_URL" --tenant-id "$ACTIVE_TENANT_ID" +node "${PLUGIN_ROOT}/scripts/detect-publisher-prefix.js" "$ACTIVE_ENV_URL" --tenant-id "$ACTIVE_TENANT_ID" ``` Output is one line of JSON, e.g.: @@ -275,7 +275,7 @@ Print the chosen tier so the user knows which path is running: #### Step 2b.1 β€” Walk-through path (only when tier = `walk-through`) -Read [`references/requirements-discovery.md`](${CLAUDE_SKILL_DIR}/references/requirements-discovery.md). Infer context-aware options from the user's description, ask exactly one structured `AskUserQuestion`, and never use markdown checkboxes in the question text. +Read [`references/requirements-discovery.md`](${PLUGIN_ROOT}/skills/create-mobile-app/references/requirements-discovery.md). Infer context-aware options from the user's description, ask exactly one structured `AskUserQuestion`, and never use markdown checkboxes in the question text. Wait for the user's response. Summarize their answers into a **requirements brief** β€” 4–8 bullet points covering what users can do, what data is tracked, and integrations. @@ -453,7 +453,7 @@ Planning stays read-only. Branch on ``: nested planner or architect rediscover the tenant. ```bash -PLANNING_ENV_JSON=$(node "${CLAUDE_SKILL_DIR}/../../scripts/resolve-environment.js" "$ACTIVE_ENV_ID") +PLANNING_ENV_JSON=$(node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$ACTIVE_ENV_ID") ACTIVE_ENV_URL=$(node -e "const j=JSON.parse(process.argv[1]); console.log(j.environmentUrl || '')" "$PLANNING_ENV_JSON") ACTIVE_TENANT_ID=$(node -e "const j=JSON.parse(process.argv[1]); console.log(j.tenantId || '')" "$PLANNING_ENV_JSON") test -n "$ACTIVE_ENV_URL" -a -n "$ACTIVE_TENANT_ID" || { @@ -491,7 +491,7 @@ Detailed advisory discovery is quality-bounded: SNAPSHOT_PATH="/.tmp/dataverse-foreground-planning-snapshot.json" EVIDENCE_PATH="/.tmp/dataverse-planning-evidence.md" -node "${CLAUDE_SKILL_DIR}/../../scripts/create-dataverse-snapshot.js" \ +node "${PLUGIN_ROOT}/scripts/create-dataverse-snapshot.js" \ --env-url "$ACTIVE_ENV_URL" \ --tenant-id "$ACTIVE_TENANT_ID" \ --output "$SNAPSHOT_PATH" \ @@ -499,7 +499,7 @@ node "${CLAUDE_SKILL_DIR}/../../scripts/create-dataverse-snapshot.js" \ --tables "" \ --proposed-tables "" -node "${CLAUDE_SKILL_DIR}/../../scripts/render-dataverse-planning-evidence.js" \ +node "${PLUGIN_ROOT}/scripts/render-dataverse-planning-evidence.js" \ --snapshot "$SNAPSHOT_PATH" \ --output "$EVIDENCE_PATH" @@ -587,7 +587,7 @@ Prompt: Original prompt: Wizard answers: Working directory: > - Plugin root: ${CLAUDE_SKILL_DIR}/../../ + Plugin root: ${PLUGIN_ROOT} Dataverse planning mode: Dataverse planning failure reason: none Normalized Dataverse foreground planning snapshot: @@ -639,7 +639,7 @@ Then execute, in order, using your own `EnterPlanMode` + `AskUserQuestion`: 3. **Run the gates yourself** β€” use `EnterPlanMode` four times (data model β†’ native caps + connectors merged β†’ screen graph 4a β†’ screen specs 4b). Same gate prompts as the planner agent would use. Gate 4 is a markdown screen-graph review only β€” design picking happens unconditionally at Step 6.75 via `/design-system` (no separate style-picker handoff at Gate 4 even in inline mode). 4. **Write the final approved `native-app-plan.md`** with an `## Approvals` block at the bottom listing each gate, who approved (user), and a timestamp. - **HARD RULES for the plan structure (mirror the planner agent's template at [`agents/native-app-planner.md`](${CLAUDE_SKILL_DIR}/../../agents/native-app-planner.md) Step 4):** + **HARD RULES for the plan structure (mirror the planner agent's template at [`agents/native-app-planner.md`](${PLUGIN_ROOT}/agents/native-app-planner.md) Step 4):** - Top-level headings are EXACTLY: `## Overview`, `## App Requirements`, `## Data Model`, `## Native Capabilities`, `## Design Direction`, `## Connectors`, `## Screens`, `## Approvals`. Do NOT invent a `## Brief` super-section that nests the data model under it. - `## App Requirements` is the user's confirmed brief verbatim (the `` from Step 2b), capped at ~80 lines. No expansion, no rewriting, no embedded preview of the data model. - Discovery failure notes (e.g. `az login` on the wrong tenant, 401 from `dataverse-request.js`, all entities classified Create) go to `/memory-bank.md` under `## Discovery Notes`, NOT into the plan. Keep at most a single one-line breadcrumb in `## Data Model` like `> Discovery skipped β€” see memory-bank.md.` if relevant. @@ -662,7 +662,7 @@ If the orchestrator's OWN `Task` tool is unavailable (rare β€” would mean even l #### 3.0 β€” Sub-agent return-status switch (canonical) -Use the plugin-wide protocol in [`AGENTS.md`](${CLAUDE_SKILL_DIR}/../../AGENTS.md) rule #10 for every `Task` return in this skill: planner, parallel screen-builders, and future agent spawns. Parse the literal first line and branch: `DONE` continues; `DONE_WITH_CONCERNS:` surfaces + records in `memory-bank.md`; `NEEDS_CONTEXT:` re-dispatches with missing context, capped at 2 retries; `BLOCKED:` stops and records under `## Blocks`. Unknown first lines are malformed and must be treated as `BLOCKED`. +Use the plugin-wide protocol in [`AGENTS.md`](${PLUGIN_ROOT}/AGENTS.md) rule #10 for every `Task` return in this skill: planner, parallel screen-builders, and future agent spawns. Parse the literal first line and branch: `DONE` continues; `DONE_WITH_CONCERNS:` surfaces + records in `memory-bank.md`; `NEEDS_CONTEXT:` re-dispatches with missing context, capped at 2 retries; `BLOCKED:` stops and records under `## Blocks`. Unknown first lines are malformed and must be treated as `BLOCKED`. **Data-model exact-name expansion:** when the planner or direct architect returns exactly @@ -675,14 +675,14 @@ inventory, issue at most one exact-name metadata query for requested names absent from it, and do not run another broad inventory query: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/create-dataverse-snapshot.js" \ +node "${PLUGIN_ROOT}/scripts/create-dataverse-snapshot.js" \ --env-url "$ACTIVE_ENV_URL" \ --tenant-id "$ACTIVE_TENANT_ID" \ --base-snapshot "$SNAPSHOT_PATH" \ --output "$SNAPSHOT_PATH" \ --tables "" -node "${CLAUDE_SKILL_DIR}/../../scripts/render-dataverse-planning-evidence.js" \ +node "${PLUGIN_ROOT}/scripts/render-dataverse-planning-evidence.js" \ --snapshot "$SNAPSHOT_PATH" \ --output "$EVIDENCE_PATH" @@ -710,14 +710,14 @@ those names. This signal is valid only in `required` mode with a validated snapshot. Perform one collision-only foreground expansion: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/create-dataverse-snapshot.js" \ +node "${PLUGIN_ROOT}/scripts/create-dataverse-snapshot.js" \ --env-url "$ACTIVE_ENV_URL" \ --tenant-id "$ACTIVE_TENANT_ID" \ --base-snapshot "$SNAPSHOT_PATH" \ --output "$SNAPSHOT_PATH" \ --proposed-tables "" -node "${CLAUDE_SKILL_DIR}/../../scripts/render-dataverse-planning-evidence.js" \ +node "${PLUGIN_ROOT}/scripts/render-dataverse-planning-evidence.js" \ --snapshot "$SNAPSHOT_PATH" \ --output "$EVIDENCE_PATH" ``` @@ -856,7 +856,7 @@ both approved artifacts: ```bash test -f "$WORKING_DIR/native-app-plan.md" test -f "$WORKING_DIR/.tmp/dataverse-schema-contract.json" -node "${CLAUDE_SKILL_DIR}/../../scripts/build-dataverse-operation-manifest.js" \ +node "${PLUGIN_ROOT}/scripts/build-dataverse-operation-manifest.js" \ --normalize-contract "$WORKING_DIR/.tmp/dataverse-schema-contract.json" \ --output "$WORKING_DIR/.tmp/dataverse-schema-contract.json" ``` @@ -868,7 +868,7 @@ path. ### Step 4 β€” Auth & environment selection ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/resolve-environment.js" "$ACTIVE_ENV_ID" +node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$ACTIVE_ENV_ID" ``` If the resolved environment doesn't match what the planner used in Step 3, ask the user for the intended environment ID and re-run `resolve-environment.js`. Capture the **environment ID** for Step 6. @@ -999,17 +999,17 @@ mkdir -p "/src/hooks" mkdir -p "/src/utils" mkdir -p "/src/tokens" -cp "${CLAUDE_SKILL_DIR}/../../shared/samples/src/components/index.tsx" "/src/components/index.tsx" -cp "${CLAUDE_SKILL_DIR}/../../shared/samples/src/hooks/index.ts" "/src/hooks/index.ts" -cp "${CLAUDE_SKILL_DIR}/../../shared/samples/src/hooks/useCursorListData.ts" "/src/hooks/useCursorListData.ts" -cp "${CLAUDE_SKILL_DIR}/../../shared/samples/src/hooks/useListData.ts" "/src/hooks/useListData.ts" -cp "${CLAUDE_SKILL_DIR}/../../shared/samples/src/hooks/useSearchFilter.ts" "/src/hooks/useSearchFilter.ts" -cp "${CLAUDE_SKILL_DIR}/../../shared/samples/src/utils/index.ts" "/src/utils/index.ts" -cp "${CLAUDE_SKILL_DIR}/../../shared/samples/src/utils/formatters.ts" "/src/utils/formatters.ts" -cp "${CLAUDE_SKILL_DIR}/../../shared/samples/src/utils/text.ts" "/src/utils/text.ts" -cp "${CLAUDE_SKILL_DIR}/../../shared/samples/src/utils/choices.ts" "/src/utils/choices.ts" -cp "${CLAUDE_SKILL_DIR}/../../shared/samples/src/utils/dataverse.ts" "/src/utils/dataverse.ts" -cp "${CLAUDE_SKILL_DIR}/../../shared/samples/src/tokens/index.ts" "/src/tokens/index.ts" +cp "${PLUGIN_ROOT}/shared/samples/src/components/index.tsx" "/src/components/index.tsx" +cp "${PLUGIN_ROOT}/shared/samples/src/hooks/index.ts" "/src/hooks/index.ts" +cp "${PLUGIN_ROOT}/shared/samples/src/hooks/useCursorListData.ts" "/src/hooks/useCursorListData.ts" +cp "${PLUGIN_ROOT}/shared/samples/src/hooks/useListData.ts" "/src/hooks/useListData.ts" +cp "${PLUGIN_ROOT}/shared/samples/src/hooks/useSearchFilter.ts" "/src/hooks/useSearchFilter.ts" +cp "${PLUGIN_ROOT}/shared/samples/src/utils/index.ts" "/src/utils/index.ts" +cp "${PLUGIN_ROOT}/shared/samples/src/utils/formatters.ts" "/src/utils/formatters.ts" +cp "${PLUGIN_ROOT}/shared/samples/src/utils/text.ts" "/src/utils/text.ts" +cp "${PLUGIN_ROOT}/shared/samples/src/utils/choices.ts" "/src/utils/choices.ts" +cp "${PLUGIN_ROOT}/shared/samples/src/utils/dataverse.ts" "/src/utils/dataverse.ts" +cp "${PLUGIN_ROOT}/shared/samples/src/tokens/index.ts" "/src/tokens/index.ts" ``` **Fix 8 β€” Thread the project's `tamaguiConfig` into the host provider** (required so screens render under brand tokens, not upstream defaults) @@ -1198,7 +1198,7 @@ This is the **Scaffold gate** from the TypeScript Gate Policy. If it fails, capt ### Step 6.7 β€” Seed the memory bank ```bash -cp "${CLAUDE_SKILL_DIR}/../../shared/memory-bank.md" "/memory-bank.md" +cp "${PLUGIN_ROOT}/shared/memory-bank.md" "/memory-bank.md" ``` Fill in the Project facts and Power Platform context sections from Steps 2 and 4. From here on, every step appends to the relevant section of `/memory-bank.md` immediately after success β€” not at the end. This is what enables Step 0's resume on a future run. @@ -1460,20 +1460,20 @@ test -f "$SCHEMA_CONTRACT" -a -f "$APPROVAL_RECEIPT" \ -a -f "$FOREGROUND_PLANNING_SNAPSHOT" \ -a -f "/native-app-plan.md" -node "${CLAUDE_SKILL_DIR}/../../scripts/build-dataverse-operation-manifest.js" \ +node "${PLUGIN_ROOT}/scripts/build-dataverse-operation-manifest.js" \ --bind-plan "$SCHEMA_CONTRACT" \ --approval-receipt "$APPROVAL_RECEIPT" \ --plan "/native-app-plan.md" \ --output "$SCHEMA_CONTRACT" -node "${CLAUDE_SKILL_DIR}/../../scripts/build-dataverse-operation-manifest.js" \ +node "${PLUGIN_ROOT}/scripts/build-dataverse-operation-manifest.js" \ --reconciliation-scope "$SCHEMA_CONTRACT" \ --output "$RECONCILIATION_SCOPE" EXACT_TABLES=$(node -e "console.log(require(process.argv[1]).exactTables.join(','))" "$RECONCILIATION_SCOPE") PROPOSED_TABLES=$(node -e "console.log(require(process.argv[1]).proposedTables.join(','))" "$RECONCILIATION_SCOPE") -node "${CLAUDE_SKILL_DIR}/../../scripts/create-dataverse-snapshot.js" \ +node "${PLUGIN_ROOT}/scripts/create-dataverse-snapshot.js" \ --env-url "$ACTIVE_ENV_URL" \ --tenant-id "$ACTIVE_TENANT_ID" \ --solution "$ACTIVE_SOLUTION_UNIQUE_NAME" \ @@ -1482,7 +1482,7 @@ node "${CLAUDE_SKILL_DIR}/../../scripts/create-dataverse-snapshot.js" \ --reconcile-exact \ --output "$EXECUTION_RECONCILIATION" -node "${CLAUDE_SKILL_DIR}/../../scripts/build-dataverse-operation-manifest.js" \ +node "${PLUGIN_ROOT}/scripts/build-dataverse-operation-manifest.js" \ --contract "$SCHEMA_CONTRACT" \ --approval-receipt "$APPROVAL_RECEIPT" \ --reconciliation "$EXECUTION_RECONCILIATION" \ @@ -1503,7 +1503,7 @@ conflict and returns to the orchestrator; do not add another opportunistic read loop or fall back to agent reconciliation. No operation may execute until: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/build-dataverse-operation-manifest.js" \ +node "${PLUGIN_ROOT}/scripts/build-dataverse-operation-manifest.js" \ --validate "$OPERATION_MANIFEST" \ --contract "$SCHEMA_CONTRACT" \ --approval-receipt "$APPROVAL_RECEIPT" \ @@ -1599,7 +1599,7 @@ If the plan says "None β€” this app uses only standard React Native components a ### Step 9a β€” Install approved pure-JavaScript dependencies -Read and execute the Installation Contract in [`shared/references/javascript-dependency-planning.md`](${CLAUDE_SKILL_DIR}/../../shared/references/javascript-dependency-planning.md) for every approved row in `## Screens β†’ ### JavaScript Dependencies`. If the subsection is absent or says `None.`, continue without changing dependencies. +Read and execute the Installation Contract in [`shared/references/javascript-dependency-planning.md`](${PLUGIN_ROOT}/shared/references/javascript-dependency-planning.md) for every approved row in `## Screens β†’ ### JavaScript Dependencies`. If the subsection is absent or says `None.`, continue without changing dependencies. Gate 4b approval is consent for exactly the packages and versions in the table. Install them into `` before any skeleton or builder imports them, validate `package.json` and the lockfile, and verify module resolution. Do not substitute another package/version, infer a package from a compiler error, or route a JS-only package through `/add-native`. If final inspection finds native code/config or incompatible runtime dependencies, remove only the newly added package and STOP with the exact failed criterion. @@ -1607,7 +1607,7 @@ Gate 4b approval is consent for exactly the packages and versions in the table. `/design-system` owns user-facing brand/design choices. This step owns the internal Tamagui integration that makes those choices usable by generated screens. Even if the user accepts the default design path, run the alias-only integration so screens can rely on the semantic token contract. -Read the `## Design` section from `native-app-plan.md` and follow the execution mapping in [`shared/references/design-planning.md`](${CLAUDE_SKILL_DIR}/../../shared/references/design-planning.md): +Read the `## Design` section from `native-app-plan.md` and follow the execution mapping in [`shared/references/design-planning.md`](${PLUGIN_ROOT}/shared/references/design-planning.md): | Condition | Action | |---|---| @@ -2188,7 +2188,7 @@ Common wave-gate repair classes to batch instead of fixing line-by-line: Then run the canonical route-contract gate from the app root: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/check-routes.js" +node "${PLUGIN_ROOT}/scripts/check-routes.js" ``` This gate is required even when TypeScript passes. It detects duplicate normalized routes, `[id].tsx` plus `[id]/.tsx` file/folder collisions, and sender/destination parameter drift. If it fails, repair the affected route files or re-spawn their screen builders with the consolidated findings, then rerun once. Do not continue to Step 11.4 or start Metro while route findings remain. @@ -2218,8 +2218,8 @@ Run one controlled stylistic debt sweep after all screen-builder waves and TypeS **Available validators in v0:** ```bash -node "${CLAUDE_SKILL_DIR}/../../hooks/validate-screen-quality.js" --report -node "${CLAUDE_SKILL_DIR}/../../hooks/validate-color-contrast.js" --report +node "${PLUGIN_ROOT}/hooks/validate-screen-quality.js" --report +node "${PLUGIN_ROOT}/hooks/validate-color-contrast.js" --report ``` `validate-screen-quality` includes accessibility-label/role, safe-area, touch-target, raw-hex, token, empty-state, shadow, and status-visual checks. If future stylistic hooks exist (for example `validate-accessibility-labels.js`), include them here only if they support `--report` and emit the same JSON issue shape. @@ -2399,6 +2399,6 @@ Which option? (or "none β€” I'll keep iterating locally") ## Reference -- [shared/shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md) -- [shared/references/screen-templates.md](${CLAUDE_SKILL_DIR}/../../shared/references/screen-templates.md) -- [agents/native-app-planner.md](${CLAUDE_SKILL_DIR}/../../agents/native-app-planner.md) +- [shared/shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md) +- [shared/references/screen-templates.md](${PLUGIN_ROOT}/shared/references/screen-templates.md) +- [agents/native-app-planner.md](${PLUGIN_ROOT}/agents/native-app-planner.md) diff --git a/plugins/mobile-apps/skills/debug-app/SKILL.md b/plugins/mobile-apps/skills/debug-app/SKILL.md index 72a9751f9..d09a782e7 100644 --- a/plugins/mobile-apps/skills/debug-app/SKILL.md +++ b/plugins/mobile-apps/skills/debug-app/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion, WebFetch model: sonnet --- -**πŸ“‹ Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)** β€” read first. +**πŸ“‹ Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** β€” read first. # Debug App β€” Monitor & Fix @@ -36,7 +36,7 @@ Monitor the running app by reading the Metro dev-server terminal output, detect - **No screen-by-screen verification** β€” Do not crawl routes or validate every screen. In symptom mode, focus only on the user-reported workflow and the terminal/source evidence needed to diagnose it. - **One fix at a time** β€” Fully resolve one issue (context β†’ fix β†’ type-check β†’ reload β†’ re-poll) before starting the next. No batching. - **Working-dir state** β€” All session state lives in `.claude/debug-app/` (gitignored): `fixes.md` for audit log, `unresolved.md` for escalations, `injected-logs.md` for tracking injected console.log statements. Survives across runs. -- **Reference resolution order** β€” For unfamiliar errors: in-repo references first ([skills/add-dataverse/references/dataverse-reference.md](${CLAUDE_SKILL_DIR}/../../skills/add-dataverse/references/dataverse-reference.md), etc.), then `mcp__microsoft-learn__microsoft_docs_search`, then general web search. +- **Reference resolution order** β€” For unfamiliar errors: in-repo references first ([skills/add-dataverse/references/dataverse-reference.md](${PLUGIN_ROOT}/skills/add-dataverse/references/dataverse-reference.md), etc.), then `mcp__microsoft-learn__microsoft_docs_search`, then general web search. ## Workflow β€” Task List First @@ -431,7 +431,7 @@ Read the relevant source file(s). Identify: | `src/components/` | Inline edit via `Edit` tool | | `src/hooks/`, `src/services/` | Inline edit via `Edit` tool | | `src/generated/` | **Do not edit.** Fix the upstream query or schema and run `npm run generate-schemas` | -| Dataverse schema (column/table missing) | **Hand-off:** route user to `/add-dataverse`. Do not auto-edit. Read [skills/add-dataverse/references/dataverse-reference.md](${CLAUDE_SKILL_DIR}/../../skills/add-dataverse/references/dataverse-reference.md) before suggesting changes. | +| Dataverse schema (column/table missing) | **Hand-off:** route user to `/add-dataverse`. Do not auto-edit. Read [skills/add-dataverse/references/dataverse-reference.md](${PLUGIN_ROOT}/skills/add-dataverse/references/dataverse-reference.md) before suggesting changes. | | Auth / MSAL (`AADSTS65001`, `AADSTS50011`) | **Hand-off:** route user to the Power Apps Wrap page via `/set-app-registration-native`. Do not auto-edit registrations. | | Connection / connector reference missing | **Hand-off:** route user to `/list-connections` or `/add-connector`. | | Native module, `app.config.js`, `app.plugin.js`, `Podfile`, `build.gradle` | **Inform the user.** Do NOT auto-edit native config β€” print the error + suggested action and skip to next issue. | @@ -461,7 +461,7 @@ These recipes apply to errors classified as "Import / Bundle" in Step B. They ar | `SyntaxError: ::` in `app/`, `src/components/`, `src/hooks/`, `src/services/` | The cited line is in editable user code (NOT `src/generated/`, NOT `node_modules/`) | `Read` the file around the cited line (Β±10 lines), identify the syntactic issue (unclosed JSX tag, missing closing brace/paren, stray comma, missing `from` in import, unterminated string, missing semicolon between statements), apply a single minimal `Edit`. Do NOT reformat surrounding code. | | `SyntaxError` in `src/generated/` | Cited file is under `src/generated/` | **Do not edit.** Schema regen produced bad output. Hand-off: tell the user to re-run `npm run generate-schemas`; if the error reproduces, route to `/add-connector` or `/add-dataverse` to re-add the affected datasource. | | `Unable to resolve module ` from `` | `` starts with `.` or `..` (relative import) | `Glob` the importer's directory for files matching `` with any extension (`.ts`, `.tsx`, `.js`, `.jsx`, `.json`). If found with a different extension β†’ fix the import to drop the extension OR match the actual one. If found with a typo (Levenshtein ≀ 2) β†’ fix the typo. If not found at all β†’ the file genuinely doesn't exist; surface to user and ask whether to create it or remove the import. | -| `Unable to resolve module ` | `` is a bare package AND not present in `package.json` `dependencies` / `devDependencies` | Follow [`shared/references/javascript-dependency-planning.md`](${CLAUDE_SKILL_DIR}/../../shared/references/javascript-dependency-planning.md) to classify the published package by contents, not its name. If native-bound and absent from the template, report that a template/runtime update is required. If verified pure JavaScript, ask consent for the exact version, install with `npm install --save-exact`, validate, and retry. Do NOT install without consent. | +| `Unable to resolve module ` | `` is a bare package AND not present in `package.json` `dependencies` / `devDependencies` | Follow [`shared/references/javascript-dependency-planning.md`](${PLUGIN_ROOT}/shared/references/javascript-dependency-planning.md) to classify the published package by contents, not its name. If native-bound and absent from the template, report that a template/runtime update is required. If verified pure JavaScript, ask consent for the exact version, install with `npm install --save-exact`, validate, and retry. Do NOT install without consent. | | `Unable to resolve module ` | `` IS in `package.json` but the bundle still fails | Likely cache: instruct the user to stop the Metro terminal and re-run with `npx expo start --clear`. Do NOT auto-restart Metro from the skill β€” it owns Metro's lifecycle (see Constraints). | | `transform failed` referencing a babel plugin (e.g., `[BABEL] ... unknown plugin "react-native-reanimated/plugin"`) | Error references `babel.config.js` | **Hand-off.** `babel.config.js` is project config (same constraint that protects `app.config.js`). Print the cited plugin and suggested fix order (e.g., "`react-native-reanimated/plugin` MUST be the LAST plugin in `babel.config.js` `plugins` array"); skip to next issue. | | `transform failed` without a babel reference | Generic transform failure (often a TS feature Metro's transformer can't handle) | Read the cited file, look for syntax that requires a specific TS lib (e.g., decorators, top-level await). If the issue is a known-bad pattern, surface and ask before fixing. Otherwise hand-off. | diff --git a/plugins/mobile-apps/skills/deploy/SKILL.md b/plugins/mobile-apps/skills/deploy/SKILL.md index 5b8b2425b..d1aca7698 100644 --- a/plugins/mobile-apps/skills/deploy/SKILL.md +++ b/plugins/mobile-apps/skills/deploy/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Glob, Bash, AskUserQuestion, Skill model: sonnet --- -**πŸ“‹ Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)** β€” read first. +**πŸ“‹ Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** β€” read first. # Deploy @@ -71,10 +71,10 @@ This is the final chance to catch schema that never made it into the Mobile Offl Run the local, no-network delta check (`.datamodel-manifest.json` vs `offline-profile.json`): ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/offline-profile-delta.js" +node "${PLUGIN_ROOT}/scripts/offline-profile-delta.js" ``` -Branch on the JSON `status` (full contract in [offline-profile-reconciliation.md](${CLAUDE_SKILL_DIR}/../../shared/references/offline-profile-reconciliation.md)): +Branch on the JSON `status` (full contract in [offline-profile-reconciliation.md](${PLUGIN_ROOT}/shared/references/offline-profile-reconciliation.md)): | `status` | Action | |---|---| @@ -96,7 +96,7 @@ Branch on the JSON `status` (full contract in [offline-profile-reconciliation.md Options: -- **Update the offline profile now (recommended)** β€” read and execute `${CLAUDE_SKILL_DIR}/../add-table-to-offline-profile/SKILL.md` for each `missingTables[]` entry (or once with `--all-new`), then read and execute `${CLAUDE_SKILL_DIR}/../edit-offline-profile/SKILL.md` with `--table --columns add:` for each `tablesWithNewColumns[]` entry. Follow the ordering in the reconciliation reference, then re-run the delta check; when it reports `in-sync`, continue to Step 3. +- **Update the offline profile now (recommended)** β€” read and execute `${PLUGIN_ROOT}/skills/add-table-to-offline-profile/SKILL.md` for each `missingTables[]` entry (or once with `--all-new`), then read and execute `${PLUGIN_ROOT}/skills/edit-offline-profile/SKILL.md` with `--table --columns add:` for each `tablesWithNewColumns[]` entry. Follow the ordering in the reconciliation reference, then re-run the delta check; when it reports `in-sync`, continue to Step 3. - **Deploy anyway** β€” requires an explicit override. Wait for the exact phrase `deploy without offline` (case-insensitive); a bare `y`/`yes` is not enough, mirroring the environment-mismatch gate in Step 3. Then continue to Step 3 and note the skipped reconciliation in the Step 4 build-history row. Do not push until the gate is resolved (reconciled to `in-sync`, or explicitly overridden). @@ -109,7 +109,7 @@ Run: ```bash ENV_ID=$(node -e "console.log(require('./power.config.json').environmentId)") -node "${CLAUDE_SKILL_DIR}/../../scripts/resolve-environment.js" "$ENV_ID" +node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$ENV_ID" ``` From `resolve-environment.js` capture the **Environment URL** (e.g. `https://contoso.crm.dynamics.com/`), **Environment ID**, and **Tenant ID**. Cross-check against `memory-bank.md` / `power.config.json`: @@ -187,6 +187,6 @@ If they want to compile a native binary locally, they run the platform-specific ## Reference -- [`shared/version-check.md`](${CLAUDE_SKILL_DIR}/../../shared/version-check.md) β€” min versions (only Always-required tier matters here) -- [`shared/memory-bank.md`](${CLAUDE_SKILL_DIR}/../../shared/memory-bank.md) β€” Build history schema -- [`shared/references/offline-profile-reconciliation.md`](${CLAUDE_SKILL_DIR}/../../shared/references/offline-profile-reconciliation.md) β€” Step 2.5 offline coverage gate +- [`shared/version-check.md`](${PLUGIN_ROOT}/shared/version-check.md) β€” min versions (only Always-required tier matters here) +- [`shared/memory-bank.md`](${PLUGIN_ROOT}/shared/memory-bank.md) β€” Build history schema +- [`shared/references/offline-profile-reconciliation.md`](${PLUGIN_ROOT}/shared/references/offline-profile-reconciliation.md) β€” Step 2.5 offline coverage gate diff --git a/plugins/mobile-apps/skills/design-system/SKILL.md b/plugins/mobile-apps/skills/design-system/SKILL.md index 4ebf1479b..0b381b95c 100644 --- a/plugins/mobile-apps/skills/design-system/SKILL.md +++ b/plugins/mobile-apps/skills/design-system/SKILL.md @@ -180,9 +180,9 @@ Persist choice to `memory-bank.md`: `visual_companion: ` - **(c) Apply defaults / (d) β€” no-brand path** β†’ skip Sub-steps 3, 5. Run these in order: 1. **Minimal Sub-step 4** β€” write `brand/tokens.ts` from the industry's direction preset. **Source-of-truth lookup order for the preset bundle:** - 1. If the user passed `--direction ` (e.g. `inspection`, `saas`, `product`), load `${CLAUDE_SKILL_DIR}/references/vibe/direction-.md` and use its tokens. - 2. **Airline / aviation / commercial-flight carve-out (HARD RULE).** If the brief contains any of `airline`, `aviation`, `flight`, `aircraft`, `carrier`, `pilot`, `cabin crew`, `boarding`, `departure`, `tarmac`, `turnaround`, `ground ops`, OR the app name contains those tokens, DO NOT load the `signature` preset (safety-orange would clash with airline brand expectations). Instead **load [`${CLAUDE_SKILL_DIR}/references/vibe/direction-airline.md`](./references/vibe/direction-airline.md)** β€” deep aviation blue (`#0A4F8F`), white surfaces, hi-vis status pills, hairline borders. Token bundle is the canonical FlightCheck tokens (proven in production). Record in `memory-bank.md` as `direction: airline`. - 3. **Else (true "all defaults" path) β†’ load [`${CLAUDE_SKILL_DIR}/references/vibe/direction-polished-inspection.md`](./references/vibe/direction-polished-inspection.md) as the canonical `polished-inspection` preset** β€” white surface, Power-Platform green `#007d48` accent (Power Platform–aligned default), status-stripe cards, soft-tinted status pills, large tap targets. This is the polished MVP default that fits any inspection / field-ops / asset-tracking app (~70% of mobile-app traffic) AND demos cleanly to enterprise stakeholders. The previous `signature` preset (slate dark + safety orange, sourced from `uber-design.md`) is now opt-in via `--direction inspection` for true outdoor-only field apps. + 1. If the user passed `--direction ` (e.g. `inspection`, `saas`, `product`), load `${PLUGIN_ROOT}/skills/design-system/references/vibe/direction-.md` and use its tokens. + 2. **Airline / aviation / commercial-flight carve-out (HARD RULE).** If the brief contains any of `airline`, `aviation`, `flight`, `aircraft`, `carrier`, `pilot`, `cabin crew`, `boarding`, `departure`, `tarmac`, `turnaround`, `ground ops`, OR the app name contains those tokens, DO NOT load the `signature` preset (safety-orange would clash with airline brand expectations). Instead **load [`${PLUGIN_ROOT}/skills/design-system/references/vibe/direction-airline.md`](./references/vibe/direction-airline.md)** β€” deep aviation blue (`#0A4F8F`), white surfaces, hi-vis status pills, hairline borders. Token bundle is the canonical FlightCheck tokens (proven in production). Record in `memory-bank.md` as `direction: airline`. + 3. **Else (true "all defaults" path) β†’ load [`${PLUGIN_ROOT}/skills/design-system/references/vibe/direction-polished-inspection.md`](./references/vibe/direction-polished-inspection.md) as the canonical `polished-inspection` preset** β€” white surface, Power-Platform green `#007d48` accent (Power Platform–aligned default), status-stripe cards, soft-tinted status pills, large tap targets. This is the polished MVP default that fits any inspection / field-ops / asset-tracking app (~70% of mobile-app traffic) AND demos cleanly to enterprise stakeholders. The previous `signature` preset (slate dark + safety orange, sourced from `uber-design.md`) is now opt-in via `--direction inspection` for true outdoor-only field apps. 4. As a last fallback, if the source file is unreadable, use the inspection direction inlined in [`references/design-system-schema.md`](./references/design-system-schema.md). Skip the full `brand/design-system.md` write β€” only `brand/tokens.ts` is needed. Record the chosen source in `memory-bank.md` under `## Design`: `direction: polished-inspection (default β€” white + Power-Platform green, demo-friendly enterprise polish)` so future runs know what was picked. diff --git a/plugins/mobile-apps/skills/edit-app/SKILL.md b/plugins/mobile-apps/skills/edit-app/SKILL.md index dda4cc010..c72dd7f55 100644 --- a/plugins/mobile-apps/skills/edit-app/SKILL.md +++ b/plugins/mobile-apps/skills/edit-app/SKILL.md @@ -322,7 +322,7 @@ Prompt: User request: Current section content: Working directory: - Plugin root: ${CLAUDE_SKILL_DIR}/../../ + Plugin root: ${PLUGIN_ROOT} Mode: edit (preserve existing decisions where the change doesn't affect them). Existing generated app must be updated after approval, so include enough detail for builders to mutate code without guessing. @@ -384,7 +384,7 @@ Apply sections in dependency order so screens always build against the current d 1. **Data Model** β€” read and execute `/add-dataverse --skip-planning` with the approved Data Model section. It must create/extend Dataverse tables, refresh generated services/models, update `.datamodel-manifest.json`, and leave generated services compiling. After it returns, run `npm run generate-schemas` and `npx tsc --noEmit`; do not continue to screens until clean. 2. **Sample Data** β€” if a new Dataverse table was created and any changed screen will show list/detail data from it, read and execute `/add-sample-data` for the project. If seeding fails, record a concern and continue only if the app handles empty states. 3. **Connector/Data Source** β€” read and execute `/add-datasource` when ambiguous, or `/add-sharepoint` / `/add-connector` for approved connector changes. Regenerate services and record connection notes in `memory-bank.md`. -4. **Pure-JavaScript Dependencies** β€” execute the Installation Contract in [`shared/references/javascript-dependency-planning.md`](${CLAUDE_SKILL_DIR}/../../shared/references/javascript-dependency-planning.md) for new or changed rows in the approved `## Screens β†’ ### JavaScript Dependencies` table. Approval is consent for those exact packages and versions. Install and validate before screen work; if final inspection finds native code/config or incompatible runtime dependencies, remove only the newly added package and stop with the exact failed criterion. +4. **Pure-JavaScript Dependencies** β€” execute the Installation Contract in [`shared/references/javascript-dependency-planning.md`](${PLUGIN_ROOT}/shared/references/javascript-dependency-planning.md) for new or changed rows in the approved `## Screens β†’ ### JavaScript Dependencies` table. Approval is consent for those exact packages and versions. Install and validate before screen work; if final inspection finds native code/config or incompatible runtime dependencies, remove only the newly added package and stop with the exact failed criterion. 5. **Native Capabilities** β€” read and execute `/add-native ` for every new capability. Do not install missing native packages or fake wrappers. If a capability is unsupported by the current template, stop before rebuilding screens that import it, record the block, and tell the user what upstream template support is missing. 6. **Design** β€” read and execute `/design-system --refresh ` or `/design-system --reskin` for design edits. Token-only changes usually do not require TSX rewrites; component/density/negative-rule changes may. @@ -415,10 +415,10 @@ If Step 5 created or extended Dataverse tables, an existing Mobile Offline Profi Run the local, no-network delta check: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/offline-profile-delta.js" +node "${PLUGIN_ROOT}/scripts/offline-profile-delta.js" ``` -Branch on the JSON `status` per [offline-profile-reconciliation.md](${CLAUDE_SKILL_DIR}/../../shared/references/offline-profile-reconciliation.md): `no-manifest` / `no-profile` / `in-sync` β†’ continue silently (do not nag when no profile exists); `delta` β†’ prompt to update, then read and execute `${CLAUDE_SKILL_DIR}/../add-table-to-offline-profile/SKILL.md` for `missingTables[]` and `${CLAUDE_SKILL_DIR}/../edit-offline-profile/SKILL.md` for `tablesWithNewColumns[]`, passing the arguments documented by each workflow, and re-check to `in-sync`. Record the reconciliation outcome in the Step 8 memory-bank edit entry. +Branch on the JSON `status` per [offline-profile-reconciliation.md](${PLUGIN_ROOT}/shared/references/offline-profile-reconciliation.md): `no-manifest` / `no-profile` / `in-sync` β†’ continue silently (do not nag when no profile exists); `delta` β†’ prompt to update, then read and execute `${PLUGIN_ROOT}/skills/add-table-to-offline-profile/SKILL.md` for `missingTables[]` and `${PLUGIN_ROOT}/skills/edit-offline-profile/SKILL.md` for `tablesWithNewColumns[]`, passing the arguments documented by each workflow, and re-check to `in-sync`. Record the reconciliation outcome in the Step 8 memory-bank edit entry. ### Step 6 β€” Rebuild affected screens @@ -522,8 +522,8 @@ node scripts/check-routes.js When screen files changed, run the mobile plugin's report-mode validators explicitly: ```bash -node "${CLAUDE_SKILL_DIR}/../../hooks/validate-screen-quality.js" --report -node "${CLAUDE_SKILL_DIR}/../../hooks/validate-color-contrast.js" --report +node "${PLUGIN_ROOT}/hooks/validate-screen-quality.js" --report +node "${PLUGIN_ROOT}/hooks/validate-color-contrast.js" --report ``` Treat validator findings like create-flow gate failures: capture once, batch by root cause, repair, and rerun the same validator once. These scripts are invoked only inside the mobile workflow; do not register them as plugin-wide hooks. diff --git a/plugins/mobile-apps/skills/edit-offline-profile/SKILL.md b/plugins/mobile-apps/skills/edit-offline-profile/SKILL.md index 7cd19aba1..2b9818beb 100644 --- a/plugins/mobile-apps/skills/edit-offline-profile/SKILL.md +++ b/plugins/mobile-apps/skills/edit-offline-profile/SKILL.md @@ -7,18 +7,18 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion model: sonnet --- -**Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)** β€” read first. +**Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** β€” read first. **References:** -- [dataverse-offline-api.md](${CLAUDE_SKILL_DIR}/../../shared/references/dataverse-offline-api.md) Β§4 / Β§7 β€” POST item + PATCH selectedcolumns -- [offline-profile-reconciliation.md](${CLAUDE_SKILL_DIR}/../../shared/references/offline-profile-reconciliation.md) β€” refreshing the `schemaColumns` baseline after a column edit +- [dataverse-offline-api.md](${PLUGIN_ROOT}/shared/references/dataverse-offline-api.md) Β§4 / Β§7 β€” POST item + PATCH selectedcolumns +- [offline-profile-reconciliation.md](${PLUGIN_ROOT}/shared/references/offline-profile-reconciliation.md) β€” refreshing the `schemaColumns` baseline after a column edit # Edit Offline Profile Re-run a single piece of an existing profile β€” change one table's row scope, update the column list, adjust sync frequency, or rename the profile. Avoids the cognitive cost of walking the full /setup-offline-profile wizard for a one-line change. -Scope: existing profile (read from `offline-profile.json` or `--profile-id`); edits at the table-item granularity. To ADD a new table see `/add-table-to-offline-profile`; to delete the entire profile see [dataverse-offline-api.md Β§11](${CLAUDE_SKILL_DIR}/../../shared/references/dataverse-offline-api.md). +Scope: existing profile (read from `offline-profile.json` or `--profile-id`); edits at the table-item granularity. To ADD a new table see `/add-table-to-offline-profile`; to delete the entire profile see [dataverse-offline-api.md Β§11](${PLUGIN_ROOT}/shared/references/dataverse-offline-api.md). ## Workflow @@ -30,7 +30,7 @@ Scope: existing profile (read from `offline-profile.json` or `--profile-id`); ed ```bash test -f power.config.json -node "${CLAUDE_SKILL_DIR}/../../scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")" +node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")" ``` Profile ID resolution (same priority as `/assign-offline-profile` Step 1): @@ -69,7 +69,7 @@ If no flags β†’ interactive picker. `AskUserQuestion` with up-to-4 most likely e GET the current item state from Dataverse for any tables being edited: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET \ "mobileofflineprofileitems()?\$select=name,recorddistributioncriteria,recordsownedbyme,recordsownedbymyteam,recordsownedbymybusinessunit,syncintervalinminutes,selectedcolumns" ``` @@ -94,7 +94,7 @@ If `Apply` β†’ continue. If `Cancel` β†’ STOP. Build the PATCH body with only the fields that changed: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" PATCH \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" PATCH \ "mobileofflineprofileitems()" \ --body '{ "recorddistributioncriteria": , @@ -107,17 +107,17 @@ node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" PATCH \ For profile-level edits (name / description): ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" PATCH \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" PATCH \ "mobileofflineprofiles()" \ --body '{"name": "...", "description": "..."}' ``` ### Step 5 β€” Publish -Use the **targeted `PublishXml`** recipe from [shared/references/dataverse-offline-api.md Β§9](${CLAUDE_SKILL_DIR}/../../shared/references/dataverse-offline-api.md): +Use the **targeted `PublishXml`** recipe from [shared/references/dataverse-offline-api.md Β§9](${PLUGIN_ROOT}/shared/references/dataverse-offline-api.md): ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" POST \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST \ "PublishXml" --body '{ "ParameterXml": "'"$PROFILE_ID"'" }' @@ -129,7 +129,7 @@ On `400 / 0x80071141` "circular relationship" β€” same handling as `/setup-offli ### Step 6 β€” Update artifacts -Re-read the changed item(s) and rewrite the matching entry in `offline-profile.json`. When the edit changed a table's **columns** (`--columns add:/remove:/reset`), also refresh that table entry's `schemaColumns` to the table's current full column set from `.datamodel-manifest.json` (root or `docs/plan-artifacts/`). This re-baselines the schema-reconciliation marker so a delta that was just reconciled clears on the next `scripts/offline-profile-delta.js` run; leave `schemaColumns` untouched for scope/sync/rename-only edits. See [offline-profile-reconciliation.md](${CLAUDE_SKILL_DIR}/../../shared/references/offline-profile-reconciliation.md). +Re-read the changed item(s) and rewrite the matching entry in `offline-profile.json`. When the edit changed a table's **columns** (`--columns add:/remove:/reset`), also refresh that table entry's `schemaColumns` to the table's current full column set from `.datamodel-manifest.json` (root or `docs/plan-artifacts/`). This re-baselines the schema-reconciliation marker so a delta that was just reconciled clears on the next `scripts/offline-profile-delta.js` run; leave `schemaColumns` untouched for scope/sync/rename-only edits. See [offline-profile-reconciliation.md](${PLUGIN_ROOT}/shared/references/offline-profile-reconciliation.md). Append a one-line entry to `memory-bank.md` `## Offline profile` block: @@ -162,5 +162,5 @@ offline-profile.json + memory-bank.md updated. - Add a NEW table to the profile β†’ use `/add-table-to-offline-profile` - Add a new association (relationship inclusion) β†’ blocked on v0.2 `selectedrelationshipsschema` work; use maker portal in the meantime -- Delete the whole profile β†’ manual `DELETE /mobileofflineprofiles()` (cascade-deletes items + associations); see [dataverse-offline-api.md Β§11](${CLAUDE_SKILL_DIR}/../../shared/references/dataverse-offline-api.md) +- Delete the whole profile β†’ manual `DELETE /mobileofflineprofiles()` (cascade-deletes items + associations); see [dataverse-offline-api.md Β§11](${PLUGIN_ROOT}/shared/references/dataverse-offline-api.md) - Migrate the profile between environments β†’ use `CloneMobileOfflineProfile` action (v0.5 work) diff --git a/plugins/mobile-apps/skills/enable-tables-offline/SKILL.md b/plugins/mobile-apps/skills/enable-tables-offline/SKILL.md index c1e5910a6..580f6dc27 100644 --- a/plugins/mobile-apps/skills/enable-tables-offline/SKILL.md +++ b/plugins/mobile-apps/skills/enable-tables-offline/SKILL.md @@ -7,11 +7,11 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion, EnterPlanMo model: sonnet --- -**Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)** β€” read first. +**Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** β€” read first. **References:** -- [offline-profile-schema.md](${CLAUDE_SKILL_DIR}/../../shared/references/offline-profile-schema.md) β€” entity field map (`IsAvailableOffline`, `ChangeTrackingEnabled`) +- [offline-profile-schema.md](${PLUGIN_ROOT}/shared/references/offline-profile-schema.md) β€” entity field map (`IsAvailableOffline`, `ChangeTrackingEnabled`) # Enable Tables Offline @@ -29,7 +29,7 @@ Sequential (Dataverse metadata lock) and idempotent: re-running on an already-en ```bash test -f power.config.json && test -f app.config.js -node "${CLAUDE_SKILL_DIR}/../../scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")" +node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")" ``` Capture the **Environment URL** from the resolver for ``. STOP if not authenticated. @@ -54,7 +54,7 @@ If the resolved list is empty, STOP with: "No Dataverse tables found in this pro For each table, in sequence: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET \ "EntityDefinitions(LogicalName='
')?\$select=LogicalName,DisplayName,IsAvailableOffline,ChangeTrackingEnabled,IsCustomizable" ``` @@ -99,7 +99,7 @@ If the user rejects, STOP. If they approve, proceed. For each table needing change (skip ones already in target state): ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/update-entity-offline-flags.js" \ +node "${PLUGIN_ROOT}/scripts/update-entity-offline-flags.js" \ --table
\ --offline true \ --tracking true @@ -128,7 +128,7 @@ Print `βœ“
` after each 204; print `β†·
(already enabled)` for no ```bash # Build the XML body from the list of tables modified in Step 4 -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" POST \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST \ "PublishXml" --body '{ "ParameterXml": "cr720_fcbflagcr720_rolloutevent" }' @@ -143,7 +143,7 @@ Substitute `...` lines for every table the helper PUT'd flags o **Fallback to `PublishAllXml`** (only when the targeted call returns a non-rate-limit error like `0x80048d19` malformed body): ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" POST \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST \ "PublishAllXml" --body '{}' ``` diff --git a/plugins/mobile-apps/skills/list-connections/SKILL.md b/plugins/mobile-apps/skills/list-connections/SKILL.md index 883629ed3..8fbe0b5e5 100644 --- a/plugins/mobile-apps/skills/list-connections/SKILL.md +++ b/plugins/mobile-apps/skills/list-connections/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Bash model: haiku --- -**πŸ“‹ Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)** β€” cross-cutting concerns (Windows CLI compatibility, memory bank, etc.). +**πŸ“‹ Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** β€” cross-cutting concerns (Windows CLI compatibility, memory bank, etc.). # List Connections diff --git a/plugins/mobile-apps/skills/preview-offline-scope/SKILL.md b/plugins/mobile-apps/skills/preview-offline-scope/SKILL.md index c13b3f073..9db0a3652 100644 --- a/plugins/mobile-apps/skills/preview-offline-scope/SKILL.md +++ b/plugins/mobile-apps/skills/preview-offline-scope/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Bash, AskUserQuestion model: sonnet --- -**Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)** β€” read first. +**Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** β€” read first. # Preview Offline Scope @@ -29,13 +29,13 @@ Same as `/edit-offline-profile` Step 1. Read profileId from `offline-profile.jso ```bash test -f power.config.json -node "${CLAUDE_SKILL_DIR}/../../scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")" +node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")" ``` ### Step 2 β€” Run verify ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/verify-offline-profile.js" \ +node "${PLUGIN_ROOT}/scripts/verify-offline-profile.js" \ --project-root "$(pwd)" ``` @@ -58,7 +58,7 @@ For current-user/current-BU filters, resolve identity only inside this skill by For each table: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET \ "?\$count=true&\$top=0&" ``` diff --git a/plugins/mobile-apps/skills/preview-screens/SKILL.md b/plugins/mobile-apps/skills/preview-screens/SKILL.md index 89ce332d5..dffdab4fd 100644 --- a/plugins/mobile-apps/skills/preview-screens/SKILL.md +++ b/plugins/mobile-apps/skills/preview-screens/SKILL.md @@ -94,7 +94,7 @@ Do not drop any screens β€” this rule only reorders. Every discovered screen sti Load the Tamagui-to-HTML mapping reference: ```text -Read file_path="${CLAUDE_SKILL_DIR}/../../shared/references/tamagui-html-mapping.md" +Read file_path="${PLUGIN_ROOT}/shared/references/tamagui-html-mapping.md" ``` Internalize: diff --git a/plugins/mobile-apps/skills/report-issue/SKILL.md b/plugins/mobile-apps/skills/report-issue/SKILL.md index b78a4d4f8..3efc6c1eb 100644 --- a/plugins/mobile-apps/skills/report-issue/SKILL.md +++ b/plugins/mobile-apps/skills/report-issue/SKILL.md @@ -7,7 +7,7 @@ allowed-tools: Read, Bash, Glob, Grep, AskUserQuestion model: haiku --- -**πŸ“‹ Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)** β€” read this first. +**πŸ“‹ Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** β€” read this first. # Report Issue β€” mobile-app diff --git a/plugins/mobile-apps/skills/setup-datamodel/SKILL.md b/plugins/mobile-apps/skills/setup-datamodel/SKILL.md index 86106df82..9e29a1bb8 100644 --- a/plugins/mobile-apps/skills/setup-datamodel/SKILL.md +++ b/plugins/mobile-apps/skills/setup-datamodel/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion, EnterPlanMo model: opus --- -**πŸ“‹ Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)** β€” read first. +**πŸ“‹ Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** β€” read first. # Set Up Data Model + Connectors @@ -30,7 +30,7 @@ Confirm we're inside a Power Apps mobile app: ```bash test -f power.config.json && echo "OK" || echo "ERROR: not a mobile app β€” run /create-mobile-app first" -node "${CLAUDE_SKILL_DIR}/../../scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")" +node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")" ``` Capture the **environment URL**, **environment ID**, **tenant ID**, and **organization ID** for Phase 5. @@ -75,7 +75,7 @@ Prompt: You are the data-model-architect agent for a Power Apps mobile app. Requirements: <$ARGUMENTS or ask the user what the app does> Working directory: - Plugin root: ${CLAUDE_SKILL_DIR}/../../ + Plugin root: ${PLUGIN_ROOT} Follow your agent file. Return a ## Data Model section with Mermaid ER diagram, reuse/extend/create table, and dependency-tier ordering. If requirements mention @@ -93,7 +93,7 @@ Write `## Data Model` as "None β€” no Dataverse tables needed." Continue to Phas ### Phase 3 β€” Plan Connectors -Follow [`shared/references/connector-planning.md`](${CLAUDE_SKILL_DIR}/../../shared/references/connector-planning.md): +Follow [`shared/references/connector-planning.md`](${PLUGIN_ROOT}/shared/references/connector-planning.md): 1. **Infer** β€” if `$ARGUMENTS` describes what the app does, scan for connector keywords. Build a candidate list. 2. **Confirm** β€” present via `AskUserQuestion`. Let the user add, remove, or confirm. @@ -170,10 +170,10 @@ If Phase 5 created or extended Dataverse tables, an existing Mobile Offline Prof Run the local, no-network delta check: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/offline-profile-delta.js" +node "${PLUGIN_ROOT}/scripts/offline-profile-delta.js" ``` -Branch on the JSON `status` per [offline-profile-reconciliation.md](${CLAUDE_SKILL_DIR}/../../shared/references/offline-profile-reconciliation.md): `no-manifest` / `no-profile` / `in-sync` β†’ continue to Phase 7 silently (do not nag when no profile exists); `delta` β†’ prompt to update, then read and execute `${CLAUDE_SKILL_DIR}/../add-table-to-offline-profile/SKILL.md` for `missingTables[]` and `${CLAUDE_SKILL_DIR}/../edit-offline-profile/SKILL.md` for `tablesWithNewColumns[]`, passing the arguments documented by each workflow, and re-check to `in-sync`. +Branch on the JSON `status` per [offline-profile-reconciliation.md](${PLUGIN_ROOT}/shared/references/offline-profile-reconciliation.md): `no-manifest` / `no-profile` / `in-sync` β†’ continue to Phase 7 silently (do not nag when no profile exists); `delta` β†’ prompt to update, then read and execute `${PLUGIN_ROOT}/skills/add-table-to-offline-profile/SKILL.md` for `missingTables[]` and `${PLUGIN_ROOT}/skills/edit-offline-profile/SKILL.md` for `tablesWithNewColumns[]`, passing the arguments documented by each workflow, and re-check to `in-sync`. ### Phase 7 β€” Summary @@ -204,7 +204,7 @@ Next steps: ## Reference -- [shared/references/connector-planning.md](${CLAUDE_SKILL_DIR}/../../shared/references/connector-planning.md) β€” connector inference + confirmation logic -- [shared/references/offline-profile-reconciliation.md](${CLAUDE_SKILL_DIR}/../../shared/references/offline-profile-reconciliation.md) β€” Phase 6.5 offline delta check + reconciliation flow +- [shared/references/connector-planning.md](${PLUGIN_ROOT}/shared/references/connector-planning.md) β€” connector inference + confirmation logic +- [shared/references/offline-profile-reconciliation.md](${PLUGIN_ROOT}/shared/references/offline-profile-reconciliation.md) β€” Phase 6.5 offline delta check + reconciliation flow - [skills/add-dataverse/SKILL.md](../add-dataverse/SKILL.md) β€” full data model execution workflow - [agents/data-model-architect.md](../../agents/data-model-architect.md) β€” read-only architect agent diff --git a/plugins/mobile-apps/skills/setup-offline-profile/SKILL.md b/plugins/mobile-apps/skills/setup-offline-profile/SKILL.md index ac9b6a566..a3a2ad1e0 100644 --- a/plugins/mobile-apps/skills/setup-offline-profile/SKILL.md +++ b/plugins/mobile-apps/skills/setup-offline-profile/SKILL.md @@ -6,13 +6,13 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion, EnterPlanMo model: opus --- -**Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)** β€” read first. +**Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** β€” read first. **References:** -- [offline-profile-schema.md](${CLAUDE_SKILL_DIR}/../../shared/references/offline-profile-schema.md) β€” Dataverse entity field map -- [dataverse-offline-api.md](${CLAUDE_SKILL_DIR}/../../shared/references/dataverse-offline-api.md) β€” Web API recipes for profile / item / association POSTs -- [offline-profile-reconciliation.md](${CLAUDE_SKILL_DIR}/../../shared/references/offline-profile-reconciliation.md) β€” the `schemaColumns` baseline this skill writes + the lifecycle delta check that consumes it +- [offline-profile-schema.md](${PLUGIN_ROOT}/shared/references/offline-profile-schema.md) β€” Dataverse entity field map +- [dataverse-offline-api.md](${PLUGIN_ROOT}/shared/references/dataverse-offline-api.md) β€” Web API recipes for profile / item / association POSTs +- [offline-profile-reconciliation.md](${PLUGIN_ROOT}/shared/references/offline-profile-reconciliation.md) β€” the `schemaColumns` baseline this skill writes + the lifecycle delta check that consumes it # Setup Offline Profile @@ -39,7 +39,7 @@ test -f power.config.json && test -f app.config.js MANIFEST=$(test -f .datamodel-manifest.json && echo ".datamodel-manifest.json" || \ (test -f docs/plan-artifacts/.datamodel-manifest.json && echo "docs/plan-artifacts/.datamodel-manifest.json")) test -n "$MANIFEST" && echo "βœ“ manifest at $MANIFEST" -node "${CLAUDE_SKILL_DIR}/../../scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")" +node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")" ``` Capture **Environment URL** for `` and **manifest path** for the architect spawn (Step 3) and the artifacts write (Step 9). @@ -65,7 +65,7 @@ console.log(JSON.stringify({ platforms, hasNative })); STOP conditions: - No `power.config.json` β†’ "Run `/create-mobile-app` first." - Neither `.datamodel-manifest.json` nor `docs/plan-artifacts/.datamodel-manifest.json` β†’ "Run `/add-dataverse` first β€” offline profiles require a data model." -- Environment resolution failure β†’ standard auth recovery (`az login --tenant ` or provide environment URL directly; see [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)). +- Environment resolution failure β†’ standard auth recovery (`az login --tenant ` or provide environment URL directly; see [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)). - Web-only + user declines override β†’ STOP. Print: `Offline profile creation skipped β€” no native target.` #### Step 1a β€” Environment consistency check @@ -123,7 +123,7 @@ Read `memory-bank.md` `## Offline profile` block. Decide based on `status`: > "β†’ Checking for existing offline profiles in the environment…" ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET \ "mobileofflineprofiles?\$select=mobileofflineprofileid,name,description,publishedon" ``` @@ -318,7 +318,7 @@ configReview: accepted ### Step 4 β€” Run the internal `enable-tables-offline` workflow if needed -If Gate 1 identified any table needing change, read and execute `${CLAUDE_SKILL_DIR}/../enable-tables-offline/SKILL.md` with the table list as its `$ARGUMENTS`: +If Gate 1 identified any table needing change, read and execute `${PLUGIN_ROOT}/skills/enable-tables-offline/SKILL.md` with the table list as its `$ARGUMENTS`: ```text $ARGUMENTS: cr123_note,cr123_visit @@ -338,7 +338,7 @@ If all tables were already enabled, skip this step. For `create-new` mode: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" POST \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST \ "mobileofflineprofiles" \ --body '{ "name": " Offline Profile", @@ -374,7 +374,7 @@ gate1: approved For each table, in sequence: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" POST \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST \ "mobileofflineprofileitems" \ --body '{ "name": "
", @@ -408,7 +408,7 @@ Print `βœ“
` after each 2xx. Empirical 2026-05-24 + 2026-05-25 capture from maker portal **unblocked** association creation. Recipe (no `selectedrelationshipsschema` field β€” server fills it): ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" POST \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST \ "mobileofflineprofileitemassociations" \ --body '{ "name": "", @@ -429,7 +429,7 @@ For each (PARENT-item, 1:N-relationship-to-child) pair from the architect's prop > **Skip-when-redundant rule.** If the parent profile item has `recorddistributioncriteria=1` (All records), the architect should have pruned associations on it (per Step 5 pruning rule). Defensively: if any propagate through, SKIP them at POST time. Print: `β†· Skipping on All-records parent
β€” redundant, all rows download anyway`. -> **Pre-publish cycle detection.** Before Step 8 publish, build the relationship graph from the just-created associations and DFS for cycles. If a cycle exists (e.g. `account β†’ task β†’ account`), prompt the user to remove ONE association and re-do this step for the affected pair β€” otherwise Step 8 will return `0x80071141`. See [shared/references/dataverse-offline-api.md Β§6b](${CLAUDE_SKILL_DIR}/../../shared/references/dataverse-offline-api.md). +> **Pre-publish cycle detection.** Before Step 8 publish, build the relationship graph from the just-created associations and DFS for cycles. If a cycle exists (e.g. `account β†’ task β†’ account`), prompt the user to remove ONE association and re-do this step for the affected pair β€” otherwise Step 8 will return `0x80071141`. See [shared/references/dataverse-offline-api.md Β§6b](${PLUGIN_ROOT}/shared/references/dataverse-offline-api.md). > **Idempotency.** Re-POSTing an existing (item, relationship) pair returns `409 Conflict`; the `dataverse-request.js` `looksLikeDuplicate` rescue treats this as success. Re-runs after a partial failure are safe. @@ -438,7 +438,7 @@ Skip Step 7a entirely if the architect's proposal includes zero relationships (r #### Step 7b β€” PATCH `selectedcolumns` on each item ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" PATCH \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" PATCH \ "mobileofflineprofileitems()" \ --body '{ "selectedcolumns": "" @@ -467,7 +467,7 @@ If syncintervalinminutes was edited at Gate 3, include it in the same PATCH. **Targeted PublishXml β€” the maker portal's pattern** (empirical 2026-05-24): ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" POST \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST \ "PublishXml" --body '{ "ParameterXml": "'"$PROFILE_ID"'" }' @@ -475,7 +475,7 @@ node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" POST \ Publishes ONLY this profile, not the entire org's customizations. Empirically much faster + less rate-limit-prone than `PublishAllXml` on shared envs. -**On `400 / 0x80071141` "circular relationship":** the profile's association graph has a cycle (e.g. `account β†’ task β†’ account`). Parse the path from the error message, prompt the user to drop ONE of the offending associations, re-DELETE that association row via DELETE `/mobileofflineprofileitemassociations()`, then re-attempt publish. See [shared/references/dataverse-offline-api.md Β§6b](${CLAUDE_SKILL_DIR}/../../shared/references/dataverse-offline-api.md). +**On `400 / 0x80071141` "circular relationship":** the profile's association graph has a cycle (e.g. `account β†’ task β†’ account`). Parse the path from the error message, prompt the user to drop ONE of the offending associations, re-DELETE that association row via DELETE `/mobileofflineprofileitemassociations()`, then re-attempt publish. See [shared/references/dataverse-offline-api.md Β§6b](${PLUGIN_ROOT}/shared/references/dataverse-offline-api.md). **On `400 / 0x80071140` "no relationships are specified" for a Related-only table:** a profile item has `recorddistributioncriteria=0` but no associations point at it. Parse the table name from the error message and prompt the user with two choices: 1. `/add-table-to-offline-profile --table --add-associations` β€” re-runs the architect for relationship discovery, POSTs missing associations. @@ -486,7 +486,7 @@ For fresh `/setup-offline-profile` runs this error should never fire because Ste **Fallback β€” `PublishAllXml`:** if the targeted publish fails with anything other than the circular-relationship error, try the broad publish. This was the v0.1 default and works correctly but rate-limits aggressively on shared envs: ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" POST \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST \ "PublishAllXml" --body '{}' ``` @@ -498,7 +498,7 @@ Protocol after the POST call returns: 2. If status `0` AND error contains `Request timed out` OR `429 rate-limited` β†’ DO NOT treat as failure. Run the verification GET below: ```bash - node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js" GET \ + node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET \ "mobileofflineprofiles()?\$select=componentstate,publishedon" 2>&1 ``` @@ -563,7 +563,7 @@ After confirmed success, re-GET the profile and check `publishedon` for the arti - `relationships[]` lives on the **PARENT** table entry (the table on the 1-side of the 1:N relationship). Pure-child tables (e.g. `cr123_orderline`) have empty `relationships: []`. - Each `relationships[]` entry has `schemaName` (the relationship's `SchemaName` from EntityDefinitions metadata), `relationshipId` (the relationship's `MetadataId` GUID β€” **this is the canonical comparison key**, stable across server-side relationshipname formatting), `targetEntity` (child entity logical name), and `associationId` (the created `mobileofflineprofileitemassociationid`). - `recordDistributionCriteria=1` (All records) parents always have `relationships: []` β€” associations would be redundant (see architect Step 5 pruning rule). -- `schemaColumns[]` is the **schema-reconciliation baseline** β€” the full set of the table's schema column logical names (from `.datamodel-manifest.json`) that existed when this item was created. It is NOT the same as `selectedColumns` (which is a curated subset the runtime syncs): `schemaColumns` records everything the schema had at reconciliation time, including columns deliberately left out of `selectedColumns`. `scripts/offline-profile-delta.js` compares later manifest columns against this baseline to detect *genuinely new* schema columns (`manifest.columns βˆ’ schemaColumns`) without false-flagging deliberate exclusions. Populate it from the manifest entry for each table; if the manifest lists no columns for a reused table, write `[]`. See [offline-profile-reconciliation.md](${CLAUDE_SKILL_DIR}/../../shared/references/offline-profile-reconciliation.md). +- `schemaColumns[]` is the **schema-reconciliation baseline** β€” the full set of the table's schema column logical names (from `.datamodel-manifest.json`) that existed when this item was created. It is NOT the same as `selectedColumns` (which is a curated subset the runtime syncs): `schemaColumns` records everything the schema had at reconciliation time, including columns deliberately left out of `selectedColumns`. `scripts/offline-profile-delta.js` compares later manifest columns against this baseline to detect *genuinely new* schema columns (`manifest.columns βˆ’ schemaColumns`) without false-flagging deliberate exclusions. Populate it from the manifest entry for each table; if the manifest lists no columns for a reused table, write `[]`. See [offline-profile-reconciliation.md](${PLUGIN_ROOT}/shared/references/offline-profile-reconciliation.md). - JSON doesn't support comments β€” the `/* BEGIN/END OFFLINE-CONFIG-WORKAROUND */` keys above are illustrative bracketing for the SKILL author. **Do NOT write those literal keys to `offline-profile.json`.** When the skill actually patches the file, emit only the real fields (`useDda`, `entitiesIncluded`, `instanceUrl`) inside `appConfig` and keep them contiguous. Example node script (writes the file in one shot β€” no read-modify-write against `power.config.json`): @@ -634,7 +634,7 @@ associationsCount: > "β†’ Verifying the on-server profile matches offline-profile.json…" ```bash -node "${CLAUDE_SKILL_DIR}/../../scripts/verify-offline-profile.js" +node "${PLUGIN_ROOT}/scripts/verify-offline-profile.js" ``` Read the JSON output: