Compare commits
261 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b05d5fee28 | |||
| 14c2d5ba3f | |||
| c43ee13eed | |||
| af07e75d58 | |||
| ac2ef1aaef | |||
| 191054d876 | |||
| b8b6ed8b85 | |||
| bdc61b5ad5 | |||
| 31dad4109b | |||
| c3643126ef | |||
| dcfc2134d4 | |||
| 77100fac5d | |||
| b1614d7504 | |||
| 0ae45cd445 | |||
| fcf4660389 | |||
| 221ea40462 | |||
| 12812dc994 | |||
| 4c3812f731 | |||
| 098a830e5d | |||
| f31e7a75be | |||
| 09c44a637a | |||
| dc81b8bbd6 | |||
| f7e46e33e9 | |||
| 5d64f726d6 | |||
| 1e1a8a7a40 | |||
| 872b9c9045 | |||
| 1e3f8e00f7 | |||
| 919907cf0f | |||
| d5cd6a1a56 | |||
| 700c98fcaf | |||
| df9eb0bf10 | |||
| 4e55f9aa32 | |||
| 127255aa96 | |||
| b46de60cd9 | |||
| 736551590a | |||
| e031d024ef | |||
| d042483a3f | |||
| 39f252df97 | |||
| 3f70561586 | |||
| b72a203ed7 | |||
| 1ecf0fb6a7 | |||
| c743313078 | |||
| 1bbe936513 | |||
| c966e20746 | |||
| f42976f852 | |||
| 6d90572257 | |||
| f84ec34cbd | |||
| 566a370375 | |||
| 4c1dd1e5ed | |||
| 8f64e844c9 | |||
| 7f7bba633d | |||
| 1a15ea183a | |||
| 8c0600225a | |||
| 832355081b | |||
| 1081c320ab | |||
| bdeeb036fb | |||
| 4b1bf5f7ba | |||
| f935d9a7bf | |||
| dd325d790a | |||
| ed786fb12c | |||
| cc5eeefde9 | |||
| cf53d15ae5 | |||
| 7351e21150 | |||
| c5ab66152a | |||
| 1805301941 | |||
| e8633e0299 | |||
| 6bcaa46864 | |||
| d545472da8 | |||
| 025db33a75 | |||
| 731db47fd7 | |||
| 4b6aea9f5e | |||
| 0568059fb8 | |||
| 3e5df300ab | |||
| 221ed882fa | |||
| df8a2c43f4 | |||
| 391d23c65e | |||
| 092cea6073 | |||
| de2dc7ca13 | |||
| 5236e02308 | |||
| 8e1d873edc | |||
| 4ca3ee97c3 | |||
| e6ad74afed | |||
| 79f41e54af | |||
| 4f9be499b4 | |||
| 42b534366d | |||
| 7528cc51fa | |||
| bbb1baa631 | |||
| 91b350efa8 | |||
| 50ca951540 | |||
| 18bfb0bc4a | |||
| 3b0fb81570 | |||
| bdbc14902e | |||
| dc6716828b | |||
| 8de74d0984 | |||
| b89c3084e7 | |||
| bdf6d257b7 | |||
| 678cd321f5 | |||
| a0e34557f9 | |||
| 5c7c9dd5fa | |||
| 4f31277f4f | |||
| c16377af25 | |||
| 130cec466f | |||
| 65b94a1293 | |||
| 896a3f31ad | |||
| ea81b52442 | |||
| edc9cc5af8 | |||
| 2fa93afe2b | |||
| ee22084d1a | |||
| ca2fc0fa85 | |||
| 0c14bcbe55 | |||
| 08d899a2d2 | |||
| 0d4160e968 | |||
| 8c15428f43 | |||
| 59b12edcb6 | |||
| dd3b79ebf4 | |||
| d64e41dd99 | |||
| f5769f0bfb | |||
| 292a21319d | |||
| c5e1bedca2 | |||
| c0540c8846 | |||
| ece9b5bf4a | |||
| 51420790c3 | |||
| c407de6e2b | |||
| 950265fc8c | |||
| 28fea0db0c | |||
| b5c3663126 | |||
| a757f40d83 | |||
| 7a4e052533 | |||
| 9a57042cbe | |||
| 754b01a066 | |||
| 5a1735b041 | |||
| a0ff232ad7 | |||
| 17f2f1bdc6 | |||
| e48f2f3a8c | |||
| 7785eae516 | |||
| 28c5d95964 | |||
| 0e53c3f3d5 | |||
| 6bf8c4693f | |||
| 8e3cff657b | |||
| 15d508e814 | |||
| 6570674abf | |||
| 2d71d9ac65 | |||
| f43d2028ff | |||
| 6c9f0cb46d | |||
| bd13929de8 | |||
| f5a7ea1606 | |||
| 9c42292ed8 | |||
| ec0f51ed75 | |||
| 5ae53ecae8 | |||
| 5066511576 | |||
| 0b6961b0d7 | |||
| 089e111fcc | |||
| 93fddd14de | |||
| f1873b27a8 | |||
| 81c157d063 | |||
| 1f5520ebb1 | |||
| 0aef1ef26f | |||
| 693f75149a | |||
| 5e4dd04b66 | |||
| 8280910e48 | |||
| 37a69f0c9d | |||
| 359c7fbf4e | |||
| 0d2882e1c0 | |||
| 541498f58a | |||
| b85ac155cf | |||
| 5f42ccc3e9 | |||
| 8fdfc523a8 | |||
| a44a27b29a | |||
| 0cac182b73 | |||
| b69ff4ceb9 | |||
| 4337d2e928 | |||
| a0be6adcf8 | |||
| dffdfa694c | |||
| a40ff52db0 | |||
| bd41ce5692 | |||
| 1f09a5d1a5 | |||
| 0b99f560d4 | |||
| 866d5b1e9b | |||
| d4ad62f522 | |||
| 3aff575002 | |||
| 7df9d96dac | |||
| 05fb208fda | |||
| 33f031be04 | |||
| 0e11f95163 | |||
| 2b07ac22c2 | |||
| efa364f73d | |||
| 82265ff157 | |||
| 015ff1a45b | |||
| cdb4bb2acc | |||
| 3c7f94e3d5 | |||
| ef55f97df3 | |||
| 5880551c6a | |||
| fb6c2982e6 | |||
| 81944b02c5 | |||
| c1cc626e52 | |||
| 6f3d38ad3c | |||
| d0e4a92738 | |||
| 2ea6961e0d | |||
| 1195565b96 | |||
| 7478cbb59d | |||
| 79d878f240 | |||
| b198606bef | |||
| 1cb6c0b14c | |||
| 3f6bdc7ff7 | |||
| cc4ec68bce | |||
| 01d2f64b90 | |||
| 39d8a9a6ac | |||
| 4f02c6b56e | |||
| 3f77cd5927 | |||
| 89aa25e5d0 | |||
| cdd735dc63 | |||
| f59c15a410 | |||
| 94319c7531 | |||
| 35bc17e4fe | |||
| d65dcfa806 | |||
| e35dcd2050 | |||
| 251121530d | |||
| ec110b7bff | |||
| bd627f1096 | |||
| e3eedf9e94 | |||
| eb678bfa59 | |||
| dc8839365f | |||
| 3f05214013 | |||
| 86de174d86 | |||
| d9f2ed0d44 | |||
| c331bf949c | |||
| 3d62c8b5c1 | |||
| 9c3a9d7e13 | |||
| e189dcc101 | |||
| 4f4b9dc048 | |||
| fdabc14892 | |||
| da3b00f961 | |||
| 8d5b316fdd | |||
| b53fd7c7f5 | |||
| 5a424fa8df | |||
| cf043b7e20 | |||
| 7276e4cff7 | |||
| f9258c9d82 | |||
| 8b245f1b0a | |||
| a092ed06c4 | |||
| de67da4bc6 | |||
| f89ccb091f | |||
| 2bd8ef3f72 | |||
| 0e8893faea | |||
| f9ab939c74 | |||
| 7a446e4979 | |||
| 0f33fa6c40 | |||
| e108dac1bb | |||
| 3fcd2008ef | |||
| 75f36edb8f | |||
| 3672e1a3ea | |||
| bc9b3229c6 | |||
| a15881db1a | |||
| 1dd4262336 | |||
| 687357c82e | |||
| 3d1e544812 | |||
| 9bb69383b8 | |||
| 1831ace87e | |||
| 478afc7046 | |||
| 1299d044eb | |||
| 273a474047 |
@@ -1,3 +1,6 @@
|
||||
[env]
|
||||
JEMALLOC_SYS_WITH_MALLOC_CONF = "dirty_decay_ms:1000,muzzy_decay_ms:0"
|
||||
|
||||
[build]
|
||||
target-dir = 'dist/target'
|
||||
|
||||
|
||||
@@ -30,5 +30,16 @@
|
||||
"enableAllProjectMcpServers": true,
|
||||
"env": {
|
||||
"BASH_MAX_TIMEOUT_MS": "1800000"
|
||||
},
|
||||
"extraKnownMarketplaces": {
|
||||
"nx-claude-plugins": {
|
||||
"source": {
|
||||
"source": "github",
|
||||
"repo": "nrwl/nx-ai-agents-config"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enabledPlugins": {
|
||||
"nx@nx-claude-plugins": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,480 @@
|
||||
---
|
||||
name: ci-watcher
|
||||
description: Polls Nx Cloud CI pipeline and self-healing status. Returns structured state when actionable. Spawned by /nx-cloud-ci-monitor command to monitor CI Attempt status.
|
||||
model: fast
|
||||
---
|
||||
|
||||
# CI Watcher Subagent
|
||||
|
||||
You are a CI monitoring subagent responsible for polling Nx Cloud CI Attempt status and self-healing state. You report status back to the main agent - you do NOT make apply/reject decisions.
|
||||
|
||||
## Your Responsibilities
|
||||
|
||||
1. Poll CI status using the `ci_information` MCP tool
|
||||
2. Implement exponential backoff between polls
|
||||
3. Return structured state when an actionable condition is reached
|
||||
4. Track iteration count and elapsed time
|
||||
5. Output status updates based on verbosity level
|
||||
|
||||
## Input Parameters (from Main Agent)
|
||||
|
||||
The main agent may provide these optional parameters in the prompt:
|
||||
|
||||
| Parameter | Description |
|
||||
| ------------------- | -------------------------------------------------------- |
|
||||
| `branch` | Branch to monitor (auto-detected if not provided) |
|
||||
| `expectedCommitSha` | Commit SHA that should trigger a new CI Attempt |
|
||||
| `previousCipeUrl` | CI Attempt URL before the action (to detect change) |
|
||||
| `subagentTimeout` | Polling timeout in minutes (default: 60) |
|
||||
| `verbosity` | Output level: minimal, medium, verbose (default: medium) |
|
||||
|
||||
When `expectedCommitSha` or `previousCipeUrl` is provided, you must detect whether a new CI Attempt has spawned.
|
||||
|
||||
## MCP Tool Reference
|
||||
|
||||
### `ci_information`
|
||||
|
||||
**Input:**
|
||||
|
||||
```json
|
||||
{
|
||||
"branch": "string (optional, defaults to current git branch)",
|
||||
"select": "string (optional, comma-separated field names)",
|
||||
"pageToken": "number (optional, 0-based pagination for long strings)"
|
||||
}
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
```json
|
||||
{
|
||||
"cipeStatus": "NOT_STARTED | IN_PROGRESS | SUCCEEDED | FAILED | CANCELED | TIMED_OUT",
|
||||
"cipeUrl": "string",
|
||||
"branch": "string",
|
||||
"commitSha": "string | null",
|
||||
"failedTaskIds": "string[]",
|
||||
"verifiedTaskIds": "string[]",
|
||||
"selfHealingEnabled": "boolean",
|
||||
"selfHealingStatus": "NOT_STARTED | IN_PROGRESS | COMPLETED | FAILED | NOT_EXECUTABLE | null",
|
||||
"verificationStatus": "NOT_STARTED | IN_PROGRESS | COMPLETED | FAILED | NOT_EXECUTABLE | null",
|
||||
"userAction": "NONE | APPLIED | REJECTED | APPLIED_LOCALLY | APPLIED_AUTOMATICALLY | null",
|
||||
"failureClassification": "string | null",
|
||||
"taskOutputSummary": "string | null",
|
||||
"suggestedFixReasoning": "string | null",
|
||||
"suggestedFixDescription": "string | null",
|
||||
"suggestedFix": "string | null",
|
||||
"shortLink": "string | null",
|
||||
"couldAutoApplyTasks": "boolean | null",
|
||||
"confidence": "number | null",
|
||||
"confidenceReasoning": "string | null"
|
||||
}
|
||||
```
|
||||
|
||||
**Select Parameter:**
|
||||
|
||||
| Usage | Returns |
|
||||
| --------------- | ----------------------------------------------------------- |
|
||||
| No `select` | Formatted overview (truncated, not recommended for polling) |
|
||||
| Single field | Raw value with pagination for long strings |
|
||||
| Multiple fields | Object with requested field values |
|
||||
|
||||
**Field Sets for Efficient Polling:**
|
||||
|
||||
```yaml
|
||||
WAIT_FIELDS:
|
||||
'cipeUrl,commitSha,cipeStatus'
|
||||
# Minimal fields for detecting new CI Attempt
|
||||
|
||||
LIGHT_FIELDS:
|
||||
'cipeStatus,cipeUrl,branch,commitSha,selfHealingStatus,verificationStatus,userAction,failedTaskIds,verifiedTaskIds,selfHealingEnabled,failureClassification,couldAutoApplyTasks,shortLink,confidence,confidenceReasoning'
|
||||
# Status fields for determining actionable state
|
||||
|
||||
HEAVY_FIELDS:
|
||||
'taskOutputSummary,suggestedFix,suggestedFixReasoning,suggestedFixDescription'
|
||||
# Large content fields - fetch only when returning to main agent
|
||||
```
|
||||
|
||||
## Initial Wait
|
||||
|
||||
Before first poll, wait based on context:
|
||||
|
||||
- **Fresh start (no expected CIPE):** Wait 60 seconds to allow CI to start
|
||||
- **Expecting new CIPE:** Wait 30 seconds (action already triggered)
|
||||
|
||||
**IMPORTANT:** Always run sleep in foreground, NOT as background command.
|
||||
|
||||
```bash
|
||||
sleep 60 # or 30 if expecting new CIPE (FOREGROUND, not background)
|
||||
```
|
||||
|
||||
## Two-Phase Operation
|
||||
|
||||
The subagent operates in one of two modes depending on input:
|
||||
|
||||
### Mode 1: Fresh Start (no `expectedCommitSha` or `previousCipeUrl`)
|
||||
|
||||
Normal polling - process whatever CIPE is returned by `ci_information`.
|
||||
|
||||
### Mode 2: Wait-for-New-CIPE (when `expectedCommitSha` or `previousCipeUrl` provided)
|
||||
|
||||
**CRITICAL**: When expecting a new CIPE, the subagent must **completely ignore** the old/stale CIPE. Do NOT process its status, do NOT return actionable states based on it.
|
||||
|
||||
#### Phase A: Wait Mode
|
||||
|
||||
1. Start a **new-CIPE timeout** timer (default: 30 minutes)
|
||||
2. On each poll of `ci_information`:
|
||||
- Check if CIPE is NEW:
|
||||
- `cipeUrl` differs from `previousCipeUrl` → **new CIPE detected**
|
||||
- `commitSha` matches `expectedCommitSha` → **correct CIPE detected**
|
||||
- If still OLD CIPE: **ignore all status fields**, just wait and poll again
|
||||
- Do NOT return `fix_available`, `ci_success`, etc. based on old CIPE!
|
||||
3. Output wait status (see below)
|
||||
4. If timeout (30 min) reached → return `no_new_cipe`
|
||||
|
||||
#### Phase B: Normal Polling (after new CIPE detected)
|
||||
|
||||
Once new CIPE is detected:
|
||||
|
||||
1. Clear the new-CIPE timeout
|
||||
2. Switch to normal polling mode
|
||||
3. Process the NEW CIPE's status normally
|
||||
4. Return when actionable state reached
|
||||
|
||||
### Wait Mode Output
|
||||
|
||||
While in wait mode, output clearly that you're waiting (not processing):
|
||||
|
||||
```
|
||||
[CI Monitor] ═══════════════════════════════════════════════════════
|
||||
[CI Monitor] WAIT MODE - Expecting new CI Attempt
|
||||
[CI Monitor] Expected SHA: <expectedCommitSha>
|
||||
[CI Monitor] Previous CI Attempt: <previousCipeUrl>
|
||||
[CI Monitor] ═══════════════════════════════════════════════════════
|
||||
|
||||
[CI Monitor] Polling... (elapsed: 0m 30s)
|
||||
[CI Monitor] Still seeing previous CI Attempt (ignoring): <oldCipeUrl>
|
||||
|
||||
[CI Monitor] Polling... (elapsed: 1m 30s)
|
||||
[CI Monitor] Still seeing previous CI Attempt (ignoring): <oldCipeUrl>
|
||||
|
||||
[CI Monitor] Polling... (elapsed: 2m 30s)
|
||||
[CI Monitor] ✓ New CI Attempt detected! URL: <newCipeUrl>, SHA: <newCommitSha>
|
||||
[CI Monitor] Switching to normal polling mode...
|
||||
```
|
||||
|
||||
### Why This Matters (Context Preservation)
|
||||
|
||||
**The problem**: Stale CIPE data can be very large:
|
||||
|
||||
- `taskOutputSummary`: potentially thousands of characters of build/test output
|
||||
- `suggestedFix`: entire patch files
|
||||
- `suggestedFixReasoning`: detailed explanation
|
||||
|
||||
If subagent returns stale CIPE data to main agent, it **pollutes main agent's context** with useless information (we already processed that CIPE). This wastes valuable context window.
|
||||
|
||||
**Without wait mode:**
|
||||
|
||||
1. Poll `ci_information` → get old CIPE with huge data
|
||||
2. Return to main agent with all that stale data
|
||||
3. Main agent's context gets polluted with useless info
|
||||
4. Main agent has to process/ignore it anyway
|
||||
|
||||
**With wait mode:**
|
||||
|
||||
1. Poll `ci_information` → get old CIPE → **ignore it, don't return**
|
||||
2. Keep waiting internally (stale data stays in subagent)
|
||||
3. New CIPE appears → switch to normal mode
|
||||
4. Return to main agent with only the NEW, relevant CIPE data
|
||||
|
||||
## Polling Loop
|
||||
|
||||
### Subagent State Management
|
||||
|
||||
Maintain internal accumulated state across polls:
|
||||
|
||||
```
|
||||
accumulated_state = {}
|
||||
```
|
||||
|
||||
### Call `ci_information` MCP Tool
|
||||
|
||||
**Wait Mode (expecting new CI Attempt):**
|
||||
|
||||
```
|
||||
ci_information({
|
||||
branch: "<branch_name>",
|
||||
select: "cipeUrl,commitSha,cipeStatus"
|
||||
})
|
||||
```
|
||||
|
||||
Only fetch minimal fields needed to detect CI Attempt change. Do NOT fetch heavy fields - stale data wastes context.
|
||||
|
||||
**Normal Mode (processing CI Attempt):**
|
||||
|
||||
```
|
||||
ci_information({
|
||||
branch: "<branch_name>",
|
||||
select: "cipeStatus,cipeUrl,branch,commitSha,selfHealingStatus,verificationStatus,userAction,failedTaskIds,verifiedTaskIds,selfHealingEnabled,failureClassification,couldAutoApplyTasks,shortLink,confidence,confidenceReasoning"
|
||||
})
|
||||
```
|
||||
|
||||
Merge response into `accumulated_state` after each poll.
|
||||
|
||||
### Analyze Response
|
||||
|
||||
**If in Wait Mode** (expecting new CIPE):
|
||||
|
||||
1. Check if CIPE is new (see Two-Phase Operation above)
|
||||
2. If old CIPE → **ignore status**, output wait message, poll again
|
||||
3. If new CIPE → switch to normal mode, continue below
|
||||
|
||||
**If in Normal Mode**:
|
||||
Based on the response, decide whether to **keep polling** or **return to main agent**.
|
||||
|
||||
### Keep Polling When
|
||||
|
||||
Continue polling (with backoff) if ANY of these conditions are true:
|
||||
|
||||
| Condition | Reason |
|
||||
| --------------------------------------- | ---------------------------------------- |
|
||||
| `cipeStatus == 'IN_PROGRESS'` | CI still running |
|
||||
| `cipeStatus == 'NOT_STARTED'` | CI hasn't started yet |
|
||||
| `selfHealingStatus == 'IN_PROGRESS'` | Self-healing agent working |
|
||||
| `selfHealingStatus == 'NOT_STARTED'` | Self-healing not started yet |
|
||||
| `failureClassification == 'FLAKY_TASK'` | Auto-rerun in progress |
|
||||
| `userAction == 'APPLIED_AUTOMATICALLY'` | New CI Attempt spawning after auto-apply |
|
||||
|
||||
When `couldAutoApplyTasks == true`:
|
||||
|
||||
- `verificationStatus` = `NOT_STARTED`, `IN_PROGRESS` → keep polling (verification still in progress)
|
||||
- `verificationStatus` = `COMPLETED` → return `fix_auto_applying` (auto-apply will happen, main agent spawns wait mode subagent)
|
||||
- `verificationStatus` = `FAILED`, `NOT_EXECUTABLE` → return `fix_available` (auto-apply won't happen, needs manual action)
|
||||
|
||||
### Exponential Backoff
|
||||
|
||||
Between polls, wait with exponential backoff:
|
||||
|
||||
| Poll Attempt | Wait Time |
|
||||
| ------------ | ----------------- |
|
||||
| 1st | 60 seconds |
|
||||
| 2nd | 90 seconds |
|
||||
| 3rd+ | 120 seconds (cap) |
|
||||
|
||||
Reset to 60 seconds when state changes significantly.
|
||||
|
||||
**IMPORTANT:** Run sleep in foreground (NOT as background command). Background sleep causes "What should Claude do?" prompts when completed.
|
||||
|
||||
```bash
|
||||
# Example backoff - run in FOREGROUND
|
||||
sleep 60 # First wait
|
||||
sleep 90 # Second wait
|
||||
sleep 120 # Third and subsequent waits (capped)
|
||||
```
|
||||
|
||||
### Fetch Heavy Fields on Actionable State
|
||||
|
||||
Before returning to main agent, fetch heavy fields if the status requires them:
|
||||
|
||||
| Status | Heavy Fields Needed |
|
||||
| ------------------- | ------------------------------------------------------------------------------ |
|
||||
| `ci_success` | None |
|
||||
| `fix_auto_applying` | None |
|
||||
| `fix_available` | `taskOutputSummary,suggestedFix,suggestedFixReasoning,suggestedFixDescription` |
|
||||
| `fix_failed` | `taskOutputSummary` |
|
||||
| `no_fix` | `taskOutputSummary` |
|
||||
| `environment_issue` | None |
|
||||
| `no_new_cipe` | None |
|
||||
| `polling_timeout` | None |
|
||||
| `cipe_canceled` | None |
|
||||
| `cipe_timed_out` | None |
|
||||
|
||||
```
|
||||
# Example: fetching heavy fields for fix_available
|
||||
ci_information({
|
||||
branch: "<branch_name>",
|
||||
select: "taskOutputSummary,suggestedFix,suggestedFixReasoning,suggestedFixDescription"
|
||||
})
|
||||
```
|
||||
|
||||
Merge response into `accumulated_state`, then return merged state to main agent.
|
||||
|
||||
**Pagination:** Heavy string fields return first page only. If `hasMore` indicated, include in return format so main agent knows more content available.
|
||||
|
||||
### Return to Main Agent When
|
||||
|
||||
Return immediately with structured state if ANY of these conditions are true:
|
||||
|
||||
| Status | Condition |
|
||||
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ci_success` | `cipeStatus == 'SUCCEEDED'` |
|
||||
| `fix_auto_applying` | `selfHealingStatus == 'COMPLETED'` AND `couldAutoApplyTasks == true` AND `verificationStatus == 'COMPLETED'` |
|
||||
| `fix_available` | `selfHealingStatus == 'COMPLETED'` AND `suggestedFix != null` AND (`couldAutoApplyTasks != true` OR `verificationStatus` in (`FAILED`, `NOT_EXECUTABLE`)) |
|
||||
| `fix_failed` | `selfHealingStatus == 'FAILED'` |
|
||||
| `environment_issue` | `failureClassification == 'ENVIRONMENT_STATE'` |
|
||||
| `no_fix` | `cipeStatus == 'FAILED'` AND (`selfHealingEnabled == false` OR `selfHealingStatus == 'NOT_EXECUTABLE'`) |
|
||||
| `no_new_cipe` | `expectedCommitSha` or `previousCipeUrl` provided, but no new CI Attempt detected after 30 min |
|
||||
| `polling_timeout` | Subagent has been polling for > configured timeout (default 60 min) |
|
||||
| `cipe_canceled` | `cipeStatus == 'CANCELED'` |
|
||||
| `cipe_timed_out` | `cipeStatus == 'TIMED_OUT'` |
|
||||
|
||||
## Subagent Timeout
|
||||
|
||||
Track elapsed time. If you have been polling for more than **60 minutes** (configurable via main agent), return with `status: polling_timeout`.
|
||||
|
||||
## Return Format
|
||||
|
||||
When returning to the main agent, provide a structured response with accumulated state:
|
||||
|
||||
```
|
||||
## CI Monitor Result
|
||||
|
||||
**Status:** <status>
|
||||
**Iterations:** <count>
|
||||
**Elapsed:** <minutes>m <seconds>s
|
||||
|
||||
### CI Attempt Details
|
||||
- **Status:** <cipeStatus>
|
||||
- **URL:** <cipeUrl>
|
||||
- **Branch:** <branch>
|
||||
- **Commit:** <commitSha>
|
||||
- **Failed Tasks:** <failedTaskIds>
|
||||
- **Verified Tasks:** <verifiedTaskIds>
|
||||
|
||||
### Self-Healing Details
|
||||
- **Enabled:** <selfHealingEnabled>
|
||||
- **Status:** <selfHealingStatus>
|
||||
- **Verification:** <verificationStatus>
|
||||
- **User Action:** <userAction>
|
||||
- **Classification:** <failureClassification>
|
||||
- **Confidence:** <confidence>
|
||||
- **Confidence Reasoning:** <confidenceReasoning>
|
||||
|
||||
### Fix Information (if available)
|
||||
- **Short Link:** <shortLink>
|
||||
- **Description:** <suggestedFixDescription>
|
||||
- **Reasoning:** <suggestedFixReasoning>
|
||||
|
||||
### Task Output Summary (first page)
|
||||
<taskOutputSummary>
|
||||
[MORE_CONTENT_AVAILABLE: taskOutputSummary, pageToken: 1]
|
||||
|
||||
### Suggested Fix (first page)
|
||||
<suggestedFix>
|
||||
[MORE_CONTENT_AVAILABLE: suggestedFix, pageToken: 1]
|
||||
```
|
||||
|
||||
### Pagination Indicators
|
||||
|
||||
When a heavy field has more content available, append indicator:
|
||||
|
||||
```
|
||||
[MORE_CONTENT_AVAILABLE: <fieldName>, pageToken: <nextPage>]
|
||||
```
|
||||
|
||||
Main agent can fetch additional pages if needed using:
|
||||
|
||||
```
|
||||
ci_information({ select: "<fieldName>", pageToken: <nextPage> })
|
||||
```
|
||||
|
||||
Fields that may have pagination:
|
||||
|
||||
- `taskOutputSummary` (reverse pagination - page 0 = most recent)
|
||||
- `suggestedFix` (forward pagination - page 0 = start)
|
||||
- `suggestedFixReasoning`
|
||||
|
||||
### Return Format for `no_new_cipe`
|
||||
|
||||
When returning with `status: no_new_cipe`, include additional context:
|
||||
|
||||
```
|
||||
## CI Monitor Result
|
||||
|
||||
**Status:** no_new_cipe
|
||||
**Iterations:** <count>
|
||||
**Elapsed:** <minutes>m <seconds>s
|
||||
|
||||
### Expected CI Attempt Not Found
|
||||
- **Expected Commit SHA:** <expectedCommitSha>
|
||||
- **Previous CI Attempt URL:** <previousCipeUrl>
|
||||
- **Last Seen CI Attempt URL:** <cipeUrl>
|
||||
- **Last Seen Commit SHA:** <commitSha>
|
||||
- **New CI Attempt Timeout:** 30 minutes (exceeded)
|
||||
|
||||
### Likely Cause
|
||||
CI workflow failed before Nx tasks could run (e.g., install step, checkout, auth).
|
||||
Check your CI provider logs for the commit <expectedCommitSha>.
|
||||
|
||||
### Last Known CI Attempt State
|
||||
- **Status:** <cipeStatus>
|
||||
- **Branch:** <branch>
|
||||
```
|
||||
|
||||
## Status Reporting (Verbosity-Controlled)
|
||||
|
||||
Output is controlled by the `verbosity` parameter from the main agent:
|
||||
|
||||
| Level | What to Output |
|
||||
| --------- | ----------------------------------------------------------------- |
|
||||
| `minimal` | No intermediate output. Only return final result when actionable. |
|
||||
| `medium` | Output only on significant state changes (not every poll). |
|
||||
| `verbose` | Output detailed phase information after every poll. |
|
||||
|
||||
### Minimal Verbosity
|
||||
|
||||
No output during polling. Poll silently and return when done.
|
||||
|
||||
### Medium Verbosity (Default)
|
||||
|
||||
Output **only when state changes significantly** to save context tokens:
|
||||
|
||||
- `cipeStatus` changes (e.g., IN_PROGRESS → FAILED)
|
||||
- `selfHealingStatus` changes (e.g., IN_PROGRESS → COMPLETED)
|
||||
- New CI Attempt detected (in wait mode)
|
||||
|
||||
Format: single line, no decorators:
|
||||
|
||||
```
|
||||
[CI Monitor] CI: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 4m
|
||||
```
|
||||
|
||||
### Verbose Verbosity
|
||||
|
||||
Output detailed phase box after every poll:
|
||||
|
||||
```
|
||||
[CI Monitor] ─────────────────────────────────────────────────────
|
||||
[CI Monitor] Iteration <N> | Elapsed: <X>m <Y>s
|
||||
[CI Monitor]
|
||||
[CI Monitor] CI Status: <cipeStatus>
|
||||
[CI Monitor] Self-Healing: <selfHealingStatus>
|
||||
[CI Monitor] Verification: <verificationStatus>
|
||||
[CI Monitor] Classification: <failureClassification>
|
||||
[CI Monitor]
|
||||
[CI Monitor] → <human-readable phase description>
|
||||
[CI Monitor] ─────────────────────────────────────────────────────
|
||||
```
|
||||
|
||||
### Phase Descriptions (for verbose output)
|
||||
|
||||
| Status Combo | Description |
|
||||
| ----------------------------------------------------------------------------------------- | ------------------------------------------- |
|
||||
| `cipeStatus: IN_PROGRESS` | "CI running..." |
|
||||
| `cipeStatus: NOT_STARTED` | "Waiting for CI to start..." |
|
||||
| `cipeStatus: FAILED` + `selfHealingStatus: NOT_STARTED` | "CI failed. Self-healing starting..." |
|
||||
| `cipeStatus: FAILED` + `selfHealingStatus: IN_PROGRESS` | "CI failed. Self-healing generating fix..." |
|
||||
| `cipeStatus: FAILED` + `selfHealingStatus: COMPLETED` + `verificationStatus: IN_PROGRESS` | "Fix generated! Verification running..." |
|
||||
| `cipeStatus: FAILED` + `selfHealingStatus: COMPLETED` + `verificationStatus: COMPLETED` | "Fix ready! Verified successfully." |
|
||||
| `cipeStatus: FAILED` + `selfHealingStatus: COMPLETED` + `verificationStatus: FAILED` | "Fix generated but verification failed." |
|
||||
| `cipeStatus: FAILED` + `selfHealingStatus: FAILED` | "Self-healing could not generate a fix." |
|
||||
| `cipeStatus: SUCCEEDED` | "CI passed!" |
|
||||
|
||||
## Important Notes
|
||||
|
||||
- You do NOT make apply/reject decisions - that's the main agent's job
|
||||
- You do NOT perform git operations
|
||||
- You only poll and report state
|
||||
- Respect the `verbosity` parameter for output (default: medium)
|
||||
- If `ci_information` returns an error, wait and retry (count as failed poll)
|
||||
- Track consecutive failures - if 5 consecutive failures, return with `status: error`
|
||||
- When expecting new CI Attempt, track the 30-minute new-CI-Attempt timeout separately from the main polling timeout
|
||||
@@ -0,0 +1,428 @@
|
||||
# CI Monitor Command
|
||||
|
||||
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
|
||||
|
||||
## Context
|
||||
|
||||
- **Current Branch:** !`git branch --show-current`
|
||||
- **Current Commit:** !`git rev-parse --short HEAD`
|
||||
- **Remote Status:** !`git status -sb | head -1`
|
||||
|
||||
## User Instructions
|
||||
|
||||
**Important:** If user provides specific instructions, respect them over default behaviors described below.
|
||||
|
||||
## Configuration Defaults
|
||||
|
||||
| Setting | Default | Description |
|
||||
| ------------------------- | ------------- | ------------------------------------------------------------------- |
|
||||
| `--max-cycles` | 10 | Maximum CIPE cycles before timeout |
|
||||
| `--timeout` | 120 | Maximum duration in minutes |
|
||||
| `--verbosity` | medium | Output level: minimal, medium, verbose |
|
||||
| `--branch` | (auto-detect) | Branch to monitor |
|
||||
| `--subagent-timeout` | 60 | Subagent polling timeout in minutes |
|
||||
| `--fresh` | false | Ignore previous context, start fresh |
|
||||
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
|
||||
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
|
||||
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
|
||||
|
||||
## Nx Cloud Connection Check
|
||||
|
||||
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
|
||||
|
||||
### Step 0: Verify Nx Cloud Connection
|
||||
|
||||
1. **Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
|
||||
2. **If `nx.json` missing OR neither property exists** → exit with:
|
||||
```
|
||||
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
|
||||
```
|
||||
3. **If connected** → continue to main loop
|
||||
|
||||
## Session Context Behavior
|
||||
|
||||
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
|
||||
|
||||
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
|
||||
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
|
||||
- **For a completely clean slate:** Exit Claude Code and restart `claude`
|
||||
|
||||
## Default Behaviors by Status
|
||||
|
||||
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
|
||||
|
||||
| Status | Default Behavior |
|
||||
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ci_success` | Exit with success. Log "CI passed successfully!" |
|
||||
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
|
||||
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
|
||||
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
|
||||
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
|
||||
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
|
||||
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
|
||||
| `polling_timeout` | Subagent polling timeout reached. Exit with timeout. |
|
||||
| `cipe_canceled` | CIPE was canceled. Exit with canceled status. |
|
||||
| `cipe_timed_out` | CIPE timed out. Exit with timeout status. |
|
||||
| `error` | Increment `no_progress_count`. If >= 3 → exit with circuit breaker. Otherwise wait 60s and loop. |
|
||||
|
||||
### Fix Available Decision Logic
|
||||
|
||||
When subagent returns `fix_available`, main agent compares `failedTaskIds` vs `verifiedTaskIds`:
|
||||
|
||||
#### Step 1: Categorize Tasks
|
||||
|
||||
1. **Verified tasks** = tasks in both `failedTaskIds` AND `verifiedTaskIds`
|
||||
2. **Unverified tasks** = tasks in `failedTaskIds` but NOT in `verifiedTaskIds`
|
||||
3. **E2E tasks** = unverified tasks where target contains "e2e" (task format: `<project>:<target>` or `<project>:<target>:<config>`)
|
||||
4. **Verifiable tasks** = unverified tasks that are NOT e2e
|
||||
|
||||
#### Step 2: Determine Path
|
||||
|
||||
| Condition | Path |
|
||||
| --------------------------------------- | ---------------------------------------- |
|
||||
| No unverified tasks (all verified) | Apply via MCP |
|
||||
| Unverified tasks exist, but ALL are e2e | Apply via MCP (treat as verified enough) |
|
||||
| Verifiable tasks exist | Local verification flow |
|
||||
|
||||
#### Step 3a: Apply via MCP (fully/e2e-only verified)
|
||||
|
||||
- Call `update_self_healing_fix({ shortLink, action: "APPLY" })`
|
||||
- Record `last_cipe_url`, spawn subagent in wait mode
|
||||
|
||||
#### Step 3b: Local Verification Flow
|
||||
|
||||
When verifiable (non-e2e) unverified tasks exist:
|
||||
|
||||
1. **Detect package manager:**
|
||||
- `pnpm-lock.yaml` exists → `pnpm nx`
|
||||
- `yarn.lock` exists → `yarn nx`
|
||||
- Otherwise → `npx nx`
|
||||
|
||||
2. **Run verifiable tasks in parallel:**
|
||||
- Spawn `general` subagents to run each task concurrently
|
||||
- Each subagent runs: `<pm> nx run <taskId>`
|
||||
- Collect pass/fail results from all subagents
|
||||
|
||||
3. **Evaluate results:**
|
||||
|
||||
| Result | Action |
|
||||
| ------------------------- | ---------------------------- |
|
||||
| ALL verifiable tasks pass | Apply via MCP |
|
||||
| ANY verifiable task fails | Apply-locally + enhance flow |
|
||||
|
||||
4. **Apply-locally + enhance flow:**
|
||||
- Run `nx apply-locally <shortLink>`
|
||||
- Enhance the code to fix failing tasks
|
||||
- Run failing tasks again to verify fix
|
||||
- If still failing → increment `local_verify_count`, loop back to enhance
|
||||
- If passing → commit and push, record `expected_commit_sha`, spawn subagent in wait mode
|
||||
|
||||
5. **Track attempts** (wraps step 4):
|
||||
- Increment `local_verify_count` after each enhance cycle
|
||||
- If `local_verify_count >= local_verify_attempts` (default: 3):
|
||||
- Get code in commit-able state
|
||||
- Commit and push with message indicating local verification failed
|
||||
- Report to user:
|
||||
```
|
||||
[ci-monitor] Local verification failed after <N> attempts. Pushed to CI for final validation. Failed: <taskIds>
|
||||
```
|
||||
- Record `expected_commit_sha`, spawn subagent in wait mode (let CI be final judge)
|
||||
|
||||
#### Commit Message Format
|
||||
|
||||
```bash
|
||||
git commit -m "fix(<projects>): <brief description>
|
||||
|
||||
Failed tasks: <taskId1>, <taskId2>
|
||||
Local verification: passed|enhanced|failed-pushing-to-ci"
|
||||
```
|
||||
|
||||
### Unverified Fix Flow (No Verification Attempted)
|
||||
|
||||
When `verificationStatus` is `FAILED`, `NOT_EXECUTABLE`, or fix has `couldAutoApplyTasks != true` with no verification:
|
||||
|
||||
- Analyze fix content (`suggestedFix`, `suggestedFixReasoning`, `taskOutputSummary`)
|
||||
- If fix looks correct → apply via MCP
|
||||
- If fix needs enhancement → use Apply Locally + Enhance Flow above
|
||||
- If fix is wrong → reject via MCP, fix from scratch, commit, push
|
||||
|
||||
### Auto-Apply Eligibility
|
||||
|
||||
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
|
||||
|
||||
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
|
||||
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
|
||||
|
||||
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
|
||||
|
||||
### Apply vs Reject vs Apply Locally
|
||||
|
||||
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
|
||||
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
|
||||
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
|
||||
|
||||
### Apply Locally + Enhance Flow
|
||||
|
||||
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
|
||||
|
||||
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
|
||||
2. Make additional changes as needed
|
||||
3. Commit and push:
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix: resolve <failedTaskIds>"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
4. Loop to poll for new CIPE
|
||||
|
||||
### Reject + Fix From Scratch Flow
|
||||
|
||||
When the fix is completely wrong:
|
||||
|
||||
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
|
||||
2. Fix the issue from scratch locally
|
||||
3. Commit and push:
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix: resolve <failedTaskIds>"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
4. Loop to poll for new CIPE
|
||||
|
||||
### Environment Issue Handling
|
||||
|
||||
When `failureClassification == 'ENVIRONMENT_STATE'`:
|
||||
|
||||
1. Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`
|
||||
2. New CIPE spawns automatically (no local git operations needed)
|
||||
3. Loop to poll for new CIPE with `previousCipeUrl` set
|
||||
|
||||
### No-New-CIPE Handling
|
||||
|
||||
When `status == 'no_new_cipe'`:
|
||||
|
||||
This means the expected CIPE was never created - CI likely failed before Nx tasks could run.
|
||||
|
||||
1. **Report to user:**
|
||||
|
||||
```
|
||||
[ci-monitor] No CI attempt for <sha> after 10 min. Check CI provider for pre-Nx failures (install, checkout, auth). Last CI attempt: <previousCipeUrl>
|
||||
```
|
||||
|
||||
2. **If user configured auto-fix attempts** (e.g., `--auto-fix-workflow`):
|
||||
- Detect package manager: check for `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`
|
||||
- Run install to update lockfile:
|
||||
```bash
|
||||
pnpm install # or npm install / yarn install
|
||||
```
|
||||
- If lockfile changed:
|
||||
```bash
|
||||
git add pnpm-lock.yaml # or appropriate lockfile
|
||||
git commit -m "chore: update lockfile"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
- Record new commit SHA, loop to poll with `expectedCommitSha`
|
||||
|
||||
3. **Otherwise:** Exit with `no_new_cipe` status, providing guidance for user to investigate
|
||||
|
||||
## Exit Conditions
|
||||
|
||||
Exit the monitoring loop when ANY of these conditions are met:
|
||||
|
||||
| Condition | Exit Type |
|
||||
| ------------------------------------------- | ---------------- |
|
||||
| CI passes (`cipeStatus == 'SUCCEEDED'`) | Success |
|
||||
| Max CIPE cycles reached | Timeout |
|
||||
| Max duration reached | Timeout |
|
||||
| 3 consecutive no-progress iterations | Circuit breaker |
|
||||
| No fix available and local fix not possible | Failure |
|
||||
| No new CIPE and auto-fix not configured | Pre-CIPE failure |
|
||||
| User cancels | Cancelled |
|
||||
|
||||
## Main Loop
|
||||
|
||||
### Step 1: Initialize Tracking
|
||||
|
||||
```
|
||||
cycle_count = 0
|
||||
start_time = now()
|
||||
no_progress_count = 0
|
||||
local_verify_count = 0
|
||||
last_state = null
|
||||
last_cipe_url = null
|
||||
expected_commit_sha = null
|
||||
```
|
||||
|
||||
### Step 2: Spawn Subagent
|
||||
|
||||
Spawn the `ci-watcher` subagent to poll CI status:
|
||||
|
||||
**Fresh start (first spawn, no expected CIPE):**
|
||||
|
||||
```
|
||||
Task(
|
||||
agent: "ci-watcher",
|
||||
prompt: "Monitor CI for branch '<branch>'.
|
||||
Subagent timeout: <subagent-timeout> minutes.
|
||||
New-CIPE timeout: <new-cipe-timeout> minutes.
|
||||
Verbosity: <verbosity>."
|
||||
)
|
||||
```
|
||||
|
||||
**After action that triggers new CIPE (wait mode):**
|
||||
|
||||
```
|
||||
Task(
|
||||
agent: "ci-watcher",
|
||||
prompt: "Monitor CI for branch '<branch>'.
|
||||
Subagent timeout: <subagent-timeout> minutes.
|
||||
New-CIPE timeout: <new-cipe-timeout> minutes.
|
||||
Verbosity: <verbosity>.
|
||||
|
||||
WAIT MODE: A new CIPE should spawn. Ignore old CIPE until new one appears.
|
||||
Expected commit SHA: <expected_commit_sha>
|
||||
Previous CIPE URL: <last_cipe_url>"
|
||||
)
|
||||
```
|
||||
|
||||
### Step 3: Handle Subagent Response
|
||||
|
||||
When subagent returns:
|
||||
|
||||
1. Check the returned status
|
||||
2. Look up default behavior in the table above
|
||||
3. Check if user instructions override the default
|
||||
4. Execute the appropriate action
|
||||
5. **If action expects new CIPE**, update tracking (see Step 3a)
|
||||
6. If action results in looping, go to Step 2
|
||||
|
||||
### Step 3a: Track State for New-CIPE Detection
|
||||
|
||||
After actions that should trigger a new CIPE, record state before looping:
|
||||
|
||||
| Action | What to Track | Subagent Mode |
|
||||
| ----------------------------- | --------------------------------------------- | ------------- |
|
||||
| Fix auto-applying | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| Apply via MCP | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| Apply locally + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Reject + fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Fix failed + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| No fix + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Environment rerun | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| No-new-CIPE + auto-fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
|
||||
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
|
||||
|
||||
- Subagent will **completely ignore** the old/stale CIPE
|
||||
- Subagent will only wait for new CIPE to appear
|
||||
- Subagent will NOT return to main agent with stale CIPE data
|
||||
- Once new CIPE detected, subagent switches to normal polling
|
||||
|
||||
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
|
||||
|
||||
### Step 4: Progress Tracking
|
||||
|
||||
After each action:
|
||||
|
||||
- If state changed significantly → reset `no_progress_count = 0`
|
||||
- If state unchanged → `no_progress_count++`
|
||||
- On new CI attempt detected → reset `local_verify_count = 0`
|
||||
|
||||
## Status Reporting
|
||||
|
||||
Based on verbosity level:
|
||||
|
||||
| Level | What to Report |
|
||||
| --------- | -------------------------------------------------------------------------- |
|
||||
| `minimal` | Only final result (success/failure/timeout) |
|
||||
| `medium` | State changes + periodic updates ("Cycle N \| Elapsed: Xm \| Status: ...") |
|
||||
| `verbose` | All of medium + full subagent responses, git outputs, MCP responses |
|
||||
|
||||
## User Instruction Examples
|
||||
|
||||
Users can override default behaviors:
|
||||
|
||||
| Instruction | Effect |
|
||||
| ------------------------------------------------ | --------------------------------------------- |
|
||||
| "never auto-apply" | Always prompt before applying any fix |
|
||||
| "always ask before git push" | Prompt before each push |
|
||||
| "reject any fix for e2e tasks" | Auto-reject if `failedTaskIds` contains e2e |
|
||||
| "apply all fixes regardless of verification" | Skip verification check, apply everything |
|
||||
| "if confidence < 70, reject" | Check confidence field before applying |
|
||||
| "run 'nx affected -t typecheck' before applying" | Add local verification step |
|
||||
| "auto-fix workflow failures" | Attempt lockfile updates on pre-CIPE failures |
|
||||
| "wait 45 min for new CIPE" | Override new-CIPE timeout (default: 10 min) |
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | Action |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------- |
|
||||
| Git rebase conflict | Report to user, exit |
|
||||
| `nx apply-locally` fails | Report to user, attempt manual patch or exit |
|
||||
| MCP tool error | Retry once, if fails report to user |
|
||||
| Subagent spawn failure | Retry once, if fails exit with error |
|
||||
| No new CIPE detected | If `--auto-fix-workflow`, try lockfile update; otherwise report to user with guidance |
|
||||
| Lockfile auto-fix fails | Report to user, exit with guidance to check CI logs |
|
||||
|
||||
## Example Session
|
||||
|
||||
### Example 1: Normal Flow with Self-Healing (medium verbosity)
|
||||
|
||||
```
|
||||
[ci-monitor] Starting CI monitor for branch 'feature/add-auth'
|
||||
[ci-monitor] Config: max-cycles=5, timeout=120m, verbosity=medium
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] CI attempt: IN_PROGRESS | Self-Healing: NOT_STARTED | Elapsed: 1m
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 3m
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 5m
|
||||
|
||||
[ci-monitor] Fix available! Verification: COMPLETED
|
||||
[ci-monitor] Applying fix via MCP...
|
||||
[ci-monitor] Fix applied in CI. Waiting for new CI attempt...
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] New CI attempt detected!
|
||||
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 8m
|
||||
|
||||
[ci-monitor] CI passed successfully!
|
||||
|
||||
[ci-monitor] Summary:
|
||||
- Total cycles: 2
|
||||
- Total time: 12m 34s
|
||||
- Fixes applied: 1
|
||||
- Result: SUCCESS
|
||||
```
|
||||
|
||||
### Example 2: Pre-CI Failure (medium verbosity)
|
||||
|
||||
```
|
||||
[ci-monitor] Starting CI monitor for branch 'feature/add-products'
|
||||
[ci-monitor] Config: max-cycles=5, timeout=120m, auto-fix-workflow=true
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 2m
|
||||
|
||||
[ci-monitor] Applying fix locally, enhancing, and pushing...
|
||||
[ci-monitor] Committed: abc1234
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] Waiting for new CI attempt... (expected SHA: abc1234)
|
||||
[CI Monitor] ⚠️ CI attempt timeout (10 min). Returning no_new_cipe.
|
||||
|
||||
[ci-monitor] Status: no_new_cipe
|
||||
[ci-monitor] --auto-fix-workflow enabled. Attempting lockfile update...
|
||||
[ci-monitor] Lockfile updated. Committed: def5678
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] New CI attempt detected!
|
||||
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 18m
|
||||
|
||||
[ci-monitor] CI passed successfully!
|
||||
|
||||
[ci-monitor] Summary:
|
||||
- Total cycles: 3
|
||||
- Total time: 22m 15s
|
||||
- Fixes applied: 1 (self-healing) + 1 (lockfile)
|
||||
- Result: SUCCESS
|
||||
```
|
||||
@@ -0,0 +1,437 @@
|
||||
---
|
||||
name: ci-monitor
|
||||
description: Monitor Nx Cloud CI pipeline and handle self-healing fixes automatically. Checks for Nx Cloud connection before starting.
|
||||
---
|
||||
|
||||
# CI Monitor Command
|
||||
|
||||
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
|
||||
|
||||
## Context
|
||||
|
||||
- **Current Branch:** !`git branch --show-current`
|
||||
- **Current Commit:** !`git rev-parse --short HEAD`
|
||||
- **Remote Status:** !`git status -sb | head -1`
|
||||
|
||||
## User Instructions
|
||||
|
||||
$ARGUMENTS
|
||||
|
||||
**Important:** If user provides specific instructions, respect them over default behaviors described below.
|
||||
|
||||
## Configuration Defaults
|
||||
|
||||
| Setting | Default | Description |
|
||||
| ------------------------- | ------------- | ------------------------------------------------------------------- |
|
||||
| `--max-cycles` | 10 | Maximum CIPE cycles before timeout |
|
||||
| `--timeout` | 120 | Maximum duration in minutes |
|
||||
| `--verbosity` | medium | Output level: minimal, medium, verbose |
|
||||
| `--branch` | (auto-detect) | Branch to monitor |
|
||||
| `--subagent-timeout` | 60 | Subagent polling timeout in minutes |
|
||||
| `--fresh` | false | Ignore previous context, start fresh |
|
||||
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
|
||||
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
|
||||
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
|
||||
|
||||
Parse any overrides from `$ARGUMENTS` and merge with defaults.
|
||||
|
||||
## Nx Cloud Connection Check
|
||||
|
||||
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
|
||||
|
||||
### Step 0: Verify Nx Cloud Connection
|
||||
|
||||
1. **Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
|
||||
2. **If `nx.json` missing OR neither property exists** → exit with:
|
||||
```
|
||||
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
|
||||
```
|
||||
3. **If connected** → continue to main loop
|
||||
|
||||
## Session Context Behavior
|
||||
|
||||
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
|
||||
|
||||
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
|
||||
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
|
||||
- **For a completely clean slate:** Exit Claude Code and restart `claude`
|
||||
|
||||
## Default Behaviors by Status
|
||||
|
||||
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
|
||||
|
||||
| Status | Default Behavior |
|
||||
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ci_success` | Exit with success. Log "CI passed successfully!" |
|
||||
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
|
||||
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
|
||||
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
|
||||
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
|
||||
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
|
||||
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
|
||||
| `polling_timeout` | Subagent polling timeout reached. Exit with timeout. |
|
||||
| `cipe_canceled` | CIPE was canceled. Exit with canceled status. |
|
||||
| `cipe_timed_out` | CIPE timed out. Exit with timeout status. |
|
||||
| `error` | Increment `no_progress_count`. If >= 3 → exit with circuit breaker. Otherwise wait 60s and loop. |
|
||||
|
||||
### Fix Available Decision Logic
|
||||
|
||||
When subagent returns `fix_available`, main agent compares `failedTaskIds` vs `verifiedTaskIds`:
|
||||
|
||||
#### Step 1: Categorize Tasks
|
||||
|
||||
1. **Verified tasks** = tasks in both `failedTaskIds` AND `verifiedTaskIds`
|
||||
2. **Unverified tasks** = tasks in `failedTaskIds` but NOT in `verifiedTaskIds`
|
||||
3. **E2E tasks** = unverified tasks where target contains "e2e" (task format: `<project>:<target>` or `<project>:<target>:<config>`)
|
||||
4. **Verifiable tasks** = unverified tasks that are NOT e2e
|
||||
|
||||
#### Step 2: Determine Path
|
||||
|
||||
| Condition | Path |
|
||||
| --------------------------------------- | ---------------------------------------- |
|
||||
| No unverified tasks (all verified) | Apply via MCP |
|
||||
| Unverified tasks exist, but ALL are e2e | Apply via MCP (treat as verified enough) |
|
||||
| Verifiable tasks exist | Local verification flow |
|
||||
|
||||
#### Step 3a: Apply via MCP (fully/e2e-only verified)
|
||||
|
||||
- Call `update_self_healing_fix({ shortLink, action: "APPLY" })`
|
||||
- Record `last_cipe_url`, spawn subagent in wait mode
|
||||
|
||||
#### Step 3b: Local Verification Flow
|
||||
|
||||
When verifiable (non-e2e) unverified tasks exist:
|
||||
|
||||
1. **Detect package manager:**
|
||||
- `pnpm-lock.yaml` exists → `pnpm nx`
|
||||
- `yarn.lock` exists → `yarn nx`
|
||||
- Otherwise → `npx nx`
|
||||
|
||||
2. **Run verifiable tasks in parallel:**
|
||||
- Spawn `general` subagents to run each task concurrently
|
||||
- Each subagent runs: `<pm> nx run <taskId>`
|
||||
- Collect pass/fail results from all subagents
|
||||
|
||||
3. **Evaluate results:**
|
||||
|
||||
| Result | Action |
|
||||
| ------------------------- | ---------------------------- |
|
||||
| ALL verifiable tasks pass | Apply via MCP |
|
||||
| ANY verifiable task fails | Apply-locally + enhance flow |
|
||||
|
||||
4. **Apply-locally + enhance flow:**
|
||||
- Run `nx apply-locally <shortLink>`
|
||||
- Enhance the code to fix failing tasks
|
||||
- Run failing tasks again to verify fix
|
||||
- If still failing → increment `local_verify_count`, loop back to enhance
|
||||
- If passing → commit and push, record `expected_commit_sha`, spawn subagent in wait mode
|
||||
|
||||
5. **Track attempts** (wraps step 4):
|
||||
- Increment `local_verify_count` after each enhance cycle
|
||||
- If `local_verify_count >= local_verify_attempts` (default: 3):
|
||||
- Get code in commit-able state
|
||||
- Commit and push with message indicating local verification failed
|
||||
- Report to user:
|
||||
```
|
||||
[ci-monitor] Local verification failed after <N> attempts. Pushed to CI for final validation. Failed: <taskIds>
|
||||
```
|
||||
- Record `expected_commit_sha`, spawn subagent in wait mode (let CI be final judge)
|
||||
|
||||
#### Commit Message Format
|
||||
|
||||
```bash
|
||||
git commit -m "fix(<projects>): <brief description>
|
||||
|
||||
Failed tasks: <taskId1>, <taskId2>
|
||||
Local verification: passed|enhanced|failed-pushing-to-ci"
|
||||
```
|
||||
|
||||
### Unverified Fix Flow (No Verification Attempted)
|
||||
|
||||
When `verificationStatus` is `FAILED`, `NOT_EXECUTABLE`, or fix has `couldAutoApplyTasks != true` with no verification:
|
||||
|
||||
- Analyze fix content (`suggestedFix`, `suggestedFixReasoning`, `taskOutputSummary`)
|
||||
- If fix looks correct → apply via MCP
|
||||
- If fix needs enhancement → use Apply Locally + Enhance Flow above
|
||||
- If fix is wrong → reject via MCP, fix from scratch, commit, push
|
||||
|
||||
### Auto-Apply Eligibility
|
||||
|
||||
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
|
||||
|
||||
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
|
||||
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
|
||||
|
||||
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
|
||||
|
||||
### Apply vs Reject vs Apply Locally
|
||||
|
||||
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
|
||||
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
|
||||
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
|
||||
|
||||
### Apply Locally + Enhance Flow
|
||||
|
||||
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
|
||||
|
||||
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
|
||||
2. Make additional changes as needed
|
||||
3. Commit and push:
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix: resolve <failedTaskIds>"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
4. Loop to poll for new CIPE
|
||||
|
||||
### Reject + Fix From Scratch Flow
|
||||
|
||||
When the fix is completely wrong:
|
||||
|
||||
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
|
||||
2. Fix the issue from scratch locally
|
||||
3. Commit and push:
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix: resolve <failedTaskIds>"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
4. Loop to poll for new CIPE
|
||||
|
||||
### Environment Issue Handling
|
||||
|
||||
When `failureClassification == 'ENVIRONMENT_STATE'`:
|
||||
|
||||
1. Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`
|
||||
2. New CIPE spawns automatically (no local git operations needed)
|
||||
3. Loop to poll for new CIPE with `previousCipeUrl` set
|
||||
|
||||
### No-New-CIPE Handling
|
||||
|
||||
When `status == 'no_new_cipe'`:
|
||||
|
||||
This means the expected CIPE was never created - CI likely failed before Nx tasks could run.
|
||||
|
||||
1. **Report to user:**
|
||||
|
||||
```
|
||||
[ci-monitor] No CI attempt for <sha> after 10 min. Check CI provider for pre-Nx failures (install, checkout, auth). Last CI attempt: <previousCipeUrl>
|
||||
```
|
||||
|
||||
2. **If user configured auto-fix attempts** (e.g., `--auto-fix-workflow`):
|
||||
- Detect package manager: check for `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`
|
||||
- Run install to update lockfile:
|
||||
```bash
|
||||
pnpm install # or npm install / yarn install
|
||||
```
|
||||
- If lockfile changed:
|
||||
```bash
|
||||
git add pnpm-lock.yaml # or appropriate lockfile
|
||||
git commit -m "chore: update lockfile"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
- Record new commit SHA, loop to poll with `expectedCommitSha`
|
||||
|
||||
3. **Otherwise:** Exit with `no_new_cipe` status, providing guidance for user to investigate
|
||||
|
||||
## Exit Conditions
|
||||
|
||||
Exit the monitoring loop when ANY of these conditions are met:
|
||||
|
||||
| Condition | Exit Type |
|
||||
| ------------------------------------------- | ---------------- |
|
||||
| CI passes (`cipeStatus == 'SUCCEEDED'`) | Success |
|
||||
| Max CIPE cycles reached | Timeout |
|
||||
| Max duration reached | Timeout |
|
||||
| 3 consecutive no-progress iterations | Circuit breaker |
|
||||
| No fix available and local fix not possible | Failure |
|
||||
| No new CIPE and auto-fix not configured | Pre-CIPE failure |
|
||||
| User cancels | Cancelled |
|
||||
|
||||
## Main Loop
|
||||
|
||||
### Step 1: Initialize Tracking
|
||||
|
||||
```
|
||||
cycle_count = 0
|
||||
start_time = now()
|
||||
no_progress_count = 0
|
||||
local_verify_count = 0
|
||||
last_state = null
|
||||
last_cipe_url = null
|
||||
expected_commit_sha = null
|
||||
```
|
||||
|
||||
### Step 2: Spawn Subagent
|
||||
|
||||
Spawn the `ci-watcher` subagent to poll CI status:
|
||||
|
||||
**Fresh start (first spawn, no expected CIPE):**
|
||||
|
||||
```
|
||||
Task(
|
||||
agent: "ci-watcher",
|
||||
prompt: "Monitor CI for branch '<branch>'.
|
||||
Subagent timeout: <subagent-timeout> minutes.
|
||||
New-CIPE timeout: <new-cipe-timeout> minutes.
|
||||
Verbosity: <verbosity>."
|
||||
)
|
||||
```
|
||||
|
||||
**After action that triggers new CIPE (wait mode):**
|
||||
|
||||
```
|
||||
Task(
|
||||
agent: "ci-watcher",
|
||||
prompt: "Monitor CI for branch '<branch>'.
|
||||
Subagent timeout: <subagent-timeout> minutes.
|
||||
New-CIPE timeout: <new-cipe-timeout> minutes.
|
||||
Verbosity: <verbosity>.
|
||||
|
||||
WAIT MODE: A new CIPE should spawn. Ignore old CIPE until new one appears.
|
||||
Expected commit SHA: <expected_commit_sha>
|
||||
Previous CIPE URL: <last_cipe_url>"
|
||||
)
|
||||
```
|
||||
|
||||
### Step 3: Handle Subagent Response
|
||||
|
||||
When subagent returns:
|
||||
|
||||
1. Check the returned status
|
||||
2. Look up default behavior in the table above
|
||||
3. Check if user instructions override the default
|
||||
4. Execute the appropriate action
|
||||
5. **If action expects new CIPE**, update tracking (see Step 3a)
|
||||
6. If action results in looping, go to Step 2
|
||||
|
||||
### Step 3a: Track State for New-CIPE Detection
|
||||
|
||||
After actions that should trigger a new CIPE, record state before looping:
|
||||
|
||||
| Action | What to Track | Subagent Mode |
|
||||
| ----------------------------- | --------------------------------------------- | ------------- |
|
||||
| Fix auto-applying | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| Apply via MCP | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| Apply locally + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Reject + fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Fix failed + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| No fix + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Environment rerun | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| No-new-CIPE + auto-fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
|
||||
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
|
||||
|
||||
- Subagent will **completely ignore** the old/stale CIPE
|
||||
- Subagent will only wait for new CIPE to appear
|
||||
- Subagent will NOT return to main agent with stale CIPE data
|
||||
- Once new CIPE detected, subagent switches to normal polling
|
||||
|
||||
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
|
||||
|
||||
### Step 4: Progress Tracking
|
||||
|
||||
After each action:
|
||||
|
||||
- If state changed significantly → reset `no_progress_count = 0`
|
||||
- If state unchanged → `no_progress_count++`
|
||||
- On new CI attempt detected → reset `local_verify_count = 0`
|
||||
|
||||
## Status Reporting
|
||||
|
||||
Based on verbosity level:
|
||||
|
||||
| Level | What to Report |
|
||||
| --------- | -------------------------------------------------------------------------- |
|
||||
| `minimal` | Only final result (success/failure/timeout) |
|
||||
| `medium` | State changes + periodic updates ("Cycle N \| Elapsed: Xm \| Status: ...") |
|
||||
| `verbose` | All of medium + full subagent responses, git outputs, MCP responses |
|
||||
|
||||
## User Instruction Examples
|
||||
|
||||
Users can override default behaviors:
|
||||
|
||||
| Instruction | Effect |
|
||||
| ------------------------------------------------ | --------------------------------------------- |
|
||||
| "never auto-apply" | Always prompt before applying any fix |
|
||||
| "always ask before git push" | Prompt before each push |
|
||||
| "reject any fix for e2e tasks" | Auto-reject if `failedTaskIds` contains e2e |
|
||||
| "apply all fixes regardless of verification" | Skip verification check, apply everything |
|
||||
| "if confidence < 70, reject" | Check confidence field before applying |
|
||||
| "run 'nx affected -t typecheck' before applying" | Add local verification step |
|
||||
| "auto-fix workflow failures" | Attempt lockfile updates on pre-CIPE failures |
|
||||
| "wait 45 min for new CIPE" | Override new-CIPE timeout (default: 10 min) |
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | Action |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------- |
|
||||
| Git rebase conflict | Report to user, exit |
|
||||
| `nx apply-locally` fails | Report to user, attempt manual patch or exit |
|
||||
| MCP tool error | Retry once, if fails report to user |
|
||||
| Subagent spawn failure | Retry once, if fails exit with error |
|
||||
| No new CIPE detected | If `--auto-fix-workflow`, try lockfile update; otherwise report to user with guidance |
|
||||
| Lockfile auto-fix fails | Report to user, exit with guidance to check CI logs |
|
||||
|
||||
## Example Session
|
||||
|
||||
### Example 1: Normal Flow with Self-Healing (medium verbosity)
|
||||
|
||||
```
|
||||
[ci-monitor] Starting CI monitor for branch 'feature/add-auth'
|
||||
[ci-monitor] Config: max-cycles=5, timeout=120m, verbosity=medium
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] CI attempt: IN_PROGRESS | Self-Healing: NOT_STARTED | Elapsed: 1m
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 3m
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 5m
|
||||
|
||||
[ci-monitor] Fix available! Verification: COMPLETED
|
||||
[ci-monitor] Applying fix via MCP...
|
||||
[ci-monitor] Fix applied in CI. Waiting for new CI attempt...
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] New CI attempt detected!
|
||||
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 8m
|
||||
|
||||
[ci-monitor] CI passed successfully!
|
||||
|
||||
[ci-monitor] Summary:
|
||||
- Total cycles: 2
|
||||
- Total time: 12m 34s
|
||||
- Fixes applied: 1
|
||||
- Result: SUCCESS
|
||||
```
|
||||
|
||||
### Example 2: Pre-CI Failure (medium verbosity)
|
||||
|
||||
```
|
||||
[ci-monitor] Starting CI monitor for branch 'feature/add-products'
|
||||
[ci-monitor] Config: max-cycles=5, timeout=120m, auto-fix-workflow=true
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 2m
|
||||
|
||||
[ci-monitor] Applying fix locally, enhancing, and pushing...
|
||||
[ci-monitor] Committed: abc1234
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] Waiting for new CI attempt... (expected SHA: abc1234)
|
||||
[CI Monitor] ⚠️ CI attempt timeout (10 min). Returning no_new_cipe.
|
||||
|
||||
[ci-monitor] Status: no_new_cipe
|
||||
[ci-monitor] --auto-fix-workflow enabled. Attempting lockfile update...
|
||||
[ci-monitor] Lockfile updated. Committed: def5678
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] New CI attempt detected!
|
||||
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 18m
|
||||
|
||||
[ci-monitor] CI passed successfully!
|
||||
|
||||
[ci-monitor] Summary:
|
||||
- Total cycles: 3
|
||||
- Total time: 22m 15s
|
||||
- Fixes applied: 1 (self-healing) + 1 (lockfile)
|
||||
- Result: SUCCESS
|
||||
```
|
||||
@@ -0,0 +1,228 @@
|
||||
---
|
||||
name: nx-generate
|
||||
description: Generate code using nx generators. USE WHEN scaffolding code or transforming existing code - for example creating libraries or applications, or anything else that is boilerplate code or automates repetitive tasks. ALWAYS use this first when generating code with Nx instead of calling MCP tools or running nx generate immediately.
|
||||
---
|
||||
|
||||
# Run Nx Generator
|
||||
|
||||
Nx generators are powerful tools that scaffold projects, make automated code migrations or automate repetitive tasks in a monorepo. They ensure consistency across the codebase and reduce boilerplate work.
|
||||
|
||||
This skill applies when the user wants to:
|
||||
|
||||
- Create new projects like libraries or applications
|
||||
- Scaffold features or boilerplate code
|
||||
- Run workspace-specific or custom generators
|
||||
- Do anything else that an nx generator exists for
|
||||
|
||||
## Generator Discovery Flow
|
||||
|
||||
### Step 1: List Available Generators
|
||||
|
||||
Use the Nx CLI to discover available generators:
|
||||
|
||||
- List all generators for a plugin: `npx nx list @nx/react`
|
||||
- View available plugins: `npx nx list`
|
||||
|
||||
This includes:
|
||||
|
||||
- Plugin generators (e.g., `@nx/react:library`, `@nx/js:library`)
|
||||
- Local workspace generators (defined in the repo's own plugins)
|
||||
|
||||
### Step 2: Match Generator to User Request
|
||||
|
||||
Based on the user's request, identify which generator(s) could fulfill their needs. Consider:
|
||||
|
||||
- What artifact type they want to create (library, application, etc.)
|
||||
- Which framework or technology stack is relevant
|
||||
- Whether they mentioned specific generator names
|
||||
|
||||
**IMPORTANT**: When both a local workspace generator and an external plugin generator could satisfy the request, **always prefer the local workspace generator**. Local generators are customized for the specific repo's patterns and conventions.
|
||||
|
||||
It's possible that the user request is something that no Nx generator exists for whatsoever. In this case, you can stop using this skill and try to help the user another way. HOWEVER, the burden of proof for this is high. Before aborting, carefully consider each and every generator that's available. Look into details for any that could be related in any way before making this decision.
|
||||
|
||||
## Pre-Execution Checklist
|
||||
|
||||
Before running any generator, complete these steps:
|
||||
|
||||
### 1. Fetch Generator Schema
|
||||
|
||||
Use the `--help` flag to understand all available options:
|
||||
|
||||
```bash
|
||||
npx nx g @nx/react:library --help
|
||||
```
|
||||
|
||||
Pay attention to:
|
||||
|
||||
- Required options that must be provided
|
||||
- Optional options that may be relevant to the user's request
|
||||
- Default values that might need to be overridden
|
||||
|
||||
### 2. Read Generator Source Code
|
||||
|
||||
Understanding what the generator actually does helps you:
|
||||
|
||||
- Know what files will be created/modified
|
||||
- Understand any side effects (updating configs, installing deps, etc.)
|
||||
- Identify options that might not be obvious from the schema
|
||||
|
||||
To find generator source code:
|
||||
|
||||
- For plugin generators: Use `node -e "console.log(require.resolve('@nx/<plugin>/generators.json'));"` to find the generators.json, then locate the source from there
|
||||
- If that fails, read directly from `node_modules/<plugin>/generators.json`
|
||||
- For local generators: They are typically in `tools/generators/` or a local plugin directory. You can search the repo for the generator name to find it.
|
||||
|
||||
### 2.5 Reevaluate if the generator is right
|
||||
|
||||
Once you have built up an understanding of what the selected generator does, reconsider: Is this the right generator to service the user request?
|
||||
If not, it's okay to go back to the Generator Discovery Flow and select a different generator before proceeding. If you do, make sure to go through the entire pre-execution checklist once more.
|
||||
|
||||
### 3. Understand Repo Context
|
||||
|
||||
Before generating, examine the target area of the codebase:
|
||||
|
||||
- Look at similar existing artifacts (other libraries, applications, etc.)
|
||||
- Identify patterns and conventions used in the repo
|
||||
- Note naming conventions, file structures, and configuration patterns
|
||||
- Try to match these patterns when configuring the generator
|
||||
|
||||
For example, if similar libraries are using a specific test runner, build tool or linter, try to match that if possible.
|
||||
If projects or other artifacts are organized with a specific naming convention, try to match it.
|
||||
|
||||
### 4. Validate Required Options
|
||||
|
||||
Ensure all required options have values:
|
||||
|
||||
- Map the user's request to generator options
|
||||
- Infer values from context where possible
|
||||
- Ask the user for any critical missing information
|
||||
|
||||
## Execution
|
||||
|
||||
Keep in mind that you might have to prefix things with npx/pnpx/yarn if the user doesn't have nx installed globally.
|
||||
Many generators will behave differently based on where they are executed. For example, first-party nx library generators use the cwd to determine the directory that the library should be placed in. This is highly important.
|
||||
|
||||
### Consider Dry-Run (Optional)
|
||||
|
||||
Running with `--dry-run` first is strongly encouraged but not mandatory. Use your judgment:
|
||||
|
||||
- For complex generators or unfamiliar territory: do a dry-run first
|
||||
- For simple, well-understood generators: may proceed directly
|
||||
- Dry-run shows file names and created/deleted/modified markers, but not content
|
||||
- There are cases where a generator does not support dry-run (for example if it had to install an npm package) - in that case --dry-run might fail. Don't be discouraged but simply move on to running the generator for real and iterating from there.
|
||||
|
||||
### Running the Generator
|
||||
|
||||
Execute the generator with:
|
||||
|
||||
```bash
|
||||
nx generate <generator-name> <options> --no-interactive
|
||||
```
|
||||
|
||||
**CRITICAL**: Always include `--no-interactive` to prevent prompts that would hang the execution.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
nx generate @nx/react:library --name=my-utils --no-interactive
|
||||
```
|
||||
|
||||
### Handling Generator Failures
|
||||
|
||||
If the generator fails:
|
||||
|
||||
1. **Diagnose the error** - Read the error message carefully
|
||||
2. **Identify the cause** - Missing options, invalid values, conflicts, etc.
|
||||
3. **Attempt automatic fix** - Adjust options or resolve conflicts
|
||||
4. **Retry** - Run the generator again with corrected options
|
||||
|
||||
Common failure reasons:
|
||||
|
||||
- Missing required options
|
||||
- Invalid option values
|
||||
- Conflicting with existing files
|
||||
- Missing dependencies
|
||||
- Generator doesn't support certain flag combinations
|
||||
|
||||
## Post-Generation
|
||||
|
||||
### 1. Modify Generated Code (If Needed)
|
||||
|
||||
Generators provide a starting point, but the output may need adjustment to match the user's specific requirements:
|
||||
|
||||
- Add or modify functionality as requested
|
||||
- Adjust imports, exports, or configurations
|
||||
- Integrate with existing code patterns in the repo
|
||||
|
||||
### 2. Format Code
|
||||
|
||||
Run formatting on all generated/modified files:
|
||||
|
||||
```bash
|
||||
nx format --fix
|
||||
```
|
||||
|
||||
Languages other than javascript/typescript might need other formatting invocations too.
|
||||
|
||||
### 3. Run Verification
|
||||
|
||||
Verify that the generated code works correctly. What this looks like will vary depending on the type of generator and the targets available.
|
||||
If the generator created a new project, run its targets directly
|
||||
Use your best judgement to determine what needs to be verified.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
nx lint <new-project>
|
||||
nx test <new-project>
|
||||
nx build <new-project>
|
||||
```
|
||||
|
||||
### 4. Handle Verification Failures
|
||||
|
||||
When verification fails:
|
||||
|
||||
**If scope is manageable** (a few lint errors, minor type issues):
|
||||
|
||||
- Fix the issues
|
||||
- Re-run verification to confirm
|
||||
|
||||
**If issues are extensive** (many errors, complex problems):
|
||||
|
||||
- Attempt simple, obvious fixes first
|
||||
- If still failing, escalate to the user with:
|
||||
- Description of what was generated
|
||||
- What verification is failing
|
||||
- What you've attempted to fix
|
||||
- Remaining issues that need user input
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Generator Failures
|
||||
|
||||
- Check the error message for specific causes
|
||||
- Verify all required options are provided
|
||||
- Check for conflicts with existing files
|
||||
- Ensure the generator name and options are correct
|
||||
|
||||
### Missing Options
|
||||
|
||||
- Consult the generator schema for required fields
|
||||
- Infer values from context when reasonable
|
||||
- Ask the user for values that cannot be inferred
|
||||
|
||||
## Key Principles
|
||||
|
||||
1. **Local generators first** - Always prefer workspace/local generators over external plugin generators when both could work
|
||||
|
||||
2. **Understand before running** - Read both the schema AND the source code to fully understand what will happen
|
||||
|
||||
3. **No prompts** - Always use `--no-interactive` to prevent hanging
|
||||
|
||||
4. **Generators are starting points** - Modify the output as needed to fully satisfy the user's requirements
|
||||
|
||||
5. **Verify changes work** - Don't just generate; ensure the code builds, lints, and tests pass
|
||||
|
||||
6. **Be proactive about fixes** - Don't just report errors; attempt to resolve them automatically when possible
|
||||
|
||||
7. **Match repo patterns** - Study existing similar code in the repo and match its conventions
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
name: nx-plugins
|
||||
description: Find and add Nx plugins. USE WHEN user wants to discover available plugins, install a new plugin, or add support for a specific framework or technology to the workspace.
|
||||
---
|
||||
|
||||
## Finding and Installing new plugins
|
||||
|
||||
- List plugins: `pnpm nx list`
|
||||
- Install plugins `pnpm nx add <plugin>`. Example: `pnpm nx add @nx/react`.
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
name: nx-run-tasks
|
||||
description: Helps with running tasks in an Nx workspace. USE WHEN the user wants to execute build, test, lint, serve, or run any other tasks defined in the workspace.
|
||||
---
|
||||
|
||||
You can run tasks with Nx in the following way.
|
||||
|
||||
Keep in mind that you might have to prefix things with npx/pnpx/yarn if the user doesn't have nx installed globally. Look at the package.json or lockfile to determine which package manager is in use.
|
||||
|
||||
For more details on any command, run it with `--help` (e.g. `nx run-many --help`, `nx affected --help`).
|
||||
|
||||
## Understand which tasks can be run
|
||||
|
||||
You can check those via `nx show project <projectname> --json`, for example `nx show project myapp --json`. It contains a `targets` section which has information about targets that can be run. You can also just look at the `package.json` scripts or `project.json` targets, but you might miss out on inferred tasks by Nx plugins.
|
||||
|
||||
## Run a single task
|
||||
|
||||
```
|
||||
nx run <project>:<task>
|
||||
```
|
||||
|
||||
where `project` is the project name defined in `package.json` or `project.json` (if present).
|
||||
|
||||
## Run multiple tasks
|
||||
|
||||
```
|
||||
nx run-many -t build test lint typecheck
|
||||
```
|
||||
|
||||
You can pass a `-p` flag to filter to specific projects, otherwise it runs on all projects. You can also use `--exclude` to exclude projects, and `--parallel` to control the number of parallel processes (default is 3).
|
||||
|
||||
Examples:
|
||||
|
||||
- `nx run-many -t test -p proj1 proj2` — test specific projects
|
||||
- `nx run-many -t test --projects=*-app --exclude=excluded-app` — test projects matching a pattern
|
||||
- `nx run-many -t test --projects=tag:api-*` — test projects by tag
|
||||
|
||||
## Run tasks for affected projects
|
||||
|
||||
Use `nx affected` to only run tasks on projects that have been changed and projects that depend on changed projects. This is especially useful in CI and for large workspaces.
|
||||
|
||||
```
|
||||
nx affected -t build test lint
|
||||
```
|
||||
|
||||
By default it compares against the base branch. You can customize this:
|
||||
|
||||
- `nx affected -t test --base=main --head=HEAD` — compare against a specific base and head
|
||||
- `nx affected -t test --files=libs/mylib/src/index.ts` — specify changed files directly
|
||||
|
||||
## Useful flags
|
||||
|
||||
These flags work with `run`, `run-many`, and `affected`:
|
||||
|
||||
- `--skipNxCache` — rerun tasks even when results are cached
|
||||
- `--verbose` — print additional information such as stack traces
|
||||
- `--nxBail` — stop execution after the first failed task
|
||||
- `--configuration=<name>` — use a specific configuration (e.g. `production`)
|
||||
@@ -0,0 +1,186 @@
|
||||
---
|
||||
name: nx-workspace
|
||||
description: "Explore and understand Nx workspaces. USE WHEN answering any questions about the nx workspace, the projects in it or tasks to run. EXAMPLES: 'What projects are in this workspace?', 'How is project X configured?', 'What targets can I run?', 'What's affected by my changes?', 'Which projects depend on library Y?', or any questions about Nx workspace structure, project configuration, or available tasks."
|
||||
---
|
||||
|
||||
# Nx Workspace Exploration
|
||||
|
||||
This skill provides read-only exploration of Nx workspaces. Use it to understand workspace structure, project configuration, available targets, and dependencies.
|
||||
|
||||
Keep in mind that you might have to prefix commands with `npx`/`pnpx`/`yarn` if nx isn't installed globally. Check the lockfile to determine the package manager in use.
|
||||
|
||||
## Listing Projects
|
||||
|
||||
Use `nx show projects` to list projects in the workspace.
|
||||
|
||||
```bash
|
||||
# List all projects
|
||||
nx show projects
|
||||
|
||||
# Filter by pattern (glob)
|
||||
nx show projects --projects "apps/*"
|
||||
nx show projects --projects "shared-*"
|
||||
|
||||
# Filter by project type
|
||||
nx show projects --type app
|
||||
nx show projects --type lib
|
||||
nx show projects --type e2e
|
||||
|
||||
# Filter by target (projects that have a specific target)
|
||||
nx show projects --withTarget build
|
||||
nx show projects --withTarget e2e
|
||||
|
||||
# Find affected projects (changed since base branch)
|
||||
nx show projects --affected
|
||||
nx show projects --affected --base=main
|
||||
nx show projects --affected --type app
|
||||
|
||||
# Combine filters
|
||||
nx show projects --type lib --withTarget test
|
||||
nx show projects --affected --exclude="*-e2e"
|
||||
|
||||
# Output as JSON
|
||||
nx show projects --json
|
||||
```
|
||||
|
||||
## Project Configuration
|
||||
|
||||
Use `nx show project <name> --json` to get the full resolved configuration for a project.
|
||||
|
||||
**Important**: Do NOT read `project.json` directly - it only contains partial configuration. The `nx show project` command returns the full resolved config including inferred targets from plugins.
|
||||
|
||||
You can read the full project schema at `node_modules/nx/schemas/project-schema.json` to understand nx project configuration options.
|
||||
|
||||
```bash
|
||||
# Get full project configuration
|
||||
nx show project my-app --json
|
||||
|
||||
# Extract specific parts from the JSON
|
||||
nx show project my-app --json | jq '.targets'
|
||||
nx show project my-app --json | jq '.targets.build'
|
||||
nx show project my-app --json | jq '.targets | keys'
|
||||
|
||||
|
||||
# Check project metadata
|
||||
nx show project my-app --json | jq '{name, root, sourceRoot, projectType, tags}'
|
||||
```
|
||||
|
||||
## Target Information
|
||||
|
||||
Targets define what tasks can be run on a project.
|
||||
|
||||
```bash
|
||||
# List all targets for a project
|
||||
nx show project my-app --json | jq '.targets | keys'
|
||||
|
||||
# Get full target configuration
|
||||
nx show project my-app --json | jq '.targets.build'
|
||||
|
||||
# Check target executor/command
|
||||
nx show project my-app --json | jq '.targets.build.executor'
|
||||
nx show project my-app --json | jq '.targets.build.command'
|
||||
|
||||
# View target options
|
||||
nx show project my-app --json | jq '.targets.build.options'
|
||||
|
||||
# Check target inputs/outputs (for caching)
|
||||
nx show project my-app --json | jq '.targets.build.inputs'
|
||||
nx show project my-app --json | jq '.targets.build.outputs'
|
||||
|
||||
# Find projects with a specific target
|
||||
nx show projects --withTarget serve
|
||||
nx show projects --withTarget e2e
|
||||
```
|
||||
|
||||
## Workspace Configuration
|
||||
|
||||
Read `nx.json` directly for workspace-level configuration.
|
||||
You can read the full project schema at `node_modules/nx/schemas/nx-schema.json` to understand nx project configuration options.
|
||||
|
||||
```bash
|
||||
# Read the full nx.json
|
||||
cat nx.json
|
||||
|
||||
# Or use jq for specific sections
|
||||
cat nx.json | jq '.targetDefaults'
|
||||
cat nx.json | jq '.namedInputs'
|
||||
cat nx.json | jq '.plugins'
|
||||
cat nx.json | jq '.generators'
|
||||
```
|
||||
|
||||
Key nx.json sections:
|
||||
|
||||
- `targetDefaults` - Default configuration applied to all targets of a given name
|
||||
- `namedInputs` - Reusable input definitions for caching
|
||||
- `plugins` - Nx plugins and their configuration
|
||||
- ...and much more, read the schema or nx.json for details
|
||||
|
||||
## Affected Projects
|
||||
|
||||
Find projects affected by changes in the current branch.
|
||||
|
||||
```bash
|
||||
# Affected since base branch (auto-detected)
|
||||
nx show projects --affected
|
||||
|
||||
# Affected with explicit base
|
||||
nx show projects --affected --base=main
|
||||
nx show projects --affected --base=origin/main
|
||||
|
||||
# Affected between two commits
|
||||
nx show projects --affected --base=abc123 --head=def456
|
||||
|
||||
# Affected apps only
|
||||
nx show projects --affected --type app
|
||||
|
||||
# Affected excluding e2e projects
|
||||
nx show projects --affected --exclude="*-e2e"
|
||||
|
||||
# Affected by uncommitted changes
|
||||
nx show projects --affected --uncommitted
|
||||
|
||||
# Affected by untracked files
|
||||
nx show projects --affected --untracked
|
||||
```
|
||||
|
||||
## Common Exploration Patterns
|
||||
|
||||
### "What's in this workspace?"
|
||||
|
||||
```bash
|
||||
nx show projects
|
||||
nx show projects --type app
|
||||
nx show projects --type lib
|
||||
```
|
||||
|
||||
### "How do I build/test/lint project X?"
|
||||
|
||||
```bash
|
||||
nx show project X --json | jq '.targets | keys'
|
||||
nx show project X --json | jq '.targets.build'
|
||||
```
|
||||
|
||||
### "What depends on library Y?"
|
||||
|
||||
```bash
|
||||
# Find projects that may depend on Y by searching for imports
|
||||
# (Nx doesn't have a direct "dependents" command via CLI)
|
||||
grep -r "from '@myorg/Y'" --include="*.ts" --include="*.tsx" apps/ libs/
|
||||
```
|
||||
|
||||
### "What configuration options are available?"
|
||||
|
||||
```bash
|
||||
cat node_modules/nx/schemas/nx-schema.json | jq '.properties | keys'
|
||||
cat node_modules/nx/schemas/project-schema.json | jq '.properties | keys'
|
||||
```
|
||||
|
||||
### "Why is project X affected?"
|
||||
|
||||
```bash
|
||||
# Check what files changed
|
||||
git diff --name-only main
|
||||
|
||||
# See which project owns those files
|
||||
nx show project X --json | jq '.root'
|
||||
```
|
||||
@@ -0,0 +1,438 @@
|
||||
description = "Monitor Nx Cloud CI pipeline and handle self-healing fixes automatically. Checks for Nx Cloud connection before starting."
|
||||
prompt = """
|
||||
# CI Monitor Command
|
||||
|
||||
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
|
||||
|
||||
## Context
|
||||
|
||||
- **Current Branch:** !`git branch --show-current`
|
||||
- **Current Commit:** !`git rev-parse --short HEAD`
|
||||
- **Remote Status:** !`git status -sb | head -1`
|
||||
|
||||
## User Instructions
|
||||
|
||||
{{args}}
|
||||
|
||||
**Important:** If user provides specific instructions, respect them over default behaviors described below.
|
||||
|
||||
## Configuration Defaults
|
||||
|
||||
| Setting | Default | Description |
|
||||
| ------------------------- | ------------- | ------------------------------------------------------------------- |
|
||||
| `--max-cycles` | 10 | Maximum CIPE cycles before timeout |
|
||||
| `--timeout` | 120 | Maximum duration in minutes |
|
||||
| `--verbosity` | medium | Output level: minimal, medium, verbose |
|
||||
| `--branch` | (auto-detect) | Branch to monitor |
|
||||
| `--subagent-timeout` | 60 | Subagent polling timeout in minutes |
|
||||
| `--fresh` | false | Ignore previous context, start fresh |
|
||||
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
|
||||
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
|
||||
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
|
||||
|
||||
Parse any overrides from `{{args}}` and merge with defaults.
|
||||
|
||||
## Nx Cloud Connection Check
|
||||
|
||||
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
|
||||
|
||||
### Step 0: Verify Nx Cloud Connection
|
||||
|
||||
1. **Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
|
||||
2. **If `nx.json` missing OR neither property exists** → exit with:
|
||||
```
|
||||
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
|
||||
```
|
||||
3. **If connected** → continue to main loop
|
||||
|
||||
## Session Context Behavior
|
||||
|
||||
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
|
||||
|
||||
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
|
||||
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
|
||||
- **For a completely clean slate:** Exit Claude Code and restart `claude`
|
||||
|
||||
## Default Behaviors by Status
|
||||
|
||||
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
|
||||
|
||||
| Status | Default Behavior |
|
||||
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ci_success` | Exit with success. Log "CI passed successfully!" |
|
||||
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
|
||||
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
|
||||
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
|
||||
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
|
||||
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
|
||||
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
|
||||
| `polling_timeout` | Subagent polling timeout reached. Exit with timeout. |
|
||||
| `cipe_canceled` | CIPE was canceled. Exit with canceled status. |
|
||||
| `cipe_timed_out` | CIPE timed out. Exit with timeout status. |
|
||||
| `error` | Increment `no_progress_count`. If >= 3 → exit with circuit breaker. Otherwise wait 60s and loop. |
|
||||
|
||||
### Fix Available Decision Logic
|
||||
|
||||
When subagent returns `fix_available`, main agent compares `failedTaskIds` vs `verifiedTaskIds`:
|
||||
|
||||
#### Step 1: Categorize Tasks
|
||||
|
||||
1. **Verified tasks** = tasks in both `failedTaskIds` AND `verifiedTaskIds`
|
||||
2. **Unverified tasks** = tasks in `failedTaskIds` but NOT in `verifiedTaskIds`
|
||||
3. **E2E tasks** = unverified tasks where target contains "e2e" (task format: `<project>:<target>` or `<project>:<target>:<config>`)
|
||||
4. **Verifiable tasks** = unverified tasks that are NOT e2e
|
||||
|
||||
#### Step 2: Determine Path
|
||||
|
||||
| Condition | Path |
|
||||
| --------------------------------------- | ---------------------------------------- |
|
||||
| No unverified tasks (all verified) | Apply via MCP |
|
||||
| Unverified tasks exist, but ALL are e2e | Apply via MCP (treat as verified enough) |
|
||||
| Verifiable tasks exist | Local verification flow |
|
||||
|
||||
#### Step 3a: Apply via MCP (fully/e2e-only verified)
|
||||
|
||||
- Call `update_self_healing_fix({ shortLink, action: "APPLY" })`
|
||||
- Record `last_cipe_url`, spawn subagent in wait mode
|
||||
|
||||
#### Step 3b: Local Verification Flow
|
||||
|
||||
When verifiable (non-e2e) unverified tasks exist:
|
||||
|
||||
1. **Detect package manager:**
|
||||
|
||||
- `pnpm-lock.yaml` exists → `pnpm nx`
|
||||
- `yarn.lock` exists → `yarn nx`
|
||||
- Otherwise → `npx nx`
|
||||
|
||||
2. **Run verifiable tasks in parallel:**
|
||||
|
||||
- Spawn `general` subagents to run each task concurrently
|
||||
- Each subagent runs: `<pm> nx run <taskId>`
|
||||
- Collect pass/fail results from all subagents
|
||||
|
||||
3. **Evaluate results:**
|
||||
|
||||
| Result | Action |
|
||||
| ------------------------- | ---------------------------- |
|
||||
| ALL verifiable tasks pass | Apply via MCP |
|
||||
| ANY verifiable task fails | Apply-locally + enhance flow |
|
||||
|
||||
4. **Apply-locally + enhance flow:**
|
||||
|
||||
- Run `nx apply-locally <shortLink>`
|
||||
- Enhance the code to fix failing tasks
|
||||
- Run failing tasks again to verify fix
|
||||
- If still failing → increment `local_verify_count`, loop back to enhance
|
||||
- If passing → commit and push, record `expected_commit_sha`, spawn subagent in wait mode
|
||||
|
||||
5. **Track attempts** (wraps step 4):
|
||||
- Increment `local_verify_count` after each enhance cycle
|
||||
- If `local_verify_count >= local_verify_attempts` (default: 3):
|
||||
- Get code in commit-able state
|
||||
- Commit and push with message indicating local verification failed
|
||||
- Report to user:
|
||||
```
|
||||
[ci-monitor] Local verification failed after <N> attempts. Pushed to CI for final validation. Failed: <taskIds>
|
||||
```
|
||||
- Record `expected_commit_sha`, spawn subagent in wait mode (let CI be final judge)
|
||||
|
||||
#### Commit Message Format
|
||||
|
||||
```bash
|
||||
git commit -m "fix(<projects>): <brief description>
|
||||
|
||||
Failed tasks: <taskId1>, <taskId2>
|
||||
Local verification: passed|enhanced|failed-pushing-to-ci"
|
||||
```
|
||||
|
||||
### Unverified Fix Flow (No Verification Attempted)
|
||||
|
||||
When `verificationStatus` is `FAILED`, `NOT_EXECUTABLE`, or fix has `couldAutoApplyTasks != true` with no verification:
|
||||
|
||||
- Analyze fix content (`suggestedFix`, `suggestedFixReasoning`, `taskOutputSummary`)
|
||||
- If fix looks correct → apply via MCP
|
||||
- If fix needs enhancement → use Apply Locally + Enhance Flow above
|
||||
- If fix is wrong → reject via MCP, fix from scratch, commit, push
|
||||
|
||||
### Auto-Apply Eligibility
|
||||
|
||||
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
|
||||
|
||||
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
|
||||
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
|
||||
|
||||
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
|
||||
|
||||
### Apply vs Reject vs Apply Locally
|
||||
|
||||
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
|
||||
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
|
||||
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
|
||||
|
||||
### Apply Locally + Enhance Flow
|
||||
|
||||
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
|
||||
|
||||
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
|
||||
2. Make additional changes as needed
|
||||
3. Commit and push:
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix: resolve <failedTaskIds>"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
4. Loop to poll for new CIPE
|
||||
|
||||
### Reject + Fix From Scratch Flow
|
||||
|
||||
When the fix is completely wrong:
|
||||
|
||||
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
|
||||
2. Fix the issue from scratch locally
|
||||
3. Commit and push:
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix: resolve <failedTaskIds>"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
4. Loop to poll for new CIPE
|
||||
|
||||
### Environment Issue Handling
|
||||
|
||||
When `failureClassification == 'ENVIRONMENT_STATE'`:
|
||||
|
||||
1. Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`
|
||||
2. New CIPE spawns automatically (no local git operations needed)
|
||||
3. Loop to poll for new CIPE with `previousCipeUrl` set
|
||||
|
||||
### No-New-CIPE Handling
|
||||
|
||||
When `status == 'no_new_cipe'`:
|
||||
|
||||
This means the expected CIPE was never created - CI likely failed before Nx tasks could run.
|
||||
|
||||
1. **Report to user:**
|
||||
|
||||
```
|
||||
[ci-monitor] No CI attempt for <sha> after 10 min. Check CI provider for pre-Nx failures (install, checkout, auth). Last CI attempt: <previousCipeUrl>
|
||||
```
|
||||
|
||||
2. **If user configured auto-fix attempts** (e.g., `--auto-fix-workflow`):
|
||||
|
||||
- Detect package manager: check for `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`
|
||||
- Run install to update lockfile:
|
||||
```bash
|
||||
pnpm install # or npm install / yarn install
|
||||
```
|
||||
- If lockfile changed:
|
||||
```bash
|
||||
git add pnpm-lock.yaml # or appropriate lockfile
|
||||
git commit -m "chore: update lockfile"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
- Record new commit SHA, loop to poll with `expectedCommitSha`
|
||||
|
||||
3. **Otherwise:** Exit with `no_new_cipe` status, providing guidance for user to investigate
|
||||
|
||||
## Exit Conditions
|
||||
|
||||
Exit the monitoring loop when ANY of these conditions are met:
|
||||
|
||||
| Condition | Exit Type |
|
||||
| ------------------------------------------- | ---------------- |
|
||||
| CI passes (`cipeStatus == 'SUCCEEDED'`) | Success |
|
||||
| Max CIPE cycles reached | Timeout |
|
||||
| Max duration reached | Timeout |
|
||||
| 3 consecutive no-progress iterations | Circuit breaker |
|
||||
| No fix available and local fix not possible | Failure |
|
||||
| No new CIPE and auto-fix not configured | Pre-CIPE failure |
|
||||
| User cancels | Cancelled |
|
||||
|
||||
## Main Loop
|
||||
|
||||
### Step 1: Initialize Tracking
|
||||
|
||||
```
|
||||
cycle_count = 0
|
||||
start_time = now()
|
||||
no_progress_count = 0
|
||||
local_verify_count = 0
|
||||
last_state = null
|
||||
last_cipe_url = null
|
||||
expected_commit_sha = null
|
||||
```
|
||||
|
||||
### Step 2: Spawn Subagent
|
||||
|
||||
Spawn the `ci-watcher` subagent to poll CI status:
|
||||
|
||||
**Fresh start (first spawn, no expected CIPE):**
|
||||
|
||||
```
|
||||
Task(
|
||||
agent: "ci-watcher",
|
||||
prompt: "Monitor CI for branch '<branch>'.
|
||||
Subagent timeout: <subagent-timeout> minutes.
|
||||
New-CIPE timeout: <new-cipe-timeout> minutes.
|
||||
Verbosity: <verbosity>."
|
||||
)
|
||||
```
|
||||
|
||||
**After action that triggers new CIPE (wait mode):**
|
||||
|
||||
```
|
||||
Task(
|
||||
agent: "ci-watcher",
|
||||
prompt: "Monitor CI for branch '<branch>'.
|
||||
Subagent timeout: <subagent-timeout> minutes.
|
||||
New-CIPE timeout: <new-cipe-timeout> minutes.
|
||||
Verbosity: <verbosity>.
|
||||
|
||||
WAIT MODE: A new CIPE should spawn. Ignore old CIPE until new one appears.
|
||||
Expected commit SHA: <expected_commit_sha>
|
||||
Previous CIPE URL: <last_cipe_url>"
|
||||
)
|
||||
```
|
||||
|
||||
### Step 3: Handle Subagent Response
|
||||
|
||||
When subagent returns:
|
||||
|
||||
1. Check the returned status
|
||||
2. Look up default behavior in the table above
|
||||
3. Check if user instructions override the default
|
||||
4. Execute the appropriate action
|
||||
5. **If action expects new CIPE**, update tracking (see Step 3a)
|
||||
6. If action results in looping, go to Step 2
|
||||
|
||||
### Step 3a: Track State for New-CIPE Detection
|
||||
|
||||
After actions that should trigger a new CIPE, record state before looping:
|
||||
|
||||
| Action | What to Track | Subagent Mode |
|
||||
| ----------------------------- | --------------------------------------------- | ------------- |
|
||||
| Fix auto-applying | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| Apply via MCP | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| Apply locally + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Reject + fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Fix failed + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| No fix + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Environment rerun | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| No-new-CIPE + auto-fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
|
||||
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
|
||||
|
||||
- Subagent will **completely ignore** the old/stale CIPE
|
||||
- Subagent will only wait for new CIPE to appear
|
||||
- Subagent will NOT return to main agent with stale CIPE data
|
||||
- Once new CIPE detected, subagent switches to normal polling
|
||||
|
||||
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
|
||||
|
||||
### Step 4: Progress Tracking
|
||||
|
||||
After each action:
|
||||
|
||||
- If state changed significantly → reset `no_progress_count = 0`
|
||||
- If state unchanged → `no_progress_count++`
|
||||
- On new CI attempt detected → reset `local_verify_count = 0`
|
||||
|
||||
## Status Reporting
|
||||
|
||||
Based on verbosity level:
|
||||
|
||||
| Level | What to Report |
|
||||
| --------- | -------------------------------------------------------------------------- |
|
||||
| `minimal` | Only final result (success/failure/timeout) |
|
||||
| `medium` | State changes + periodic updates ("Cycle N \\| Elapsed: Xm \\| Status: ...") |
|
||||
| `verbose` | All of medium + full subagent responses, git outputs, MCP responses |
|
||||
|
||||
## User Instruction Examples
|
||||
|
||||
Users can override default behaviors:
|
||||
|
||||
| Instruction | Effect |
|
||||
| ------------------------------------------------ | --------------------------------------------- |
|
||||
| "never auto-apply" | Always prompt before applying any fix |
|
||||
| "always ask before git push" | Prompt before each push |
|
||||
| "reject any fix for e2e tasks" | Auto-reject if `failedTaskIds` contains e2e |
|
||||
| "apply all fixes regardless of verification" | Skip verification check, apply everything |
|
||||
| "if confidence < 70, reject" | Check confidence field before applying |
|
||||
| "run 'nx affected -t typecheck' before applying" | Add local verification step |
|
||||
| "auto-fix workflow failures" | Attempt lockfile updates on pre-CIPE failures |
|
||||
| "wait 45 min for new CIPE" | Override new-CIPE timeout (default: 10 min) |
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | Action |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------- |
|
||||
| Git rebase conflict | Report to user, exit |
|
||||
| `nx apply-locally` fails | Report to user, attempt manual patch or exit |
|
||||
| MCP tool error | Retry once, if fails report to user |
|
||||
| Subagent spawn failure | Retry once, if fails exit with error |
|
||||
| No new CIPE detected | If `--auto-fix-workflow`, try lockfile update; otherwise report to user with guidance |
|
||||
| Lockfile auto-fix fails | Report to user, exit with guidance to check CI logs |
|
||||
|
||||
## Example Session
|
||||
|
||||
### Example 1: Normal Flow with Self-Healing (medium verbosity)
|
||||
|
||||
```
|
||||
[ci-monitor] Starting CI monitor for branch 'feature/add-auth'
|
||||
[ci-monitor] Config: max-cycles=5, timeout=120m, verbosity=medium
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] CI attempt: IN_PROGRESS | Self-Healing: NOT_STARTED | Elapsed: 1m
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 3m
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 5m
|
||||
|
||||
[ci-monitor] Fix available! Verification: COMPLETED
|
||||
[ci-monitor] Applying fix via MCP...
|
||||
[ci-monitor] Fix applied in CI. Waiting for new CI attempt...
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] New CI attempt detected!
|
||||
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 8m
|
||||
|
||||
[ci-monitor] CI passed successfully!
|
||||
|
||||
[ci-monitor] Summary:
|
||||
- Total cycles: 2
|
||||
- Total time: 12m 34s
|
||||
- Fixes applied: 1
|
||||
- Result: SUCCESS
|
||||
```
|
||||
|
||||
### Example 2: Pre-CI Failure (medium verbosity)
|
||||
|
||||
```
|
||||
[ci-monitor] Starting CI monitor for branch 'feature/add-products'
|
||||
[ci-monitor] Config: max-cycles=5, timeout=120m, auto-fix-workflow=true
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 2m
|
||||
|
||||
[ci-monitor] Applying fix locally, enhancing, and pushing...
|
||||
[ci-monitor] Committed: abc1234
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] Waiting for new CI attempt... (expected SHA: abc1234)
|
||||
[CI Monitor] ⚠️ CI attempt timeout (10 min). Returning no_new_cipe.
|
||||
|
||||
[ci-monitor] Status: no_new_cipe
|
||||
[ci-monitor] --auto-fix-workflow enabled. Attempting lockfile update...
|
||||
[ci-monitor] Lockfile updated. Committed: def5678
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] New CI attempt detected!
|
||||
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 18m
|
||||
|
||||
[ci-monitor] CI passed successfully!
|
||||
|
||||
[ci-monitor] Summary:
|
||||
- Total cycles: 3
|
||||
- Total time: 22m 15s
|
||||
- Fixes applied: 1 (self-healing) + 1 (lockfile)
|
||||
- Result: SUCCESS
|
||||
```"""
|
||||
@@ -0,0 +1,437 @@
|
||||
---
|
||||
name: ci-monitor
|
||||
description: Monitor Nx Cloud CI pipeline and handle self-healing fixes automatically. Checks for Nx Cloud connection before starting.
|
||||
---
|
||||
|
||||
# CI Monitor Command
|
||||
|
||||
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
|
||||
|
||||
## Context
|
||||
|
||||
- **Current Branch:** !`git branch --show-current`
|
||||
- **Current Commit:** !`git rev-parse --short HEAD`
|
||||
- **Remote Status:** !`git status -sb | head -1`
|
||||
|
||||
## User Instructions
|
||||
|
||||
$ARGUMENTS
|
||||
|
||||
**Important:** If user provides specific instructions, respect them over default behaviors described below.
|
||||
|
||||
## Configuration Defaults
|
||||
|
||||
| Setting | Default | Description |
|
||||
| ------------------------- | ------------- | ------------------------------------------------------------------- |
|
||||
| `--max-cycles` | 10 | Maximum CIPE cycles before timeout |
|
||||
| `--timeout` | 120 | Maximum duration in minutes |
|
||||
| `--verbosity` | medium | Output level: minimal, medium, verbose |
|
||||
| `--branch` | (auto-detect) | Branch to monitor |
|
||||
| `--subagent-timeout` | 60 | Subagent polling timeout in minutes |
|
||||
| `--fresh` | false | Ignore previous context, start fresh |
|
||||
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
|
||||
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
|
||||
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
|
||||
|
||||
Parse any overrides from `$ARGUMENTS` and merge with defaults.
|
||||
|
||||
## Nx Cloud Connection Check
|
||||
|
||||
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
|
||||
|
||||
### Step 0: Verify Nx Cloud Connection
|
||||
|
||||
1. **Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
|
||||
2. **If `nx.json` missing OR neither property exists** → exit with:
|
||||
```
|
||||
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
|
||||
```
|
||||
3. **If connected** → continue to main loop
|
||||
|
||||
## Session Context Behavior
|
||||
|
||||
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
|
||||
|
||||
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
|
||||
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
|
||||
- **For a completely clean slate:** Exit Claude Code and restart `claude`
|
||||
|
||||
## Default Behaviors by Status
|
||||
|
||||
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
|
||||
|
||||
| Status | Default Behavior |
|
||||
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ci_success` | Exit with success. Log "CI passed successfully!" |
|
||||
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
|
||||
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
|
||||
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
|
||||
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
|
||||
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
|
||||
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
|
||||
| `polling_timeout` | Subagent polling timeout reached. Exit with timeout. |
|
||||
| `cipe_canceled` | CIPE was canceled. Exit with canceled status. |
|
||||
| `cipe_timed_out` | CIPE timed out. Exit with timeout status. |
|
||||
| `error` | Increment `no_progress_count`. If >= 3 → exit with circuit breaker. Otherwise wait 60s and loop. |
|
||||
|
||||
### Fix Available Decision Logic
|
||||
|
||||
When subagent returns `fix_available`, main agent compares `failedTaskIds` vs `verifiedTaskIds`:
|
||||
|
||||
#### Step 1: Categorize Tasks
|
||||
|
||||
1. **Verified tasks** = tasks in both `failedTaskIds` AND `verifiedTaskIds`
|
||||
2. **Unverified tasks** = tasks in `failedTaskIds` but NOT in `verifiedTaskIds`
|
||||
3. **E2E tasks** = unverified tasks where target contains "e2e" (task format: `<project>:<target>` or `<project>:<target>:<config>`)
|
||||
4. **Verifiable tasks** = unverified tasks that are NOT e2e
|
||||
|
||||
#### Step 2: Determine Path
|
||||
|
||||
| Condition | Path |
|
||||
| --------------------------------------- | ---------------------------------------- |
|
||||
| No unverified tasks (all verified) | Apply via MCP |
|
||||
| Unverified tasks exist, but ALL are e2e | Apply via MCP (treat as verified enough) |
|
||||
| Verifiable tasks exist | Local verification flow |
|
||||
|
||||
#### Step 3a: Apply via MCP (fully/e2e-only verified)
|
||||
|
||||
- Call `update_self_healing_fix({ shortLink, action: "APPLY" })`
|
||||
- Record `last_cipe_url`, spawn subagent in wait mode
|
||||
|
||||
#### Step 3b: Local Verification Flow
|
||||
|
||||
When verifiable (non-e2e) unverified tasks exist:
|
||||
|
||||
1. **Detect package manager:**
|
||||
- `pnpm-lock.yaml` exists → `pnpm nx`
|
||||
- `yarn.lock` exists → `yarn nx`
|
||||
- Otherwise → `npx nx`
|
||||
|
||||
2. **Run verifiable tasks in parallel:**
|
||||
- Spawn `general` subagents to run each task concurrently
|
||||
- Each subagent runs: `<pm> nx run <taskId>`
|
||||
- Collect pass/fail results from all subagents
|
||||
|
||||
3. **Evaluate results:**
|
||||
|
||||
| Result | Action |
|
||||
| ------------------------- | ---------------------------- |
|
||||
| ALL verifiable tasks pass | Apply via MCP |
|
||||
| ANY verifiable task fails | Apply-locally + enhance flow |
|
||||
|
||||
4. **Apply-locally + enhance flow:**
|
||||
- Run `nx apply-locally <shortLink>`
|
||||
- Enhance the code to fix failing tasks
|
||||
- Run failing tasks again to verify fix
|
||||
- If still failing → increment `local_verify_count`, loop back to enhance
|
||||
- If passing → commit and push, record `expected_commit_sha`, spawn subagent in wait mode
|
||||
|
||||
5. **Track attempts** (wraps step 4):
|
||||
- Increment `local_verify_count` after each enhance cycle
|
||||
- If `local_verify_count >= local_verify_attempts` (default: 3):
|
||||
- Get code in commit-able state
|
||||
- Commit and push with message indicating local verification failed
|
||||
- Report to user:
|
||||
```
|
||||
[ci-monitor] Local verification failed after <N> attempts. Pushed to CI for final validation. Failed: <taskIds>
|
||||
```
|
||||
- Record `expected_commit_sha`, spawn subagent in wait mode (let CI be final judge)
|
||||
|
||||
#### Commit Message Format
|
||||
|
||||
```bash
|
||||
git commit -m "fix(<projects>): <brief description>
|
||||
|
||||
Failed tasks: <taskId1>, <taskId2>
|
||||
Local verification: passed|enhanced|failed-pushing-to-ci"
|
||||
```
|
||||
|
||||
### Unverified Fix Flow (No Verification Attempted)
|
||||
|
||||
When `verificationStatus` is `FAILED`, `NOT_EXECUTABLE`, or fix has `couldAutoApplyTasks != true` with no verification:
|
||||
|
||||
- Analyze fix content (`suggestedFix`, `suggestedFixReasoning`, `taskOutputSummary`)
|
||||
- If fix looks correct → apply via MCP
|
||||
- If fix needs enhancement → use Apply Locally + Enhance Flow above
|
||||
- If fix is wrong → reject via MCP, fix from scratch, commit, push
|
||||
|
||||
### Auto-Apply Eligibility
|
||||
|
||||
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
|
||||
|
||||
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
|
||||
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
|
||||
|
||||
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
|
||||
|
||||
### Apply vs Reject vs Apply Locally
|
||||
|
||||
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
|
||||
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
|
||||
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
|
||||
|
||||
### Apply Locally + Enhance Flow
|
||||
|
||||
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
|
||||
|
||||
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
|
||||
2. Make additional changes as needed
|
||||
3. Commit and push:
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix: resolve <failedTaskIds>"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
4. Loop to poll for new CIPE
|
||||
|
||||
### Reject + Fix From Scratch Flow
|
||||
|
||||
When the fix is completely wrong:
|
||||
|
||||
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
|
||||
2. Fix the issue from scratch locally
|
||||
3. Commit and push:
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix: resolve <failedTaskIds>"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
4. Loop to poll for new CIPE
|
||||
|
||||
### Environment Issue Handling
|
||||
|
||||
When `failureClassification == 'ENVIRONMENT_STATE'`:
|
||||
|
||||
1. Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`
|
||||
2. New CIPE spawns automatically (no local git operations needed)
|
||||
3. Loop to poll for new CIPE with `previousCipeUrl` set
|
||||
|
||||
### No-New-CIPE Handling
|
||||
|
||||
When `status == 'no_new_cipe'`:
|
||||
|
||||
This means the expected CIPE was never created - CI likely failed before Nx tasks could run.
|
||||
|
||||
1. **Report to user:**
|
||||
|
||||
```
|
||||
[ci-monitor] No CI attempt for <sha> after 10 min. Check CI provider for pre-Nx failures (install, checkout, auth). Last CI attempt: <previousCipeUrl>
|
||||
```
|
||||
|
||||
2. **If user configured auto-fix attempts** (e.g., `--auto-fix-workflow`):
|
||||
- Detect package manager: check for `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`
|
||||
- Run install to update lockfile:
|
||||
```bash
|
||||
pnpm install # or npm install / yarn install
|
||||
```
|
||||
- If lockfile changed:
|
||||
```bash
|
||||
git add pnpm-lock.yaml # or appropriate lockfile
|
||||
git commit -m "chore: update lockfile"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
- Record new commit SHA, loop to poll with `expectedCommitSha`
|
||||
|
||||
3. **Otherwise:** Exit with `no_new_cipe` status, providing guidance for user to investigate
|
||||
|
||||
## Exit Conditions
|
||||
|
||||
Exit the monitoring loop when ANY of these conditions are met:
|
||||
|
||||
| Condition | Exit Type |
|
||||
| ------------------------------------------- | ---------------- |
|
||||
| CI passes (`cipeStatus == 'SUCCEEDED'`) | Success |
|
||||
| Max CIPE cycles reached | Timeout |
|
||||
| Max duration reached | Timeout |
|
||||
| 3 consecutive no-progress iterations | Circuit breaker |
|
||||
| No fix available and local fix not possible | Failure |
|
||||
| No new CIPE and auto-fix not configured | Pre-CIPE failure |
|
||||
| User cancels | Cancelled |
|
||||
|
||||
## Main Loop
|
||||
|
||||
### Step 1: Initialize Tracking
|
||||
|
||||
```
|
||||
cycle_count = 0
|
||||
start_time = now()
|
||||
no_progress_count = 0
|
||||
local_verify_count = 0
|
||||
last_state = null
|
||||
last_cipe_url = null
|
||||
expected_commit_sha = null
|
||||
```
|
||||
|
||||
### Step 2: Spawn Subagent
|
||||
|
||||
Spawn the `ci-watcher` subagent to poll CI status:
|
||||
|
||||
**Fresh start (first spawn, no expected CIPE):**
|
||||
|
||||
```
|
||||
Task(
|
||||
agent: "ci-watcher",
|
||||
prompt: "Monitor CI for branch '<branch>'.
|
||||
Subagent timeout: <subagent-timeout> minutes.
|
||||
New-CIPE timeout: <new-cipe-timeout> minutes.
|
||||
Verbosity: <verbosity>."
|
||||
)
|
||||
```
|
||||
|
||||
**After action that triggers new CIPE (wait mode):**
|
||||
|
||||
```
|
||||
Task(
|
||||
agent: "ci-watcher",
|
||||
prompt: "Monitor CI for branch '<branch>'.
|
||||
Subagent timeout: <subagent-timeout> minutes.
|
||||
New-CIPE timeout: <new-cipe-timeout> minutes.
|
||||
Verbosity: <verbosity>.
|
||||
|
||||
WAIT MODE: A new CIPE should spawn. Ignore old CIPE until new one appears.
|
||||
Expected commit SHA: <expected_commit_sha>
|
||||
Previous CIPE URL: <last_cipe_url>"
|
||||
)
|
||||
```
|
||||
|
||||
### Step 3: Handle Subagent Response
|
||||
|
||||
When subagent returns:
|
||||
|
||||
1. Check the returned status
|
||||
2. Look up default behavior in the table above
|
||||
3. Check if user instructions override the default
|
||||
4. Execute the appropriate action
|
||||
5. **If action expects new CIPE**, update tracking (see Step 3a)
|
||||
6. If action results in looping, go to Step 2
|
||||
|
||||
### Step 3a: Track State for New-CIPE Detection
|
||||
|
||||
After actions that should trigger a new CIPE, record state before looping:
|
||||
|
||||
| Action | What to Track | Subagent Mode |
|
||||
| ----------------------------- | --------------------------------------------- | ------------- |
|
||||
| Fix auto-applying | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| Apply via MCP | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| Apply locally + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Reject + fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Fix failed + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| No fix + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Environment rerun | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| No-new-CIPE + auto-fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
|
||||
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
|
||||
|
||||
- Subagent will **completely ignore** the old/stale CIPE
|
||||
- Subagent will only wait for new CIPE to appear
|
||||
- Subagent will NOT return to main agent with stale CIPE data
|
||||
- Once new CIPE detected, subagent switches to normal polling
|
||||
|
||||
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
|
||||
|
||||
### Step 4: Progress Tracking
|
||||
|
||||
After each action:
|
||||
|
||||
- If state changed significantly → reset `no_progress_count = 0`
|
||||
- If state unchanged → `no_progress_count++`
|
||||
- On new CI attempt detected → reset `local_verify_count = 0`
|
||||
|
||||
## Status Reporting
|
||||
|
||||
Based on verbosity level:
|
||||
|
||||
| Level | What to Report |
|
||||
| --------- | -------------------------------------------------------------------------- |
|
||||
| `minimal` | Only final result (success/failure/timeout) |
|
||||
| `medium` | State changes + periodic updates ("Cycle N \| Elapsed: Xm \| Status: ...") |
|
||||
| `verbose` | All of medium + full subagent responses, git outputs, MCP responses |
|
||||
|
||||
## User Instruction Examples
|
||||
|
||||
Users can override default behaviors:
|
||||
|
||||
| Instruction | Effect |
|
||||
| ------------------------------------------------ | --------------------------------------------- |
|
||||
| "never auto-apply" | Always prompt before applying any fix |
|
||||
| "always ask before git push" | Prompt before each push |
|
||||
| "reject any fix for e2e tasks" | Auto-reject if `failedTaskIds` contains e2e |
|
||||
| "apply all fixes regardless of verification" | Skip verification check, apply everything |
|
||||
| "if confidence < 70, reject" | Check confidence field before applying |
|
||||
| "run 'nx affected -t typecheck' before applying" | Add local verification step |
|
||||
| "auto-fix workflow failures" | Attempt lockfile updates on pre-CIPE failures |
|
||||
| "wait 45 min for new CIPE" | Override new-CIPE timeout (default: 10 min) |
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | Action |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------- |
|
||||
| Git rebase conflict | Report to user, exit |
|
||||
| `nx apply-locally` fails | Report to user, attempt manual patch or exit |
|
||||
| MCP tool error | Retry once, if fails report to user |
|
||||
| Subagent spawn failure | Retry once, if fails exit with error |
|
||||
| No new CIPE detected | If `--auto-fix-workflow`, try lockfile update; otherwise report to user with guidance |
|
||||
| Lockfile auto-fix fails | Report to user, exit with guidance to check CI logs |
|
||||
|
||||
## Example Session
|
||||
|
||||
### Example 1: Normal Flow with Self-Healing (medium verbosity)
|
||||
|
||||
```
|
||||
[ci-monitor] Starting CI monitor for branch 'feature/add-auth'
|
||||
[ci-monitor] Config: max-cycles=5, timeout=120m, verbosity=medium
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] CI attempt: IN_PROGRESS | Self-Healing: NOT_STARTED | Elapsed: 1m
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 3m
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 5m
|
||||
|
||||
[ci-monitor] Fix available! Verification: COMPLETED
|
||||
[ci-monitor] Applying fix via MCP...
|
||||
[ci-monitor] Fix applied in CI. Waiting for new CI attempt...
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] New CI attempt detected!
|
||||
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 8m
|
||||
|
||||
[ci-monitor] CI passed successfully!
|
||||
|
||||
[ci-monitor] Summary:
|
||||
- Total cycles: 2
|
||||
- Total time: 12m 34s
|
||||
- Fixes applied: 1
|
||||
- Result: SUCCESS
|
||||
```
|
||||
|
||||
### Example 2: Pre-CI Failure (medium verbosity)
|
||||
|
||||
```
|
||||
[ci-monitor] Starting CI monitor for branch 'feature/add-products'
|
||||
[ci-monitor] Config: max-cycles=5, timeout=120m, auto-fix-workflow=true
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 2m
|
||||
|
||||
[ci-monitor] Applying fix locally, enhancing, and pushing...
|
||||
[ci-monitor] Committed: abc1234
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] Waiting for new CI attempt... (expected SHA: abc1234)
|
||||
[CI Monitor] ⚠️ CI attempt timeout (10 min). Returning no_new_cipe.
|
||||
|
||||
[ci-monitor] Status: no_new_cipe
|
||||
[ci-monitor] --auto-fix-workflow enabled. Attempting lockfile update...
|
||||
[ci-monitor] Lockfile updated. Committed: def5678
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] New CI attempt detected!
|
||||
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 18m
|
||||
|
||||
[ci-monitor] CI passed successfully!
|
||||
|
||||
[ci-monitor] Summary:
|
||||
- Total cycles: 3
|
||||
- Total time: 22m 15s
|
||||
- Fixes applied: 1 (self-healing) + 1 (lockfile)
|
||||
- Result: SUCCESS
|
||||
```
|
||||
@@ -0,0 +1,228 @@
|
||||
---
|
||||
name: nx-generate
|
||||
description: Generate code using nx generators. USE WHEN scaffolding code or transforming existing code - for example creating libraries or applications, or anything else that is boilerplate code or automates repetitive tasks. ALWAYS use this first when generating code with Nx instead of calling MCP tools or running nx generate immediately.
|
||||
---
|
||||
|
||||
# Run Nx Generator
|
||||
|
||||
Nx generators are powerful tools that scaffold projects, make automated code migrations or automate repetitive tasks in a monorepo. They ensure consistency across the codebase and reduce boilerplate work.
|
||||
|
||||
This skill applies when the user wants to:
|
||||
|
||||
- Create new projects like libraries or applications
|
||||
- Scaffold features or boilerplate code
|
||||
- Run workspace-specific or custom generators
|
||||
- Do anything else that an nx generator exists for
|
||||
|
||||
## Generator Discovery Flow
|
||||
|
||||
### Step 1: List Available Generators
|
||||
|
||||
Use the Nx CLI to discover available generators:
|
||||
|
||||
- List all generators for a plugin: `npx nx list @nx/react`
|
||||
- View available plugins: `npx nx list`
|
||||
|
||||
This includes:
|
||||
|
||||
- Plugin generators (e.g., `@nx/react:library`, `@nx/js:library`)
|
||||
- Local workspace generators (defined in the repo's own plugins)
|
||||
|
||||
### Step 2: Match Generator to User Request
|
||||
|
||||
Based on the user's request, identify which generator(s) could fulfill their needs. Consider:
|
||||
|
||||
- What artifact type they want to create (library, application, etc.)
|
||||
- Which framework or technology stack is relevant
|
||||
- Whether they mentioned specific generator names
|
||||
|
||||
**IMPORTANT**: When both a local workspace generator and an external plugin generator could satisfy the request, **always prefer the local workspace generator**. Local generators are customized for the specific repo's patterns and conventions.
|
||||
|
||||
It's possible that the user request is something that no Nx generator exists for whatsoever. In this case, you can stop using this skill and try to help the user another way. HOWEVER, the burden of proof for this is high. Before aborting, carefully consider each and every generator that's available. Look into details for any that could be related in any way before making this decision.
|
||||
|
||||
## Pre-Execution Checklist
|
||||
|
||||
Before running any generator, complete these steps:
|
||||
|
||||
### 1. Fetch Generator Schema
|
||||
|
||||
Use the `--help` flag to understand all available options:
|
||||
|
||||
```bash
|
||||
npx nx g @nx/react:library --help
|
||||
```
|
||||
|
||||
Pay attention to:
|
||||
|
||||
- Required options that must be provided
|
||||
- Optional options that may be relevant to the user's request
|
||||
- Default values that might need to be overridden
|
||||
|
||||
### 2. Read Generator Source Code
|
||||
|
||||
Understanding what the generator actually does helps you:
|
||||
|
||||
- Know what files will be created/modified
|
||||
- Understand any side effects (updating configs, installing deps, etc.)
|
||||
- Identify options that might not be obvious from the schema
|
||||
|
||||
To find generator source code:
|
||||
|
||||
- For plugin generators: Use `node -e "console.log(require.resolve('@nx/<plugin>/generators.json'));"` to find the generators.json, then locate the source from there
|
||||
- If that fails, read directly from `node_modules/<plugin>/generators.json`
|
||||
- For local generators: They are typically in `tools/generators/` or a local plugin directory. You can search the repo for the generator name to find it.
|
||||
|
||||
### 2.5 Reevaluate if the generator is right
|
||||
|
||||
Once you have built up an understanding of what the selected generator does, reconsider: Is this the right generator to service the user request?
|
||||
If not, it's okay to go back to the Generator Discovery Flow and select a different generator before proceeding. If you do, make sure to go through the entire pre-execution checklist once more.
|
||||
|
||||
### 3. Understand Repo Context
|
||||
|
||||
Before generating, examine the target area of the codebase:
|
||||
|
||||
- Look at similar existing artifacts (other libraries, applications, etc.)
|
||||
- Identify patterns and conventions used in the repo
|
||||
- Note naming conventions, file structures, and configuration patterns
|
||||
- Try to match these patterns when configuring the generator
|
||||
|
||||
For example, if similar libraries are using a specific test runner, build tool or linter, try to match that if possible.
|
||||
If projects or other artifacts are organized with a specific naming convention, try to match it.
|
||||
|
||||
### 4. Validate Required Options
|
||||
|
||||
Ensure all required options have values:
|
||||
|
||||
- Map the user's request to generator options
|
||||
- Infer values from context where possible
|
||||
- Ask the user for any critical missing information
|
||||
|
||||
## Execution
|
||||
|
||||
Keep in mind that you might have to prefix things with npx/pnpx/yarn if the user doesn't have nx installed globally.
|
||||
Many generators will behave differently based on where they are executed. For example, first-party nx library generators use the cwd to determine the directory that the library should be placed in. This is highly important.
|
||||
|
||||
### Consider Dry-Run (Optional)
|
||||
|
||||
Running with `--dry-run` first is strongly encouraged but not mandatory. Use your judgment:
|
||||
|
||||
- For complex generators or unfamiliar territory: do a dry-run first
|
||||
- For simple, well-understood generators: may proceed directly
|
||||
- Dry-run shows file names and created/deleted/modified markers, but not content
|
||||
- There are cases where a generator does not support dry-run (for example if it had to install an npm package) - in that case --dry-run might fail. Don't be discouraged but simply move on to running the generator for real and iterating from there.
|
||||
|
||||
### Running the Generator
|
||||
|
||||
Execute the generator with:
|
||||
|
||||
```bash
|
||||
nx generate <generator-name> <options> --no-interactive
|
||||
```
|
||||
|
||||
**CRITICAL**: Always include `--no-interactive` to prevent prompts that would hang the execution.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
nx generate @nx/react:library --name=my-utils --no-interactive
|
||||
```
|
||||
|
||||
### Handling Generator Failures
|
||||
|
||||
If the generator fails:
|
||||
|
||||
1. **Diagnose the error** - Read the error message carefully
|
||||
2. **Identify the cause** - Missing options, invalid values, conflicts, etc.
|
||||
3. **Attempt automatic fix** - Adjust options or resolve conflicts
|
||||
4. **Retry** - Run the generator again with corrected options
|
||||
|
||||
Common failure reasons:
|
||||
|
||||
- Missing required options
|
||||
- Invalid option values
|
||||
- Conflicting with existing files
|
||||
- Missing dependencies
|
||||
- Generator doesn't support certain flag combinations
|
||||
|
||||
## Post-Generation
|
||||
|
||||
### 1. Modify Generated Code (If Needed)
|
||||
|
||||
Generators provide a starting point, but the output may need adjustment to match the user's specific requirements:
|
||||
|
||||
- Add or modify functionality as requested
|
||||
- Adjust imports, exports, or configurations
|
||||
- Integrate with existing code patterns in the repo
|
||||
|
||||
### 2. Format Code
|
||||
|
||||
Run formatting on all generated/modified files:
|
||||
|
||||
```bash
|
||||
nx format --fix
|
||||
```
|
||||
|
||||
Languages other than javascript/typescript might need other formatting invocations too.
|
||||
|
||||
### 3. Run Verification
|
||||
|
||||
Verify that the generated code works correctly. What this looks like will vary depending on the type of generator and the targets available.
|
||||
If the generator created a new project, run its targets directly
|
||||
Use your best judgement to determine what needs to be verified.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
nx lint <new-project>
|
||||
nx test <new-project>
|
||||
nx build <new-project>
|
||||
```
|
||||
|
||||
### 4. Handle Verification Failures
|
||||
|
||||
When verification fails:
|
||||
|
||||
**If scope is manageable** (a few lint errors, minor type issues):
|
||||
|
||||
- Fix the issues
|
||||
- Re-run verification to confirm
|
||||
|
||||
**If issues are extensive** (many errors, complex problems):
|
||||
|
||||
- Attempt simple, obvious fixes first
|
||||
- If still failing, escalate to the user with:
|
||||
- Description of what was generated
|
||||
- What verification is failing
|
||||
- What you've attempted to fix
|
||||
- Remaining issues that need user input
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Generator Failures
|
||||
|
||||
- Check the error message for specific causes
|
||||
- Verify all required options are provided
|
||||
- Check for conflicts with existing files
|
||||
- Ensure the generator name and options are correct
|
||||
|
||||
### Missing Options
|
||||
|
||||
- Consult the generator schema for required fields
|
||||
- Infer values from context when reasonable
|
||||
- Ask the user for values that cannot be inferred
|
||||
|
||||
## Key Principles
|
||||
|
||||
1. **Local generators first** - Always prefer workspace/local generators over external plugin generators when both could work
|
||||
|
||||
2. **Understand before running** - Read both the schema AND the source code to fully understand what will happen
|
||||
|
||||
3. **No prompts** - Always use `--no-interactive` to prevent hanging
|
||||
|
||||
4. **Generators are starting points** - Modify the output as needed to fully satisfy the user's requirements
|
||||
|
||||
5. **Verify changes work** - Don't just generate; ensure the code builds, lints, and tests pass
|
||||
|
||||
6. **Be proactive about fixes** - Don't just report errors; attempt to resolve them automatically when possible
|
||||
|
||||
7. **Match repo patterns** - Study existing similar code in the repo and match its conventions
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
name: nx-plugins
|
||||
description: Find and add Nx plugins. USE WHEN user wants to discover available plugins, install a new plugin, or add support for a specific framework or technology to the workspace.
|
||||
---
|
||||
|
||||
## Finding and Installing new plugins
|
||||
|
||||
- List plugins: `pnpm nx list`
|
||||
- Install plugins `pnpm nx add <plugin>`. Example: `pnpm nx add @nx/react`.
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
name: nx-run-tasks
|
||||
description: Helps with running tasks in an Nx workspace. USE WHEN the user wants to execute build, test, lint, serve, or run any other tasks defined in the workspace.
|
||||
---
|
||||
|
||||
You can run tasks with Nx in the following way.
|
||||
|
||||
Keep in mind that you might have to prefix things with npx/pnpx/yarn if the user doesn't have nx installed globally. Look at the package.json or lockfile to determine which package manager is in use.
|
||||
|
||||
For more details on any command, run it with `--help` (e.g. `nx run-many --help`, `nx affected --help`).
|
||||
|
||||
## Understand which tasks can be run
|
||||
|
||||
You can check those via `nx show project <projectname> --json`, for example `nx show project myapp --json`. It contains a `targets` section which has information about targets that can be run. You can also just look at the `package.json` scripts or `project.json` targets, but you might miss out on inferred tasks by Nx plugins.
|
||||
|
||||
## Run a single task
|
||||
|
||||
```
|
||||
nx run <project>:<task>
|
||||
```
|
||||
|
||||
where `project` is the project name defined in `package.json` or `project.json` (if present).
|
||||
|
||||
## Run multiple tasks
|
||||
|
||||
```
|
||||
nx run-many -t build test lint typecheck
|
||||
```
|
||||
|
||||
You can pass a `-p` flag to filter to specific projects, otherwise it runs on all projects. You can also use `--exclude` to exclude projects, and `--parallel` to control the number of parallel processes (default is 3).
|
||||
|
||||
Examples:
|
||||
|
||||
- `nx run-many -t test -p proj1 proj2` — test specific projects
|
||||
- `nx run-many -t test --projects=*-app --exclude=excluded-app` — test projects matching a pattern
|
||||
- `nx run-many -t test --projects=tag:api-*` — test projects by tag
|
||||
|
||||
## Run tasks for affected projects
|
||||
|
||||
Use `nx affected` to only run tasks on projects that have been changed and projects that depend on changed projects. This is especially useful in CI and for large workspaces.
|
||||
|
||||
```
|
||||
nx affected -t build test lint
|
||||
```
|
||||
|
||||
By default it compares against the base branch. You can customize this:
|
||||
|
||||
- `nx affected -t test --base=main --head=HEAD` — compare against a specific base and head
|
||||
- `nx affected -t test --files=libs/mylib/src/index.ts` — specify changed files directly
|
||||
|
||||
## Useful flags
|
||||
|
||||
These flags work with `run`, `run-many`, and `affected`:
|
||||
|
||||
- `--skipNxCache` — rerun tasks even when results are cached
|
||||
- `--verbose` — print additional information such as stack traces
|
||||
- `--nxBail` — stop execution after the first failed task
|
||||
- `--configuration=<name>` — use a specific configuration (e.g. `production`)
|
||||
@@ -0,0 +1,186 @@
|
||||
---
|
||||
name: nx-workspace
|
||||
description: "Explore and understand Nx workspaces. USE WHEN answering any questions about the nx workspace, the projects in it or tasks to run. EXAMPLES: 'What projects are in this workspace?', 'How is project X configured?', 'What targets can I run?', 'What's affected by my changes?', 'Which projects depend on library Y?', or any questions about Nx workspace structure, project configuration, or available tasks."
|
||||
---
|
||||
|
||||
# Nx Workspace Exploration
|
||||
|
||||
This skill provides read-only exploration of Nx workspaces. Use it to understand workspace structure, project configuration, available targets, and dependencies.
|
||||
|
||||
Keep in mind that you might have to prefix commands with `npx`/`pnpx`/`yarn` if nx isn't installed globally. Check the lockfile to determine the package manager in use.
|
||||
|
||||
## Listing Projects
|
||||
|
||||
Use `nx show projects` to list projects in the workspace.
|
||||
|
||||
```bash
|
||||
# List all projects
|
||||
nx show projects
|
||||
|
||||
# Filter by pattern (glob)
|
||||
nx show projects --projects "apps/*"
|
||||
nx show projects --projects "shared-*"
|
||||
|
||||
# Filter by project type
|
||||
nx show projects --type app
|
||||
nx show projects --type lib
|
||||
nx show projects --type e2e
|
||||
|
||||
# Filter by target (projects that have a specific target)
|
||||
nx show projects --withTarget build
|
||||
nx show projects --withTarget e2e
|
||||
|
||||
# Find affected projects (changed since base branch)
|
||||
nx show projects --affected
|
||||
nx show projects --affected --base=main
|
||||
nx show projects --affected --type app
|
||||
|
||||
# Combine filters
|
||||
nx show projects --type lib --withTarget test
|
||||
nx show projects --affected --exclude="*-e2e"
|
||||
|
||||
# Output as JSON
|
||||
nx show projects --json
|
||||
```
|
||||
|
||||
## Project Configuration
|
||||
|
||||
Use `nx show project <name> --json` to get the full resolved configuration for a project.
|
||||
|
||||
**Important**: Do NOT read `project.json` directly - it only contains partial configuration. The `nx show project` command returns the full resolved config including inferred targets from plugins.
|
||||
|
||||
You can read the full project schema at `node_modules/nx/schemas/project-schema.json` to understand nx project configuration options.
|
||||
|
||||
```bash
|
||||
# Get full project configuration
|
||||
nx show project my-app --json
|
||||
|
||||
# Extract specific parts from the JSON
|
||||
nx show project my-app --json | jq '.targets'
|
||||
nx show project my-app --json | jq '.targets.build'
|
||||
nx show project my-app --json | jq '.targets | keys'
|
||||
|
||||
|
||||
# Check project metadata
|
||||
nx show project my-app --json | jq '{name, root, sourceRoot, projectType, tags}'
|
||||
```
|
||||
|
||||
## Target Information
|
||||
|
||||
Targets define what tasks can be run on a project.
|
||||
|
||||
```bash
|
||||
# List all targets for a project
|
||||
nx show project my-app --json | jq '.targets | keys'
|
||||
|
||||
# Get full target configuration
|
||||
nx show project my-app --json | jq '.targets.build'
|
||||
|
||||
# Check target executor/command
|
||||
nx show project my-app --json | jq '.targets.build.executor'
|
||||
nx show project my-app --json | jq '.targets.build.command'
|
||||
|
||||
# View target options
|
||||
nx show project my-app --json | jq '.targets.build.options'
|
||||
|
||||
# Check target inputs/outputs (for caching)
|
||||
nx show project my-app --json | jq '.targets.build.inputs'
|
||||
nx show project my-app --json | jq '.targets.build.outputs'
|
||||
|
||||
# Find projects with a specific target
|
||||
nx show projects --withTarget serve
|
||||
nx show projects --withTarget e2e
|
||||
```
|
||||
|
||||
## Workspace Configuration
|
||||
|
||||
Read `nx.json` directly for workspace-level configuration.
|
||||
You can read the full project schema at `node_modules/nx/schemas/nx-schema.json` to understand nx project configuration options.
|
||||
|
||||
```bash
|
||||
# Read the full nx.json
|
||||
cat nx.json
|
||||
|
||||
# Or use jq for specific sections
|
||||
cat nx.json | jq '.targetDefaults'
|
||||
cat nx.json | jq '.namedInputs'
|
||||
cat nx.json | jq '.plugins'
|
||||
cat nx.json | jq '.generators'
|
||||
```
|
||||
|
||||
Key nx.json sections:
|
||||
|
||||
- `targetDefaults` - Default configuration applied to all targets of a given name
|
||||
- `namedInputs` - Reusable input definitions for caching
|
||||
- `plugins` - Nx plugins and their configuration
|
||||
- ...and much more, read the schema or nx.json for details
|
||||
|
||||
## Affected Projects
|
||||
|
||||
Find projects affected by changes in the current branch.
|
||||
|
||||
```bash
|
||||
# Affected since base branch (auto-detected)
|
||||
nx show projects --affected
|
||||
|
||||
# Affected with explicit base
|
||||
nx show projects --affected --base=main
|
||||
nx show projects --affected --base=origin/main
|
||||
|
||||
# Affected between two commits
|
||||
nx show projects --affected --base=abc123 --head=def456
|
||||
|
||||
# Affected apps only
|
||||
nx show projects --affected --type app
|
||||
|
||||
# Affected excluding e2e projects
|
||||
nx show projects --affected --exclude="*-e2e"
|
||||
|
||||
# Affected by uncommitted changes
|
||||
nx show projects --affected --uncommitted
|
||||
|
||||
# Affected by untracked files
|
||||
nx show projects --affected --untracked
|
||||
```
|
||||
|
||||
## Common Exploration Patterns
|
||||
|
||||
### "What's in this workspace?"
|
||||
|
||||
```bash
|
||||
nx show projects
|
||||
nx show projects --type app
|
||||
nx show projects --type lib
|
||||
```
|
||||
|
||||
### "How do I build/test/lint project X?"
|
||||
|
||||
```bash
|
||||
nx show project X --json | jq '.targets | keys'
|
||||
nx show project X --json | jq '.targets.build'
|
||||
```
|
||||
|
||||
### "What depends on library Y?"
|
||||
|
||||
```bash
|
||||
# Find projects that may depend on Y by searching for imports
|
||||
# (Nx doesn't have a direct "dependents" command via CLI)
|
||||
grep -r "from '@myorg/Y'" --include="*.ts" --include="*.tsx" apps/ libs/
|
||||
```
|
||||
|
||||
### "What configuration options are available?"
|
||||
|
||||
```bash
|
||||
cat node_modules/nx/schemas/nx-schema.json | jq '.properties | keys'
|
||||
cat node_modules/nx/schemas/project-schema.json | jq '.properties | keys'
|
||||
```
|
||||
|
||||
### "Why is project X affected?"
|
||||
|
||||
```bash
|
||||
# Check what files changed
|
||||
git diff --name-only main
|
||||
|
||||
# See which project owns those files
|
||||
nx show project X --json | jq '.root'
|
||||
```
|
||||
@@ -0,0 +1,478 @@
|
||||
---
|
||||
description: Polls Nx Cloud CI pipeline and self-healing status. Returns structured state when actionable. Spawned by /nx-cloud-ci-monitor command to monitor CI Attempt status.
|
||||
---
|
||||
|
||||
# CI Watcher Subagent
|
||||
|
||||
You are a CI monitoring subagent responsible for polling Nx Cloud CI Attempt status and self-healing state. You report status back to the main agent - you do NOT make apply/reject decisions.
|
||||
|
||||
## Your Responsibilities
|
||||
|
||||
1. Poll CI status using the `ci_information` MCP tool
|
||||
2. Implement exponential backoff between polls
|
||||
3. Return structured state when an actionable condition is reached
|
||||
4. Track iteration count and elapsed time
|
||||
5. Output status updates based on verbosity level
|
||||
|
||||
## Input Parameters (from Main Agent)
|
||||
|
||||
The main agent may provide these optional parameters in the prompt:
|
||||
|
||||
| Parameter | Description |
|
||||
| ------------------- | -------------------------------------------------------- |
|
||||
| `branch` | Branch to monitor (auto-detected if not provided) |
|
||||
| `expectedCommitSha` | Commit SHA that should trigger a new CI Attempt |
|
||||
| `previousCipeUrl` | CI Attempt URL before the action (to detect change) |
|
||||
| `subagentTimeout` | Polling timeout in minutes (default: 60) |
|
||||
| `verbosity` | Output level: minimal, medium, verbose (default: medium) |
|
||||
|
||||
When `expectedCommitSha` or `previousCipeUrl` is provided, you must detect whether a new CI Attempt has spawned.
|
||||
|
||||
## MCP Tool Reference
|
||||
|
||||
### `ci_information`
|
||||
|
||||
**Input:**
|
||||
|
||||
```json
|
||||
{
|
||||
"branch": "string (optional, defaults to current git branch)",
|
||||
"select": "string (optional, comma-separated field names)",
|
||||
"pageToken": "number (optional, 0-based pagination for long strings)"
|
||||
}
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
```json
|
||||
{
|
||||
"cipeStatus": "NOT_STARTED | IN_PROGRESS | SUCCEEDED | FAILED | CANCELED | TIMED_OUT",
|
||||
"cipeUrl": "string",
|
||||
"branch": "string",
|
||||
"commitSha": "string | null",
|
||||
"failedTaskIds": "string[]",
|
||||
"verifiedTaskIds": "string[]",
|
||||
"selfHealingEnabled": "boolean",
|
||||
"selfHealingStatus": "NOT_STARTED | IN_PROGRESS | COMPLETED | FAILED | NOT_EXECUTABLE | null",
|
||||
"verificationStatus": "NOT_STARTED | IN_PROGRESS | COMPLETED | FAILED | NOT_EXECUTABLE | null",
|
||||
"userAction": "NONE | APPLIED | REJECTED | APPLIED_LOCALLY | APPLIED_AUTOMATICALLY | null",
|
||||
"failureClassification": "string | null",
|
||||
"taskOutputSummary": "string | null",
|
||||
"suggestedFixReasoning": "string | null",
|
||||
"suggestedFixDescription": "string | null",
|
||||
"suggestedFix": "string | null",
|
||||
"shortLink": "string | null",
|
||||
"couldAutoApplyTasks": "boolean | null",
|
||||
"confidence": "number | null",
|
||||
"confidenceReasoning": "string | null"
|
||||
}
|
||||
```
|
||||
|
||||
**Select Parameter:**
|
||||
|
||||
| Usage | Returns |
|
||||
| --------------- | ----------------------------------------------------------- |
|
||||
| No `select` | Formatted overview (truncated, not recommended for polling) |
|
||||
| Single field | Raw value with pagination for long strings |
|
||||
| Multiple fields | Object with requested field values |
|
||||
|
||||
**Field Sets for Efficient Polling:**
|
||||
|
||||
```yaml
|
||||
WAIT_FIELDS:
|
||||
'cipeUrl,commitSha,cipeStatus'
|
||||
# Minimal fields for detecting new CI Attempt
|
||||
|
||||
LIGHT_FIELDS:
|
||||
'cipeStatus,cipeUrl,branch,commitSha,selfHealingStatus,verificationStatus,userAction,failedTaskIds,verifiedTaskIds,selfHealingEnabled,failureClassification,couldAutoApplyTasks,shortLink,confidence,confidenceReasoning'
|
||||
# Status fields for determining actionable state
|
||||
|
||||
HEAVY_FIELDS:
|
||||
'taskOutputSummary,suggestedFix,suggestedFixReasoning,suggestedFixDescription'
|
||||
# Large content fields - fetch only when returning to main agent
|
||||
```
|
||||
|
||||
## Initial Wait
|
||||
|
||||
Before first poll, wait based on context:
|
||||
|
||||
- **Fresh start (no expected CIPE):** Wait 60 seconds to allow CI to start
|
||||
- **Expecting new CIPE:** Wait 30 seconds (action already triggered)
|
||||
|
||||
**IMPORTANT:** Always run sleep in foreground, NOT as background command.
|
||||
|
||||
```bash
|
||||
sleep 60 # or 30 if expecting new CIPE (FOREGROUND, not background)
|
||||
```
|
||||
|
||||
## Two-Phase Operation
|
||||
|
||||
The subagent operates in one of two modes depending on input:
|
||||
|
||||
### Mode 1: Fresh Start (no `expectedCommitSha` or `previousCipeUrl`)
|
||||
|
||||
Normal polling - process whatever CIPE is returned by `ci_information`.
|
||||
|
||||
### Mode 2: Wait-for-New-CIPE (when `expectedCommitSha` or `previousCipeUrl` provided)
|
||||
|
||||
**CRITICAL**: When expecting a new CIPE, the subagent must **completely ignore** the old/stale CIPE. Do NOT process its status, do NOT return actionable states based on it.
|
||||
|
||||
#### Phase A: Wait Mode
|
||||
|
||||
1. Start a **new-CIPE timeout** timer (default: 30 minutes)
|
||||
2. On each poll of `ci_information`:
|
||||
- Check if CIPE is NEW:
|
||||
- `cipeUrl` differs from `previousCipeUrl` → **new CIPE detected**
|
||||
- `commitSha` matches `expectedCommitSha` → **correct CIPE detected**
|
||||
- If still OLD CIPE: **ignore all status fields**, just wait and poll again
|
||||
- Do NOT return `fix_available`, `ci_success`, etc. based on old CIPE!
|
||||
3. Output wait status (see below)
|
||||
4. If timeout (30 min) reached → return `no_new_cipe`
|
||||
|
||||
#### Phase B: Normal Polling (after new CIPE detected)
|
||||
|
||||
Once new CIPE is detected:
|
||||
|
||||
1. Clear the new-CIPE timeout
|
||||
2. Switch to normal polling mode
|
||||
3. Process the NEW CIPE's status normally
|
||||
4. Return when actionable state reached
|
||||
|
||||
### Wait Mode Output
|
||||
|
||||
While in wait mode, output clearly that you're waiting (not processing):
|
||||
|
||||
```
|
||||
[CI Monitor] ═══════════════════════════════════════════════════════
|
||||
[CI Monitor] WAIT MODE - Expecting new CI Attempt
|
||||
[CI Monitor] Expected SHA: <expectedCommitSha>
|
||||
[CI Monitor] Previous CI Attempt: <previousCipeUrl>
|
||||
[CI Monitor] ═══════════════════════════════════════════════════════
|
||||
|
||||
[CI Monitor] Polling... (elapsed: 0m 30s)
|
||||
[CI Monitor] Still seeing previous CI Attempt (ignoring): <oldCipeUrl>
|
||||
|
||||
[CI Monitor] Polling... (elapsed: 1m 30s)
|
||||
[CI Monitor] Still seeing previous CI Attempt (ignoring): <oldCipeUrl>
|
||||
|
||||
[CI Monitor] Polling... (elapsed: 2m 30s)
|
||||
[CI Monitor] ✓ New CI Attempt detected! URL: <newCipeUrl>, SHA: <newCommitSha>
|
||||
[CI Monitor] Switching to normal polling mode...
|
||||
```
|
||||
|
||||
### Why This Matters (Context Preservation)
|
||||
|
||||
**The problem**: Stale CIPE data can be very large:
|
||||
|
||||
- `taskOutputSummary`: potentially thousands of characters of build/test output
|
||||
- `suggestedFix`: entire patch files
|
||||
- `suggestedFixReasoning`: detailed explanation
|
||||
|
||||
If subagent returns stale CIPE data to main agent, it **pollutes main agent's context** with useless information (we already processed that CIPE). This wastes valuable context window.
|
||||
|
||||
**Without wait mode:**
|
||||
|
||||
1. Poll `ci_information` → get old CIPE with huge data
|
||||
2. Return to main agent with all that stale data
|
||||
3. Main agent's context gets polluted with useless info
|
||||
4. Main agent has to process/ignore it anyway
|
||||
|
||||
**With wait mode:**
|
||||
|
||||
1. Poll `ci_information` → get old CIPE → **ignore it, don't return**
|
||||
2. Keep waiting internally (stale data stays in subagent)
|
||||
3. New CIPE appears → switch to normal mode
|
||||
4. Return to main agent with only the NEW, relevant CIPE data
|
||||
|
||||
## Polling Loop
|
||||
|
||||
### Subagent State Management
|
||||
|
||||
Maintain internal accumulated state across polls:
|
||||
|
||||
```
|
||||
accumulated_state = {}
|
||||
```
|
||||
|
||||
### Call `ci_information` MCP Tool
|
||||
|
||||
**Wait Mode (expecting new CI Attempt):**
|
||||
|
||||
```
|
||||
ci_information({
|
||||
branch: "<branch_name>",
|
||||
select: "cipeUrl,commitSha,cipeStatus"
|
||||
})
|
||||
```
|
||||
|
||||
Only fetch minimal fields needed to detect CI Attempt change. Do NOT fetch heavy fields - stale data wastes context.
|
||||
|
||||
**Normal Mode (processing CI Attempt):**
|
||||
|
||||
```
|
||||
ci_information({
|
||||
branch: "<branch_name>",
|
||||
select: "cipeStatus,cipeUrl,branch,commitSha,selfHealingStatus,verificationStatus,userAction,failedTaskIds,verifiedTaskIds,selfHealingEnabled,failureClassification,couldAutoApplyTasks,shortLink,confidence,confidenceReasoning"
|
||||
})
|
||||
```
|
||||
|
||||
Merge response into `accumulated_state` after each poll.
|
||||
|
||||
### Analyze Response
|
||||
|
||||
**If in Wait Mode** (expecting new CIPE):
|
||||
|
||||
1. Check if CIPE is new (see Two-Phase Operation above)
|
||||
2. If old CIPE → **ignore status**, output wait message, poll again
|
||||
3. If new CIPE → switch to normal mode, continue below
|
||||
|
||||
**If in Normal Mode**:
|
||||
Based on the response, decide whether to **keep polling** or **return to main agent**.
|
||||
|
||||
### Keep Polling When
|
||||
|
||||
Continue polling (with backoff) if ANY of these conditions are true:
|
||||
|
||||
| Condition | Reason |
|
||||
| --------------------------------------- | ---------------------------------------- |
|
||||
| `cipeStatus == 'IN_PROGRESS'` | CI still running |
|
||||
| `cipeStatus == 'NOT_STARTED'` | CI hasn't started yet |
|
||||
| `selfHealingStatus == 'IN_PROGRESS'` | Self-healing agent working |
|
||||
| `selfHealingStatus == 'NOT_STARTED'` | Self-healing not started yet |
|
||||
| `failureClassification == 'FLAKY_TASK'` | Auto-rerun in progress |
|
||||
| `userAction == 'APPLIED_AUTOMATICALLY'` | New CI Attempt spawning after auto-apply |
|
||||
|
||||
When `couldAutoApplyTasks == true`:
|
||||
|
||||
- `verificationStatus` = `NOT_STARTED`, `IN_PROGRESS` → keep polling (verification still in progress)
|
||||
- `verificationStatus` = `COMPLETED` → return `fix_auto_applying` (auto-apply will happen, main agent spawns wait mode subagent)
|
||||
- `verificationStatus` = `FAILED`, `NOT_EXECUTABLE` → return `fix_available` (auto-apply won't happen, needs manual action)
|
||||
|
||||
### Exponential Backoff
|
||||
|
||||
Between polls, wait with exponential backoff:
|
||||
|
||||
| Poll Attempt | Wait Time |
|
||||
| ------------ | ----------------- |
|
||||
| 1st | 60 seconds |
|
||||
| 2nd | 90 seconds |
|
||||
| 3rd+ | 120 seconds (cap) |
|
||||
|
||||
Reset to 60 seconds when state changes significantly.
|
||||
|
||||
**IMPORTANT:** Run sleep in foreground (NOT as background command). Background sleep causes "What should Claude do?" prompts when completed.
|
||||
|
||||
```bash
|
||||
# Example backoff - run in FOREGROUND
|
||||
sleep 60 # First wait
|
||||
sleep 90 # Second wait
|
||||
sleep 120 # Third and subsequent waits (capped)
|
||||
```
|
||||
|
||||
### Fetch Heavy Fields on Actionable State
|
||||
|
||||
Before returning to main agent, fetch heavy fields if the status requires them:
|
||||
|
||||
| Status | Heavy Fields Needed |
|
||||
| ------------------- | ------------------------------------------------------------------------------ |
|
||||
| `ci_success` | None |
|
||||
| `fix_auto_applying` | None |
|
||||
| `fix_available` | `taskOutputSummary,suggestedFix,suggestedFixReasoning,suggestedFixDescription` |
|
||||
| `fix_failed` | `taskOutputSummary` |
|
||||
| `no_fix` | `taskOutputSummary` |
|
||||
| `environment_issue` | None |
|
||||
| `no_new_cipe` | None |
|
||||
| `polling_timeout` | None |
|
||||
| `cipe_canceled` | None |
|
||||
| `cipe_timed_out` | None |
|
||||
|
||||
```
|
||||
# Example: fetching heavy fields for fix_available
|
||||
ci_information({
|
||||
branch: "<branch_name>",
|
||||
select: "taskOutputSummary,suggestedFix,suggestedFixReasoning,suggestedFixDescription"
|
||||
})
|
||||
```
|
||||
|
||||
Merge response into `accumulated_state`, then return merged state to main agent.
|
||||
|
||||
**Pagination:** Heavy string fields return first page only. If `hasMore` indicated, include in return format so main agent knows more content available.
|
||||
|
||||
### Return to Main Agent When
|
||||
|
||||
Return immediately with structured state if ANY of these conditions are true:
|
||||
|
||||
| Status | Condition |
|
||||
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ci_success` | `cipeStatus == 'SUCCEEDED'` |
|
||||
| `fix_auto_applying` | `selfHealingStatus == 'COMPLETED'` AND `couldAutoApplyTasks == true` AND `verificationStatus == 'COMPLETED'` |
|
||||
| `fix_available` | `selfHealingStatus == 'COMPLETED'` AND `suggestedFix != null` AND (`couldAutoApplyTasks != true` OR `verificationStatus` in (`FAILED`, `NOT_EXECUTABLE`)) |
|
||||
| `fix_failed` | `selfHealingStatus == 'FAILED'` |
|
||||
| `environment_issue` | `failureClassification == 'ENVIRONMENT_STATE'` |
|
||||
| `no_fix` | `cipeStatus == 'FAILED'` AND (`selfHealingEnabled == false` OR `selfHealingStatus == 'NOT_EXECUTABLE'`) |
|
||||
| `no_new_cipe` | `expectedCommitSha` or `previousCipeUrl` provided, but no new CI Attempt detected after 30 min |
|
||||
| `polling_timeout` | Subagent has been polling for > configured timeout (default 60 min) |
|
||||
| `cipe_canceled` | `cipeStatus == 'CANCELED'` |
|
||||
| `cipe_timed_out` | `cipeStatus == 'TIMED_OUT'` |
|
||||
|
||||
## Subagent Timeout
|
||||
|
||||
Track elapsed time. If you have been polling for more than **60 minutes** (configurable via main agent), return with `status: polling_timeout`.
|
||||
|
||||
## Return Format
|
||||
|
||||
When returning to the main agent, provide a structured response with accumulated state:
|
||||
|
||||
```
|
||||
## CI Monitor Result
|
||||
|
||||
**Status:** <status>
|
||||
**Iterations:** <count>
|
||||
**Elapsed:** <minutes>m <seconds>s
|
||||
|
||||
### CI Attempt Details
|
||||
- **Status:** <cipeStatus>
|
||||
- **URL:** <cipeUrl>
|
||||
- **Branch:** <branch>
|
||||
- **Commit:** <commitSha>
|
||||
- **Failed Tasks:** <failedTaskIds>
|
||||
- **Verified Tasks:** <verifiedTaskIds>
|
||||
|
||||
### Self-Healing Details
|
||||
- **Enabled:** <selfHealingEnabled>
|
||||
- **Status:** <selfHealingStatus>
|
||||
- **Verification:** <verificationStatus>
|
||||
- **User Action:** <userAction>
|
||||
- **Classification:** <failureClassification>
|
||||
- **Confidence:** <confidence>
|
||||
- **Confidence Reasoning:** <confidenceReasoning>
|
||||
|
||||
### Fix Information (if available)
|
||||
- **Short Link:** <shortLink>
|
||||
- **Description:** <suggestedFixDescription>
|
||||
- **Reasoning:** <suggestedFixReasoning>
|
||||
|
||||
### Task Output Summary (first page)
|
||||
<taskOutputSummary>
|
||||
[MORE_CONTENT_AVAILABLE: taskOutputSummary, pageToken: 1]
|
||||
|
||||
### Suggested Fix (first page)
|
||||
<suggestedFix>
|
||||
[MORE_CONTENT_AVAILABLE: suggestedFix, pageToken: 1]
|
||||
```
|
||||
|
||||
### Pagination Indicators
|
||||
|
||||
When a heavy field has more content available, append indicator:
|
||||
|
||||
```
|
||||
[MORE_CONTENT_AVAILABLE: <fieldName>, pageToken: <nextPage>]
|
||||
```
|
||||
|
||||
Main agent can fetch additional pages if needed using:
|
||||
|
||||
```
|
||||
ci_information({ select: "<fieldName>", pageToken: <nextPage> })
|
||||
```
|
||||
|
||||
Fields that may have pagination:
|
||||
|
||||
- `taskOutputSummary` (reverse pagination - page 0 = most recent)
|
||||
- `suggestedFix` (forward pagination - page 0 = start)
|
||||
- `suggestedFixReasoning`
|
||||
|
||||
### Return Format for `no_new_cipe`
|
||||
|
||||
When returning with `status: no_new_cipe`, include additional context:
|
||||
|
||||
```
|
||||
## CI Monitor Result
|
||||
|
||||
**Status:** no_new_cipe
|
||||
**Iterations:** <count>
|
||||
**Elapsed:** <minutes>m <seconds>s
|
||||
|
||||
### Expected CI Attempt Not Found
|
||||
- **Expected Commit SHA:** <expectedCommitSha>
|
||||
- **Previous CI Attempt URL:** <previousCipeUrl>
|
||||
- **Last Seen CI Attempt URL:** <cipeUrl>
|
||||
- **Last Seen Commit SHA:** <commitSha>
|
||||
- **New CI Attempt Timeout:** 30 minutes (exceeded)
|
||||
|
||||
### Likely Cause
|
||||
CI workflow failed before Nx tasks could run (e.g., install step, checkout, auth).
|
||||
Check your CI provider logs for the commit <expectedCommitSha>.
|
||||
|
||||
### Last Known CI Attempt State
|
||||
- **Status:** <cipeStatus>
|
||||
- **Branch:** <branch>
|
||||
```
|
||||
|
||||
## Status Reporting (Verbosity-Controlled)
|
||||
|
||||
Output is controlled by the `verbosity` parameter from the main agent:
|
||||
|
||||
| Level | What to Output |
|
||||
| --------- | ----------------------------------------------------------------- |
|
||||
| `minimal` | No intermediate output. Only return final result when actionable. |
|
||||
| `medium` | Output only on significant state changes (not every poll). |
|
||||
| `verbose` | Output detailed phase information after every poll. |
|
||||
|
||||
### Minimal Verbosity
|
||||
|
||||
No output during polling. Poll silently and return when done.
|
||||
|
||||
### Medium Verbosity (Default)
|
||||
|
||||
Output **only when state changes significantly** to save context tokens:
|
||||
|
||||
- `cipeStatus` changes (e.g., IN_PROGRESS → FAILED)
|
||||
- `selfHealingStatus` changes (e.g., IN_PROGRESS → COMPLETED)
|
||||
- New CI Attempt detected (in wait mode)
|
||||
|
||||
Format: single line, no decorators:
|
||||
|
||||
```
|
||||
[CI Monitor] CI: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 4m
|
||||
```
|
||||
|
||||
### Verbose Verbosity
|
||||
|
||||
Output detailed phase box after every poll:
|
||||
|
||||
```
|
||||
[CI Monitor] ─────────────────────────────────────────────────────
|
||||
[CI Monitor] Iteration <N> | Elapsed: <X>m <Y>s
|
||||
[CI Monitor]
|
||||
[CI Monitor] CI Status: <cipeStatus>
|
||||
[CI Monitor] Self-Healing: <selfHealingStatus>
|
||||
[CI Monitor] Verification: <verificationStatus>
|
||||
[CI Monitor] Classification: <failureClassification>
|
||||
[CI Monitor]
|
||||
[CI Monitor] → <human-readable phase description>
|
||||
[CI Monitor] ─────────────────────────────────────────────────────
|
||||
```
|
||||
|
||||
### Phase Descriptions (for verbose output)
|
||||
|
||||
| Status Combo | Description |
|
||||
| ----------------------------------------------------------------------------------------- | ------------------------------------------- |
|
||||
| `cipeStatus: IN_PROGRESS` | "CI running..." |
|
||||
| `cipeStatus: NOT_STARTED` | "Waiting for CI to start..." |
|
||||
| `cipeStatus: FAILED` + `selfHealingStatus: NOT_STARTED` | "CI failed. Self-healing starting..." |
|
||||
| `cipeStatus: FAILED` + `selfHealingStatus: IN_PROGRESS` | "CI failed. Self-healing generating fix..." |
|
||||
| `cipeStatus: FAILED` + `selfHealingStatus: COMPLETED` + `verificationStatus: IN_PROGRESS` | "Fix generated! Verification running..." |
|
||||
| `cipeStatus: FAILED` + `selfHealingStatus: COMPLETED` + `verificationStatus: COMPLETED` | "Fix ready! Verified successfully." |
|
||||
| `cipeStatus: FAILED` + `selfHealingStatus: COMPLETED` + `verificationStatus: FAILED` | "Fix generated but verification failed." |
|
||||
| `cipeStatus: FAILED` + `selfHealingStatus: FAILED` | "Self-healing could not generate a fix." |
|
||||
| `cipeStatus: SUCCEEDED` | "CI passed!" |
|
||||
|
||||
## Important Notes
|
||||
|
||||
- You do NOT make apply/reject decisions - that's the main agent's job
|
||||
- You do NOT perform git operations
|
||||
- You only poll and report state
|
||||
- Respect the `verbosity` parameter for output (default: medium)
|
||||
- If `ci_information` returns an error, wait and retry (count as failed poll)
|
||||
- Track consecutive failures - if 5 consecutive failures, return with `status: error`
|
||||
- When expecting new CI Attempt, track the 30-minute new-CI-Attempt timeout separately from the main polling timeout
|
||||
@@ -0,0 +1,18 @@
|
||||
# This configuration is here to prevent false positive alerts for __fixtures__.
|
||||
# We are intentionally disabling the PR opening feature.
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: 'npm'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
open-pull-requests-limit: 0
|
||||
exclude-paths:
|
||||
- '**/__fixtures__/**'
|
||||
|
||||
- package-ecosystem: 'github-actions'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
open-pull-requests-limit: 0
|
||||
@@ -0,0 +1,437 @@
|
||||
---
|
||||
description: Monitor Nx Cloud CI pipeline and handle self-healing fixes automatically. Checks for Nx Cloud connection before starting.
|
||||
argument-hint: '[instructions] [--max-cycles N] [--timeout MINUTES] [--verbosity minimal|medium|verbose] [--branch BRANCH] [--fresh] [--auto-fix-workflow] [--new-cipe-timeout MINUTES]'
|
||||
---
|
||||
|
||||
# CI Monitor Command
|
||||
|
||||
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
|
||||
|
||||
## Context
|
||||
|
||||
- **Current Branch:** !`git branch --show-current`
|
||||
- **Current Commit:** !`git rev-parse --short HEAD`
|
||||
- **Remote Status:** !`git status -sb | head -1`
|
||||
|
||||
## User Instructions
|
||||
|
||||
${input:args}
|
||||
|
||||
**Important:** If user provides specific instructions, respect them over default behaviors described below.
|
||||
|
||||
## Configuration Defaults
|
||||
|
||||
| Setting | Default | Description |
|
||||
| ------------------------- | ------------- | ------------------------------------------------------------------- |
|
||||
| `--max-cycles` | 10 | Maximum CIPE cycles before timeout |
|
||||
| `--timeout` | 120 | Maximum duration in minutes |
|
||||
| `--verbosity` | medium | Output level: minimal, medium, verbose |
|
||||
| `--branch` | (auto-detect) | Branch to monitor |
|
||||
| `--subagent-timeout` | 60 | Subagent polling timeout in minutes |
|
||||
| `--fresh` | false | Ignore previous context, start fresh |
|
||||
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
|
||||
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
|
||||
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
|
||||
|
||||
Parse any overrides from `${input:args}` and merge with defaults.
|
||||
|
||||
## Nx Cloud Connection Check
|
||||
|
||||
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
|
||||
|
||||
### Step 0: Verify Nx Cloud Connection
|
||||
|
||||
1. **Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
|
||||
2. **If `nx.json` missing OR neither property exists** → exit with:
|
||||
```
|
||||
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
|
||||
```
|
||||
3. **If connected** → continue to main loop
|
||||
|
||||
## Session Context Behavior
|
||||
|
||||
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
|
||||
|
||||
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
|
||||
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
|
||||
- **For a completely clean slate:** Exit Claude Code and restart `claude`
|
||||
|
||||
## Default Behaviors by Status
|
||||
|
||||
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
|
||||
|
||||
| Status | Default Behavior |
|
||||
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ci_success` | Exit with success. Log "CI passed successfully!" |
|
||||
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
|
||||
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
|
||||
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
|
||||
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
|
||||
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
|
||||
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
|
||||
| `polling_timeout` | Subagent polling timeout reached. Exit with timeout. |
|
||||
| `cipe_canceled` | CIPE was canceled. Exit with canceled status. |
|
||||
| `cipe_timed_out` | CIPE timed out. Exit with timeout status. |
|
||||
| `error` | Increment `no_progress_count`. If >= 3 → exit with circuit breaker. Otherwise wait 60s and loop. |
|
||||
|
||||
### Fix Available Decision Logic
|
||||
|
||||
When subagent returns `fix_available`, main agent compares `failedTaskIds` vs `verifiedTaskIds`:
|
||||
|
||||
#### Step 1: Categorize Tasks
|
||||
|
||||
1. **Verified tasks** = tasks in both `failedTaskIds` AND `verifiedTaskIds`
|
||||
2. **Unverified tasks** = tasks in `failedTaskIds` but NOT in `verifiedTaskIds`
|
||||
3. **E2E tasks** = unverified tasks where target contains "e2e" (task format: `<project>:<target>` or `<project>:<target>:<config>`)
|
||||
4. **Verifiable tasks** = unverified tasks that are NOT e2e
|
||||
|
||||
#### Step 2: Determine Path
|
||||
|
||||
| Condition | Path |
|
||||
| --------------------------------------- | ---------------------------------------- |
|
||||
| No unverified tasks (all verified) | Apply via MCP |
|
||||
| Unverified tasks exist, but ALL are e2e | Apply via MCP (treat as verified enough) |
|
||||
| Verifiable tasks exist | Local verification flow |
|
||||
|
||||
#### Step 3a: Apply via MCP (fully/e2e-only verified)
|
||||
|
||||
- Call `update_self_healing_fix({ shortLink, action: "APPLY" })`
|
||||
- Record `last_cipe_url`, spawn subagent in wait mode
|
||||
|
||||
#### Step 3b: Local Verification Flow
|
||||
|
||||
When verifiable (non-e2e) unverified tasks exist:
|
||||
|
||||
1. **Detect package manager:**
|
||||
- `pnpm-lock.yaml` exists → `pnpm nx`
|
||||
- `yarn.lock` exists → `yarn nx`
|
||||
- Otherwise → `npx nx`
|
||||
|
||||
2. **Run verifiable tasks in parallel:**
|
||||
- Spawn `general` subagents to run each task concurrently
|
||||
- Each subagent runs: `<pm> nx run <taskId>`
|
||||
- Collect pass/fail results from all subagents
|
||||
|
||||
3. **Evaluate results:**
|
||||
|
||||
| Result | Action |
|
||||
| ------------------------- | ---------------------------- |
|
||||
| ALL verifiable tasks pass | Apply via MCP |
|
||||
| ANY verifiable task fails | Apply-locally + enhance flow |
|
||||
|
||||
4. **Apply-locally + enhance flow:**
|
||||
- Run `nx apply-locally <shortLink>`
|
||||
- Enhance the code to fix failing tasks
|
||||
- Run failing tasks again to verify fix
|
||||
- If still failing → increment `local_verify_count`, loop back to enhance
|
||||
- If passing → commit and push, record `expected_commit_sha`, spawn subagent in wait mode
|
||||
|
||||
5. **Track attempts** (wraps step 4):
|
||||
- Increment `local_verify_count` after each enhance cycle
|
||||
- If `local_verify_count >= local_verify_attempts` (default: 3):
|
||||
- Get code in commit-able state
|
||||
- Commit and push with message indicating local verification failed
|
||||
- Report to user:
|
||||
```
|
||||
[ci-monitor] Local verification failed after <N> attempts. Pushed to CI for final validation. Failed: <taskIds>
|
||||
```
|
||||
- Record `expected_commit_sha`, spawn subagent in wait mode (let CI be final judge)
|
||||
|
||||
#### Commit Message Format
|
||||
|
||||
```bash
|
||||
git commit -m "fix(<projects>): <brief description>
|
||||
|
||||
Failed tasks: <taskId1>, <taskId2>
|
||||
Local verification: passed|enhanced|failed-pushing-to-ci"
|
||||
```
|
||||
|
||||
### Unverified Fix Flow (No Verification Attempted)
|
||||
|
||||
When `verificationStatus` is `FAILED`, `NOT_EXECUTABLE`, or fix has `couldAutoApplyTasks != true` with no verification:
|
||||
|
||||
- Analyze fix content (`suggestedFix`, `suggestedFixReasoning`, `taskOutputSummary`)
|
||||
- If fix looks correct → apply via MCP
|
||||
- If fix needs enhancement → use Apply Locally + Enhance Flow above
|
||||
- If fix is wrong → reject via MCP, fix from scratch, commit, push
|
||||
|
||||
### Auto-Apply Eligibility
|
||||
|
||||
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
|
||||
|
||||
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
|
||||
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
|
||||
|
||||
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
|
||||
|
||||
### Apply vs Reject vs Apply Locally
|
||||
|
||||
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
|
||||
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
|
||||
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
|
||||
|
||||
### Apply Locally + Enhance Flow
|
||||
|
||||
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
|
||||
|
||||
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
|
||||
2. Make additional changes as needed
|
||||
3. Commit and push:
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix: resolve <failedTaskIds>"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
4. Loop to poll for new CIPE
|
||||
|
||||
### Reject + Fix From Scratch Flow
|
||||
|
||||
When the fix is completely wrong:
|
||||
|
||||
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
|
||||
2. Fix the issue from scratch locally
|
||||
3. Commit and push:
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix: resolve <failedTaskIds>"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
4. Loop to poll for new CIPE
|
||||
|
||||
### Environment Issue Handling
|
||||
|
||||
When `failureClassification == 'ENVIRONMENT_STATE'`:
|
||||
|
||||
1. Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`
|
||||
2. New CIPE spawns automatically (no local git operations needed)
|
||||
3. Loop to poll for new CIPE with `previousCipeUrl` set
|
||||
|
||||
### No-New-CIPE Handling
|
||||
|
||||
When `status == 'no_new_cipe'`:
|
||||
|
||||
This means the expected CIPE was never created - CI likely failed before Nx tasks could run.
|
||||
|
||||
1. **Report to user:**
|
||||
|
||||
```
|
||||
[ci-monitor] No CI attempt for <sha> after 10 min. Check CI provider for pre-Nx failures (install, checkout, auth). Last CI attempt: <previousCipeUrl>
|
||||
```
|
||||
|
||||
2. **If user configured auto-fix attempts** (e.g., `--auto-fix-workflow`):
|
||||
- Detect package manager: check for `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`
|
||||
- Run install to update lockfile:
|
||||
```bash
|
||||
pnpm install # or npm install / yarn install
|
||||
```
|
||||
- If lockfile changed:
|
||||
```bash
|
||||
git add pnpm-lock.yaml # or appropriate lockfile
|
||||
git commit -m "chore: update lockfile"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
- Record new commit SHA, loop to poll with `expectedCommitSha`
|
||||
|
||||
3. **Otherwise:** Exit with `no_new_cipe` status, providing guidance for user to investigate
|
||||
|
||||
## Exit Conditions
|
||||
|
||||
Exit the monitoring loop when ANY of these conditions are met:
|
||||
|
||||
| Condition | Exit Type |
|
||||
| ------------------------------------------- | ---------------- |
|
||||
| CI passes (`cipeStatus == 'SUCCEEDED'`) | Success |
|
||||
| Max CIPE cycles reached | Timeout |
|
||||
| Max duration reached | Timeout |
|
||||
| 3 consecutive no-progress iterations | Circuit breaker |
|
||||
| No fix available and local fix not possible | Failure |
|
||||
| No new CIPE and auto-fix not configured | Pre-CIPE failure |
|
||||
| User cancels | Cancelled |
|
||||
|
||||
## Main Loop
|
||||
|
||||
### Step 1: Initialize Tracking
|
||||
|
||||
```
|
||||
cycle_count = 0
|
||||
start_time = now()
|
||||
no_progress_count = 0
|
||||
local_verify_count = 0
|
||||
last_state = null
|
||||
last_cipe_url = null
|
||||
expected_commit_sha = null
|
||||
```
|
||||
|
||||
### Step 2: Spawn Subagent
|
||||
|
||||
Spawn the `ci-watcher` subagent to poll CI status:
|
||||
|
||||
**Fresh start (first spawn, no expected CIPE):**
|
||||
|
||||
```
|
||||
Task(
|
||||
agent: "ci-watcher",
|
||||
prompt: "Monitor CI for branch '<branch>'.
|
||||
Subagent timeout: <subagent-timeout> minutes.
|
||||
New-CIPE timeout: <new-cipe-timeout> minutes.
|
||||
Verbosity: <verbosity>."
|
||||
)
|
||||
```
|
||||
|
||||
**After action that triggers new CIPE (wait mode):**
|
||||
|
||||
```
|
||||
Task(
|
||||
agent: "ci-watcher",
|
||||
prompt: "Monitor CI for branch '<branch>'.
|
||||
Subagent timeout: <subagent-timeout> minutes.
|
||||
New-CIPE timeout: <new-cipe-timeout> minutes.
|
||||
Verbosity: <verbosity>.
|
||||
|
||||
WAIT MODE: A new CIPE should spawn. Ignore old CIPE until new one appears.
|
||||
Expected commit SHA: <expected_commit_sha>
|
||||
Previous CIPE URL: <last_cipe_url>"
|
||||
)
|
||||
```
|
||||
|
||||
### Step 3: Handle Subagent Response
|
||||
|
||||
When subagent returns:
|
||||
|
||||
1. Check the returned status
|
||||
2. Look up default behavior in the table above
|
||||
3. Check if user instructions override the default
|
||||
4. Execute the appropriate action
|
||||
5. **If action expects new CIPE**, update tracking (see Step 3a)
|
||||
6. If action results in looping, go to Step 2
|
||||
|
||||
### Step 3a: Track State for New-CIPE Detection
|
||||
|
||||
After actions that should trigger a new CIPE, record state before looping:
|
||||
|
||||
| Action | What to Track | Subagent Mode |
|
||||
| ----------------------------- | --------------------------------------------- | ------------- |
|
||||
| Fix auto-applying | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| Apply via MCP | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| Apply locally + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Reject + fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Fix failed + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| No fix + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Environment rerun | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| No-new-CIPE + auto-fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
|
||||
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
|
||||
|
||||
- Subagent will **completely ignore** the old/stale CIPE
|
||||
- Subagent will only wait for new CIPE to appear
|
||||
- Subagent will NOT return to main agent with stale CIPE data
|
||||
- Once new CIPE detected, subagent switches to normal polling
|
||||
|
||||
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
|
||||
|
||||
### Step 4: Progress Tracking
|
||||
|
||||
After each action:
|
||||
|
||||
- If state changed significantly → reset `no_progress_count = 0`
|
||||
- If state unchanged → `no_progress_count++`
|
||||
- On new CI attempt detected → reset `local_verify_count = 0`
|
||||
|
||||
## Status Reporting
|
||||
|
||||
Based on verbosity level:
|
||||
|
||||
| Level | What to Report |
|
||||
| --------- | -------------------------------------------------------------------------- |
|
||||
| `minimal` | Only final result (success/failure/timeout) |
|
||||
| `medium` | State changes + periodic updates ("Cycle N \| Elapsed: Xm \| Status: ...") |
|
||||
| `verbose` | All of medium + full subagent responses, git outputs, MCP responses |
|
||||
|
||||
## User Instruction Examples
|
||||
|
||||
Users can override default behaviors:
|
||||
|
||||
| Instruction | Effect |
|
||||
| ------------------------------------------------ | --------------------------------------------- |
|
||||
| "never auto-apply" | Always prompt before applying any fix |
|
||||
| "always ask before git push" | Prompt before each push |
|
||||
| "reject any fix for e2e tasks" | Auto-reject if `failedTaskIds` contains e2e |
|
||||
| "apply all fixes regardless of verification" | Skip verification check, apply everything |
|
||||
| "if confidence < 70, reject" | Check confidence field before applying |
|
||||
| "run 'nx affected -t typecheck' before applying" | Add local verification step |
|
||||
| "auto-fix workflow failures" | Attempt lockfile updates on pre-CIPE failures |
|
||||
| "wait 45 min for new CIPE" | Override new-CIPE timeout (default: 10 min) |
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | Action |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------- |
|
||||
| Git rebase conflict | Report to user, exit |
|
||||
| `nx apply-locally` fails | Report to user, attempt manual patch or exit |
|
||||
| MCP tool error | Retry once, if fails report to user |
|
||||
| Subagent spawn failure | Retry once, if fails exit with error |
|
||||
| No new CIPE detected | If `--auto-fix-workflow`, try lockfile update; otherwise report to user with guidance |
|
||||
| Lockfile auto-fix fails | Report to user, exit with guidance to check CI logs |
|
||||
|
||||
## Example Session
|
||||
|
||||
### Example 1: Normal Flow with Self-Healing (medium verbosity)
|
||||
|
||||
```
|
||||
[ci-monitor] Starting CI monitor for branch 'feature/add-auth'
|
||||
[ci-monitor] Config: max-cycles=5, timeout=120m, verbosity=medium
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] CI attempt: IN_PROGRESS | Self-Healing: NOT_STARTED | Elapsed: 1m
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 3m
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 5m
|
||||
|
||||
[ci-monitor] Fix available! Verification: COMPLETED
|
||||
[ci-monitor] Applying fix via MCP...
|
||||
[ci-monitor] Fix applied in CI. Waiting for new CI attempt...
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] New CI attempt detected!
|
||||
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 8m
|
||||
|
||||
[ci-monitor] CI passed successfully!
|
||||
|
||||
[ci-monitor] Summary:
|
||||
- Total cycles: 2
|
||||
- Total time: 12m 34s
|
||||
- Fixes applied: 1
|
||||
- Result: SUCCESS
|
||||
```
|
||||
|
||||
### Example 2: Pre-CI Failure (medium verbosity)
|
||||
|
||||
```
|
||||
[ci-monitor] Starting CI monitor for branch 'feature/add-products'
|
||||
[ci-monitor] Config: max-cycles=5, timeout=120m, auto-fix-workflow=true
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 2m
|
||||
|
||||
[ci-monitor] Applying fix locally, enhancing, and pushing...
|
||||
[ci-monitor] Committed: abc1234
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] Waiting for new CI attempt... (expected SHA: abc1234)
|
||||
[CI Monitor] ⚠️ CI attempt timeout (10 min). Returning no_new_cipe.
|
||||
|
||||
[ci-monitor] Status: no_new_cipe
|
||||
[ci-monitor] --auto-fix-workflow enabled. Attempting lockfile update...
|
||||
[ci-monitor] Lockfile updated. Committed: def5678
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] New CI attempt detected!
|
||||
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 18m
|
||||
|
||||
[ci-monitor] CI passed successfully!
|
||||
|
||||
[ci-monitor] Summary:
|
||||
- Total cycles: 3
|
||||
- Total time: 22m 15s
|
||||
- Fixes applied: 1 (self-healing) + 1 (lockfile)
|
||||
- Result: SUCCESS
|
||||
```
|
||||
@@ -0,0 +1,437 @@
|
||||
---
|
||||
name: ci-monitor
|
||||
description: Monitor Nx Cloud CI pipeline and handle self-healing fixes automatically. Checks for Nx Cloud connection before starting.
|
||||
---
|
||||
|
||||
# CI Monitor Command
|
||||
|
||||
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
|
||||
|
||||
## Context
|
||||
|
||||
- **Current Branch:** !`git branch --show-current`
|
||||
- **Current Commit:** !`git rev-parse --short HEAD`
|
||||
- **Remote Status:** !`git status -sb | head -1`
|
||||
|
||||
## User Instructions
|
||||
|
||||
$ARGUMENTS
|
||||
|
||||
**Important:** If user provides specific instructions, respect them over default behaviors described below.
|
||||
|
||||
## Configuration Defaults
|
||||
|
||||
| Setting | Default | Description |
|
||||
| ------------------------- | ------------- | ------------------------------------------------------------------- |
|
||||
| `--max-cycles` | 10 | Maximum CIPE cycles before timeout |
|
||||
| `--timeout` | 120 | Maximum duration in minutes |
|
||||
| `--verbosity` | medium | Output level: minimal, medium, verbose |
|
||||
| `--branch` | (auto-detect) | Branch to monitor |
|
||||
| `--subagent-timeout` | 60 | Subagent polling timeout in minutes |
|
||||
| `--fresh` | false | Ignore previous context, start fresh |
|
||||
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
|
||||
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
|
||||
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
|
||||
|
||||
Parse any overrides from `$ARGUMENTS` and merge with defaults.
|
||||
|
||||
## Nx Cloud Connection Check
|
||||
|
||||
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
|
||||
|
||||
### Step 0: Verify Nx Cloud Connection
|
||||
|
||||
1. **Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
|
||||
2. **If `nx.json` missing OR neither property exists** → exit with:
|
||||
```
|
||||
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
|
||||
```
|
||||
3. **If connected** → continue to main loop
|
||||
|
||||
## Session Context Behavior
|
||||
|
||||
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
|
||||
|
||||
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
|
||||
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
|
||||
- **For a completely clean slate:** Exit Claude Code and restart `claude`
|
||||
|
||||
## Default Behaviors by Status
|
||||
|
||||
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
|
||||
|
||||
| Status | Default Behavior |
|
||||
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ci_success` | Exit with success. Log "CI passed successfully!" |
|
||||
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
|
||||
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
|
||||
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
|
||||
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
|
||||
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
|
||||
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
|
||||
| `polling_timeout` | Subagent polling timeout reached. Exit with timeout. |
|
||||
| `cipe_canceled` | CIPE was canceled. Exit with canceled status. |
|
||||
| `cipe_timed_out` | CIPE timed out. Exit with timeout status. |
|
||||
| `error` | Increment `no_progress_count`. If >= 3 → exit with circuit breaker. Otherwise wait 60s and loop. |
|
||||
|
||||
### Fix Available Decision Logic
|
||||
|
||||
When subagent returns `fix_available`, main agent compares `failedTaskIds` vs `verifiedTaskIds`:
|
||||
|
||||
#### Step 1: Categorize Tasks
|
||||
|
||||
1. **Verified tasks** = tasks in both `failedTaskIds` AND `verifiedTaskIds`
|
||||
2. **Unverified tasks** = tasks in `failedTaskIds` but NOT in `verifiedTaskIds`
|
||||
3. **E2E tasks** = unverified tasks where target contains "e2e" (task format: `<project>:<target>` or `<project>:<target>:<config>`)
|
||||
4. **Verifiable tasks** = unverified tasks that are NOT e2e
|
||||
|
||||
#### Step 2: Determine Path
|
||||
|
||||
| Condition | Path |
|
||||
| --------------------------------------- | ---------------------------------------- |
|
||||
| No unverified tasks (all verified) | Apply via MCP |
|
||||
| Unverified tasks exist, but ALL are e2e | Apply via MCP (treat as verified enough) |
|
||||
| Verifiable tasks exist | Local verification flow |
|
||||
|
||||
#### Step 3a: Apply via MCP (fully/e2e-only verified)
|
||||
|
||||
- Call `update_self_healing_fix({ shortLink, action: "APPLY" })`
|
||||
- Record `last_cipe_url`, spawn subagent in wait mode
|
||||
|
||||
#### Step 3b: Local Verification Flow
|
||||
|
||||
When verifiable (non-e2e) unverified tasks exist:
|
||||
|
||||
1. **Detect package manager:**
|
||||
- `pnpm-lock.yaml` exists → `pnpm nx`
|
||||
- `yarn.lock` exists → `yarn nx`
|
||||
- Otherwise → `npx nx`
|
||||
|
||||
2. **Run verifiable tasks in parallel:**
|
||||
- Spawn `general` subagents to run each task concurrently
|
||||
- Each subagent runs: `<pm> nx run <taskId>`
|
||||
- Collect pass/fail results from all subagents
|
||||
|
||||
3. **Evaluate results:**
|
||||
|
||||
| Result | Action |
|
||||
| ------------------------- | ---------------------------- |
|
||||
| ALL verifiable tasks pass | Apply via MCP |
|
||||
| ANY verifiable task fails | Apply-locally + enhance flow |
|
||||
|
||||
4. **Apply-locally + enhance flow:**
|
||||
- Run `nx apply-locally <shortLink>`
|
||||
- Enhance the code to fix failing tasks
|
||||
- Run failing tasks again to verify fix
|
||||
- If still failing → increment `local_verify_count`, loop back to enhance
|
||||
- If passing → commit and push, record `expected_commit_sha`, spawn subagent in wait mode
|
||||
|
||||
5. **Track attempts** (wraps step 4):
|
||||
- Increment `local_verify_count` after each enhance cycle
|
||||
- If `local_verify_count >= local_verify_attempts` (default: 3):
|
||||
- Get code in commit-able state
|
||||
- Commit and push with message indicating local verification failed
|
||||
- Report to user:
|
||||
```
|
||||
[ci-monitor] Local verification failed after <N> attempts. Pushed to CI for final validation. Failed: <taskIds>
|
||||
```
|
||||
- Record `expected_commit_sha`, spawn subagent in wait mode (let CI be final judge)
|
||||
|
||||
#### Commit Message Format
|
||||
|
||||
```bash
|
||||
git commit -m "fix(<projects>): <brief description>
|
||||
|
||||
Failed tasks: <taskId1>, <taskId2>
|
||||
Local verification: passed|enhanced|failed-pushing-to-ci"
|
||||
```
|
||||
|
||||
### Unverified Fix Flow (No Verification Attempted)
|
||||
|
||||
When `verificationStatus` is `FAILED`, `NOT_EXECUTABLE`, or fix has `couldAutoApplyTasks != true` with no verification:
|
||||
|
||||
- Analyze fix content (`suggestedFix`, `suggestedFixReasoning`, `taskOutputSummary`)
|
||||
- If fix looks correct → apply via MCP
|
||||
- If fix needs enhancement → use Apply Locally + Enhance Flow above
|
||||
- If fix is wrong → reject via MCP, fix from scratch, commit, push
|
||||
|
||||
### Auto-Apply Eligibility
|
||||
|
||||
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
|
||||
|
||||
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
|
||||
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
|
||||
|
||||
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
|
||||
|
||||
### Apply vs Reject vs Apply Locally
|
||||
|
||||
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
|
||||
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
|
||||
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
|
||||
|
||||
### Apply Locally + Enhance Flow
|
||||
|
||||
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
|
||||
|
||||
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
|
||||
2. Make additional changes as needed
|
||||
3. Commit and push:
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix: resolve <failedTaskIds>"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
4. Loop to poll for new CIPE
|
||||
|
||||
### Reject + Fix From Scratch Flow
|
||||
|
||||
When the fix is completely wrong:
|
||||
|
||||
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
|
||||
2. Fix the issue from scratch locally
|
||||
3. Commit and push:
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix: resolve <failedTaskIds>"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
4. Loop to poll for new CIPE
|
||||
|
||||
### Environment Issue Handling
|
||||
|
||||
When `failureClassification == 'ENVIRONMENT_STATE'`:
|
||||
|
||||
1. Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`
|
||||
2. New CIPE spawns automatically (no local git operations needed)
|
||||
3. Loop to poll for new CIPE with `previousCipeUrl` set
|
||||
|
||||
### No-New-CIPE Handling
|
||||
|
||||
When `status == 'no_new_cipe'`:
|
||||
|
||||
This means the expected CIPE was never created - CI likely failed before Nx tasks could run.
|
||||
|
||||
1. **Report to user:**
|
||||
|
||||
```
|
||||
[ci-monitor] No CI attempt for <sha> after 10 min. Check CI provider for pre-Nx failures (install, checkout, auth). Last CI attempt: <previousCipeUrl>
|
||||
```
|
||||
|
||||
2. **If user configured auto-fix attempts** (e.g., `--auto-fix-workflow`):
|
||||
- Detect package manager: check for `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`
|
||||
- Run install to update lockfile:
|
||||
```bash
|
||||
pnpm install # or npm install / yarn install
|
||||
```
|
||||
- If lockfile changed:
|
||||
```bash
|
||||
git add pnpm-lock.yaml # or appropriate lockfile
|
||||
git commit -m "chore: update lockfile"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
- Record new commit SHA, loop to poll with `expectedCommitSha`
|
||||
|
||||
3. **Otherwise:** Exit with `no_new_cipe` status, providing guidance for user to investigate
|
||||
|
||||
## Exit Conditions
|
||||
|
||||
Exit the monitoring loop when ANY of these conditions are met:
|
||||
|
||||
| Condition | Exit Type |
|
||||
| ------------------------------------------- | ---------------- |
|
||||
| CI passes (`cipeStatus == 'SUCCEEDED'`) | Success |
|
||||
| Max CIPE cycles reached | Timeout |
|
||||
| Max duration reached | Timeout |
|
||||
| 3 consecutive no-progress iterations | Circuit breaker |
|
||||
| No fix available and local fix not possible | Failure |
|
||||
| No new CIPE and auto-fix not configured | Pre-CIPE failure |
|
||||
| User cancels | Cancelled |
|
||||
|
||||
## Main Loop
|
||||
|
||||
### Step 1: Initialize Tracking
|
||||
|
||||
```
|
||||
cycle_count = 0
|
||||
start_time = now()
|
||||
no_progress_count = 0
|
||||
local_verify_count = 0
|
||||
last_state = null
|
||||
last_cipe_url = null
|
||||
expected_commit_sha = null
|
||||
```
|
||||
|
||||
### Step 2: Spawn Subagent
|
||||
|
||||
Spawn the `ci-watcher` subagent to poll CI status:
|
||||
|
||||
**Fresh start (first spawn, no expected CIPE):**
|
||||
|
||||
```
|
||||
Task(
|
||||
agent: "ci-watcher",
|
||||
prompt: "Monitor CI for branch '<branch>'.
|
||||
Subagent timeout: <subagent-timeout> minutes.
|
||||
New-CIPE timeout: <new-cipe-timeout> minutes.
|
||||
Verbosity: <verbosity>."
|
||||
)
|
||||
```
|
||||
|
||||
**After action that triggers new CIPE (wait mode):**
|
||||
|
||||
```
|
||||
Task(
|
||||
agent: "ci-watcher",
|
||||
prompt: "Monitor CI for branch '<branch>'.
|
||||
Subagent timeout: <subagent-timeout> minutes.
|
||||
New-CIPE timeout: <new-cipe-timeout> minutes.
|
||||
Verbosity: <verbosity>.
|
||||
|
||||
WAIT MODE: A new CIPE should spawn. Ignore old CIPE until new one appears.
|
||||
Expected commit SHA: <expected_commit_sha>
|
||||
Previous CIPE URL: <last_cipe_url>"
|
||||
)
|
||||
```
|
||||
|
||||
### Step 3: Handle Subagent Response
|
||||
|
||||
When subagent returns:
|
||||
|
||||
1. Check the returned status
|
||||
2. Look up default behavior in the table above
|
||||
3. Check if user instructions override the default
|
||||
4. Execute the appropriate action
|
||||
5. **If action expects new CIPE**, update tracking (see Step 3a)
|
||||
6. If action results in looping, go to Step 2
|
||||
|
||||
### Step 3a: Track State for New-CIPE Detection
|
||||
|
||||
After actions that should trigger a new CIPE, record state before looping:
|
||||
|
||||
| Action | What to Track | Subagent Mode |
|
||||
| ----------------------------- | --------------------------------------------- | ------------- |
|
||||
| Fix auto-applying | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| Apply via MCP | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| Apply locally + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Reject + fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Fix failed + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| No fix + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Environment rerun | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| No-new-CIPE + auto-fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
|
||||
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
|
||||
|
||||
- Subagent will **completely ignore** the old/stale CIPE
|
||||
- Subagent will only wait for new CIPE to appear
|
||||
- Subagent will NOT return to main agent with stale CIPE data
|
||||
- Once new CIPE detected, subagent switches to normal polling
|
||||
|
||||
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
|
||||
|
||||
### Step 4: Progress Tracking
|
||||
|
||||
After each action:
|
||||
|
||||
- If state changed significantly → reset `no_progress_count = 0`
|
||||
- If state unchanged → `no_progress_count++`
|
||||
- On new CI attempt detected → reset `local_verify_count = 0`
|
||||
|
||||
## Status Reporting
|
||||
|
||||
Based on verbosity level:
|
||||
|
||||
| Level | What to Report |
|
||||
| --------- | -------------------------------------------------------------------------- |
|
||||
| `minimal` | Only final result (success/failure/timeout) |
|
||||
| `medium` | State changes + periodic updates ("Cycle N \| Elapsed: Xm \| Status: ...") |
|
||||
| `verbose` | All of medium + full subagent responses, git outputs, MCP responses |
|
||||
|
||||
## User Instruction Examples
|
||||
|
||||
Users can override default behaviors:
|
||||
|
||||
| Instruction | Effect |
|
||||
| ------------------------------------------------ | --------------------------------------------- |
|
||||
| "never auto-apply" | Always prompt before applying any fix |
|
||||
| "always ask before git push" | Prompt before each push |
|
||||
| "reject any fix for e2e tasks" | Auto-reject if `failedTaskIds` contains e2e |
|
||||
| "apply all fixes regardless of verification" | Skip verification check, apply everything |
|
||||
| "if confidence < 70, reject" | Check confidence field before applying |
|
||||
| "run 'nx affected -t typecheck' before applying" | Add local verification step |
|
||||
| "auto-fix workflow failures" | Attempt lockfile updates on pre-CIPE failures |
|
||||
| "wait 45 min for new CIPE" | Override new-CIPE timeout (default: 10 min) |
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | Action |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------- |
|
||||
| Git rebase conflict | Report to user, exit |
|
||||
| `nx apply-locally` fails | Report to user, attempt manual patch or exit |
|
||||
| MCP tool error | Retry once, if fails report to user |
|
||||
| Subagent spawn failure | Retry once, if fails exit with error |
|
||||
| No new CIPE detected | If `--auto-fix-workflow`, try lockfile update; otherwise report to user with guidance |
|
||||
| Lockfile auto-fix fails | Report to user, exit with guidance to check CI logs |
|
||||
|
||||
## Example Session
|
||||
|
||||
### Example 1: Normal Flow with Self-Healing (medium verbosity)
|
||||
|
||||
```
|
||||
[ci-monitor] Starting CI monitor for branch 'feature/add-auth'
|
||||
[ci-monitor] Config: max-cycles=5, timeout=120m, verbosity=medium
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] CI attempt: IN_PROGRESS | Self-Healing: NOT_STARTED | Elapsed: 1m
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 3m
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 5m
|
||||
|
||||
[ci-monitor] Fix available! Verification: COMPLETED
|
||||
[ci-monitor] Applying fix via MCP...
|
||||
[ci-monitor] Fix applied in CI. Waiting for new CI attempt...
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] New CI attempt detected!
|
||||
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 8m
|
||||
|
||||
[ci-monitor] CI passed successfully!
|
||||
|
||||
[ci-monitor] Summary:
|
||||
- Total cycles: 2
|
||||
- Total time: 12m 34s
|
||||
- Fixes applied: 1
|
||||
- Result: SUCCESS
|
||||
```
|
||||
|
||||
### Example 2: Pre-CI Failure (medium verbosity)
|
||||
|
||||
```
|
||||
[ci-monitor] Starting CI monitor for branch 'feature/add-products'
|
||||
[ci-monitor] Config: max-cycles=5, timeout=120m, auto-fix-workflow=true
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 2m
|
||||
|
||||
[ci-monitor] Applying fix locally, enhancing, and pushing...
|
||||
[ci-monitor] Committed: abc1234
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] Waiting for new CI attempt... (expected SHA: abc1234)
|
||||
[CI Monitor] ⚠️ CI attempt timeout (10 min). Returning no_new_cipe.
|
||||
|
||||
[ci-monitor] Status: no_new_cipe
|
||||
[ci-monitor] --auto-fix-workflow enabled. Attempting lockfile update...
|
||||
[ci-monitor] Lockfile updated. Committed: def5678
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] New CI attempt detected!
|
||||
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 18m
|
||||
|
||||
[ci-monitor] CI passed successfully!
|
||||
|
||||
[ci-monitor] Summary:
|
||||
- Total cycles: 3
|
||||
- Total time: 22m 15s
|
||||
- Fixes applied: 1 (self-healing) + 1 (lockfile)
|
||||
- Result: SUCCESS
|
||||
```
|
||||
@@ -0,0 +1,228 @@
|
||||
---
|
||||
name: nx-generate
|
||||
description: Generate code using nx generators. USE WHEN scaffolding code or transforming existing code - for example creating libraries or applications, or anything else that is boilerplate code or automates repetitive tasks. ALWAYS use this first when generating code with Nx instead of calling MCP tools or running nx generate immediately.
|
||||
---
|
||||
|
||||
# Run Nx Generator
|
||||
|
||||
Nx generators are powerful tools that scaffold projects, make automated code migrations or automate repetitive tasks in a monorepo. They ensure consistency across the codebase and reduce boilerplate work.
|
||||
|
||||
This skill applies when the user wants to:
|
||||
|
||||
- Create new projects like libraries or applications
|
||||
- Scaffold features or boilerplate code
|
||||
- Run workspace-specific or custom generators
|
||||
- Do anything else that an nx generator exists for
|
||||
|
||||
## Generator Discovery Flow
|
||||
|
||||
### Step 1: List Available Generators
|
||||
|
||||
Use the Nx CLI to discover available generators:
|
||||
|
||||
- List all generators for a plugin: `npx nx list @nx/react`
|
||||
- View available plugins: `npx nx list`
|
||||
|
||||
This includes:
|
||||
|
||||
- Plugin generators (e.g., `@nx/react:library`, `@nx/js:library`)
|
||||
- Local workspace generators (defined in the repo's own plugins)
|
||||
|
||||
### Step 2: Match Generator to User Request
|
||||
|
||||
Based on the user's request, identify which generator(s) could fulfill their needs. Consider:
|
||||
|
||||
- What artifact type they want to create (library, application, etc.)
|
||||
- Which framework or technology stack is relevant
|
||||
- Whether they mentioned specific generator names
|
||||
|
||||
**IMPORTANT**: When both a local workspace generator and an external plugin generator could satisfy the request, **always prefer the local workspace generator**. Local generators are customized for the specific repo's patterns and conventions.
|
||||
|
||||
It's possible that the user request is something that no Nx generator exists for whatsoever. In this case, you can stop using this skill and try to help the user another way. HOWEVER, the burden of proof for this is high. Before aborting, carefully consider each and every generator that's available. Look into details for any that could be related in any way before making this decision.
|
||||
|
||||
## Pre-Execution Checklist
|
||||
|
||||
Before running any generator, complete these steps:
|
||||
|
||||
### 1. Fetch Generator Schema
|
||||
|
||||
Use the `--help` flag to understand all available options:
|
||||
|
||||
```bash
|
||||
npx nx g @nx/react:library --help
|
||||
```
|
||||
|
||||
Pay attention to:
|
||||
|
||||
- Required options that must be provided
|
||||
- Optional options that may be relevant to the user's request
|
||||
- Default values that might need to be overridden
|
||||
|
||||
### 2. Read Generator Source Code
|
||||
|
||||
Understanding what the generator actually does helps you:
|
||||
|
||||
- Know what files will be created/modified
|
||||
- Understand any side effects (updating configs, installing deps, etc.)
|
||||
- Identify options that might not be obvious from the schema
|
||||
|
||||
To find generator source code:
|
||||
|
||||
- For plugin generators: Use `node -e "console.log(require.resolve('@nx/<plugin>/generators.json'));"` to find the generators.json, then locate the source from there
|
||||
- If that fails, read directly from `node_modules/<plugin>/generators.json`
|
||||
- For local generators: They are typically in `tools/generators/` or a local plugin directory. You can search the repo for the generator name to find it.
|
||||
|
||||
### 2.5 Reevaluate if the generator is right
|
||||
|
||||
Once you have built up an understanding of what the selected generator does, reconsider: Is this the right generator to service the user request?
|
||||
If not, it's okay to go back to the Generator Discovery Flow and select a different generator before proceeding. If you do, make sure to go through the entire pre-execution checklist once more.
|
||||
|
||||
### 3. Understand Repo Context
|
||||
|
||||
Before generating, examine the target area of the codebase:
|
||||
|
||||
- Look at similar existing artifacts (other libraries, applications, etc.)
|
||||
- Identify patterns and conventions used in the repo
|
||||
- Note naming conventions, file structures, and configuration patterns
|
||||
- Try to match these patterns when configuring the generator
|
||||
|
||||
For example, if similar libraries are using a specific test runner, build tool or linter, try to match that if possible.
|
||||
If projects or other artifacts are organized with a specific naming convention, try to match it.
|
||||
|
||||
### 4. Validate Required Options
|
||||
|
||||
Ensure all required options have values:
|
||||
|
||||
- Map the user's request to generator options
|
||||
- Infer values from context where possible
|
||||
- Ask the user for any critical missing information
|
||||
|
||||
## Execution
|
||||
|
||||
Keep in mind that you might have to prefix things with npx/pnpx/yarn if the user doesn't have nx installed globally.
|
||||
Many generators will behave differently based on where they are executed. For example, first-party nx library generators use the cwd to determine the directory that the library should be placed in. This is highly important.
|
||||
|
||||
### Consider Dry-Run (Optional)
|
||||
|
||||
Running with `--dry-run` first is strongly encouraged but not mandatory. Use your judgment:
|
||||
|
||||
- For complex generators or unfamiliar territory: do a dry-run first
|
||||
- For simple, well-understood generators: may proceed directly
|
||||
- Dry-run shows file names and created/deleted/modified markers, but not content
|
||||
- There are cases where a generator does not support dry-run (for example if it had to install an npm package) - in that case --dry-run might fail. Don't be discouraged but simply move on to running the generator for real and iterating from there.
|
||||
|
||||
### Running the Generator
|
||||
|
||||
Execute the generator with:
|
||||
|
||||
```bash
|
||||
nx generate <generator-name> <options> --no-interactive
|
||||
```
|
||||
|
||||
**CRITICAL**: Always include `--no-interactive` to prevent prompts that would hang the execution.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
nx generate @nx/react:library --name=my-utils --no-interactive
|
||||
```
|
||||
|
||||
### Handling Generator Failures
|
||||
|
||||
If the generator fails:
|
||||
|
||||
1. **Diagnose the error** - Read the error message carefully
|
||||
2. **Identify the cause** - Missing options, invalid values, conflicts, etc.
|
||||
3. **Attempt automatic fix** - Adjust options or resolve conflicts
|
||||
4. **Retry** - Run the generator again with corrected options
|
||||
|
||||
Common failure reasons:
|
||||
|
||||
- Missing required options
|
||||
- Invalid option values
|
||||
- Conflicting with existing files
|
||||
- Missing dependencies
|
||||
- Generator doesn't support certain flag combinations
|
||||
|
||||
## Post-Generation
|
||||
|
||||
### 1. Modify Generated Code (If Needed)
|
||||
|
||||
Generators provide a starting point, but the output may need adjustment to match the user's specific requirements:
|
||||
|
||||
- Add or modify functionality as requested
|
||||
- Adjust imports, exports, or configurations
|
||||
- Integrate with existing code patterns in the repo
|
||||
|
||||
### 2. Format Code
|
||||
|
||||
Run formatting on all generated/modified files:
|
||||
|
||||
```bash
|
||||
nx format --fix
|
||||
```
|
||||
|
||||
Languages other than javascript/typescript might need other formatting invocations too.
|
||||
|
||||
### 3. Run Verification
|
||||
|
||||
Verify that the generated code works correctly. What this looks like will vary depending on the type of generator and the targets available.
|
||||
If the generator created a new project, run its targets directly
|
||||
Use your best judgement to determine what needs to be verified.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
nx lint <new-project>
|
||||
nx test <new-project>
|
||||
nx build <new-project>
|
||||
```
|
||||
|
||||
### 4. Handle Verification Failures
|
||||
|
||||
When verification fails:
|
||||
|
||||
**If scope is manageable** (a few lint errors, minor type issues):
|
||||
|
||||
- Fix the issues
|
||||
- Re-run verification to confirm
|
||||
|
||||
**If issues are extensive** (many errors, complex problems):
|
||||
|
||||
- Attempt simple, obvious fixes first
|
||||
- If still failing, escalate to the user with:
|
||||
- Description of what was generated
|
||||
- What verification is failing
|
||||
- What you've attempted to fix
|
||||
- Remaining issues that need user input
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Generator Failures
|
||||
|
||||
- Check the error message for specific causes
|
||||
- Verify all required options are provided
|
||||
- Check for conflicts with existing files
|
||||
- Ensure the generator name and options are correct
|
||||
|
||||
### Missing Options
|
||||
|
||||
- Consult the generator schema for required fields
|
||||
- Infer values from context when reasonable
|
||||
- Ask the user for values that cannot be inferred
|
||||
|
||||
## Key Principles
|
||||
|
||||
1. **Local generators first** - Always prefer workspace/local generators over external plugin generators when both could work
|
||||
|
||||
2. **Understand before running** - Read both the schema AND the source code to fully understand what will happen
|
||||
|
||||
3. **No prompts** - Always use `--no-interactive` to prevent hanging
|
||||
|
||||
4. **Generators are starting points** - Modify the output as needed to fully satisfy the user's requirements
|
||||
|
||||
5. **Verify changes work** - Don't just generate; ensure the code builds, lints, and tests pass
|
||||
|
||||
6. **Be proactive about fixes** - Don't just report errors; attempt to resolve them automatically when possible
|
||||
|
||||
7. **Match repo patterns** - Study existing similar code in the repo and match its conventions
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
name: nx-plugins
|
||||
description: Find and add Nx plugins. USE WHEN user wants to discover available plugins, install a new plugin, or add support for a specific framework or technology to the workspace.
|
||||
---
|
||||
|
||||
## Finding and Installing new plugins
|
||||
|
||||
- List plugins: `pnpm nx list`
|
||||
- Install plugins `pnpm nx add <plugin>`. Example: `pnpm nx add @nx/react`.
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
name: nx-run-tasks
|
||||
description: Helps with running tasks in an Nx workspace. USE WHEN the user wants to execute build, test, lint, serve, or run any other tasks defined in the workspace.
|
||||
---
|
||||
|
||||
You can run tasks with Nx in the following way.
|
||||
|
||||
Keep in mind that you might have to prefix things with npx/pnpx/yarn if the user doesn't have nx installed globally. Look at the package.json or lockfile to determine which package manager is in use.
|
||||
|
||||
For more details on any command, run it with `--help` (e.g. `nx run-many --help`, `nx affected --help`).
|
||||
|
||||
## Understand which tasks can be run
|
||||
|
||||
You can check those via `nx show project <projectname> --json`, for example `nx show project myapp --json`. It contains a `targets` section which has information about targets that can be run. You can also just look at the `package.json` scripts or `project.json` targets, but you might miss out on inferred tasks by Nx plugins.
|
||||
|
||||
## Run a single task
|
||||
|
||||
```
|
||||
nx run <project>:<task>
|
||||
```
|
||||
|
||||
where `project` is the project name defined in `package.json` or `project.json` (if present).
|
||||
|
||||
## Run multiple tasks
|
||||
|
||||
```
|
||||
nx run-many -t build test lint typecheck
|
||||
```
|
||||
|
||||
You can pass a `-p` flag to filter to specific projects, otherwise it runs on all projects. You can also use `--exclude` to exclude projects, and `--parallel` to control the number of parallel processes (default is 3).
|
||||
|
||||
Examples:
|
||||
|
||||
- `nx run-many -t test -p proj1 proj2` — test specific projects
|
||||
- `nx run-many -t test --projects=*-app --exclude=excluded-app` — test projects matching a pattern
|
||||
- `nx run-many -t test --projects=tag:api-*` — test projects by tag
|
||||
|
||||
## Run tasks for affected projects
|
||||
|
||||
Use `nx affected` to only run tasks on projects that have been changed and projects that depend on changed projects. This is especially useful in CI and for large workspaces.
|
||||
|
||||
```
|
||||
nx affected -t build test lint
|
||||
```
|
||||
|
||||
By default it compares against the base branch. You can customize this:
|
||||
|
||||
- `nx affected -t test --base=main --head=HEAD` — compare against a specific base and head
|
||||
- `nx affected -t test --files=libs/mylib/src/index.ts` — specify changed files directly
|
||||
|
||||
## Useful flags
|
||||
|
||||
These flags work with `run`, `run-many`, and `affected`:
|
||||
|
||||
- `--skipNxCache` — rerun tasks even when results are cached
|
||||
- `--verbose` — print additional information such as stack traces
|
||||
- `--nxBail` — stop execution after the first failed task
|
||||
- `--configuration=<name>` — use a specific configuration (e.g. `production`)
|
||||
@@ -0,0 +1,186 @@
|
||||
---
|
||||
name: nx-workspace
|
||||
description: "Explore and understand Nx workspaces. USE WHEN answering any questions about the nx workspace, the projects in it or tasks to run. EXAMPLES: 'What projects are in this workspace?', 'How is project X configured?', 'What targets can I run?', 'What's affected by my changes?', 'Which projects depend on library Y?', or any questions about Nx workspace structure, project configuration, or available tasks."
|
||||
---
|
||||
|
||||
# Nx Workspace Exploration
|
||||
|
||||
This skill provides read-only exploration of Nx workspaces. Use it to understand workspace structure, project configuration, available targets, and dependencies.
|
||||
|
||||
Keep in mind that you might have to prefix commands with `npx`/`pnpx`/`yarn` if nx isn't installed globally. Check the lockfile to determine the package manager in use.
|
||||
|
||||
## Listing Projects
|
||||
|
||||
Use `nx show projects` to list projects in the workspace.
|
||||
|
||||
```bash
|
||||
# List all projects
|
||||
nx show projects
|
||||
|
||||
# Filter by pattern (glob)
|
||||
nx show projects --projects "apps/*"
|
||||
nx show projects --projects "shared-*"
|
||||
|
||||
# Filter by project type
|
||||
nx show projects --type app
|
||||
nx show projects --type lib
|
||||
nx show projects --type e2e
|
||||
|
||||
# Filter by target (projects that have a specific target)
|
||||
nx show projects --withTarget build
|
||||
nx show projects --withTarget e2e
|
||||
|
||||
# Find affected projects (changed since base branch)
|
||||
nx show projects --affected
|
||||
nx show projects --affected --base=main
|
||||
nx show projects --affected --type app
|
||||
|
||||
# Combine filters
|
||||
nx show projects --type lib --withTarget test
|
||||
nx show projects --affected --exclude="*-e2e"
|
||||
|
||||
# Output as JSON
|
||||
nx show projects --json
|
||||
```
|
||||
|
||||
## Project Configuration
|
||||
|
||||
Use `nx show project <name> --json` to get the full resolved configuration for a project.
|
||||
|
||||
**Important**: Do NOT read `project.json` directly - it only contains partial configuration. The `nx show project` command returns the full resolved config including inferred targets from plugins.
|
||||
|
||||
You can read the full project schema at `node_modules/nx/schemas/project-schema.json` to understand nx project configuration options.
|
||||
|
||||
```bash
|
||||
# Get full project configuration
|
||||
nx show project my-app --json
|
||||
|
||||
# Extract specific parts from the JSON
|
||||
nx show project my-app --json | jq '.targets'
|
||||
nx show project my-app --json | jq '.targets.build'
|
||||
nx show project my-app --json | jq '.targets | keys'
|
||||
|
||||
|
||||
# Check project metadata
|
||||
nx show project my-app --json | jq '{name, root, sourceRoot, projectType, tags}'
|
||||
```
|
||||
|
||||
## Target Information
|
||||
|
||||
Targets define what tasks can be run on a project.
|
||||
|
||||
```bash
|
||||
# List all targets for a project
|
||||
nx show project my-app --json | jq '.targets | keys'
|
||||
|
||||
# Get full target configuration
|
||||
nx show project my-app --json | jq '.targets.build'
|
||||
|
||||
# Check target executor/command
|
||||
nx show project my-app --json | jq '.targets.build.executor'
|
||||
nx show project my-app --json | jq '.targets.build.command'
|
||||
|
||||
# View target options
|
||||
nx show project my-app --json | jq '.targets.build.options'
|
||||
|
||||
# Check target inputs/outputs (for caching)
|
||||
nx show project my-app --json | jq '.targets.build.inputs'
|
||||
nx show project my-app --json | jq '.targets.build.outputs'
|
||||
|
||||
# Find projects with a specific target
|
||||
nx show projects --withTarget serve
|
||||
nx show projects --withTarget e2e
|
||||
```
|
||||
|
||||
## Workspace Configuration
|
||||
|
||||
Read `nx.json` directly for workspace-level configuration.
|
||||
You can read the full project schema at `node_modules/nx/schemas/nx-schema.json` to understand nx project configuration options.
|
||||
|
||||
```bash
|
||||
# Read the full nx.json
|
||||
cat nx.json
|
||||
|
||||
# Or use jq for specific sections
|
||||
cat nx.json | jq '.targetDefaults'
|
||||
cat nx.json | jq '.namedInputs'
|
||||
cat nx.json | jq '.plugins'
|
||||
cat nx.json | jq '.generators'
|
||||
```
|
||||
|
||||
Key nx.json sections:
|
||||
|
||||
- `targetDefaults` - Default configuration applied to all targets of a given name
|
||||
- `namedInputs` - Reusable input definitions for caching
|
||||
- `plugins` - Nx plugins and their configuration
|
||||
- ...and much more, read the schema or nx.json for details
|
||||
|
||||
## Affected Projects
|
||||
|
||||
Find projects affected by changes in the current branch.
|
||||
|
||||
```bash
|
||||
# Affected since base branch (auto-detected)
|
||||
nx show projects --affected
|
||||
|
||||
# Affected with explicit base
|
||||
nx show projects --affected --base=main
|
||||
nx show projects --affected --base=origin/main
|
||||
|
||||
# Affected between two commits
|
||||
nx show projects --affected --base=abc123 --head=def456
|
||||
|
||||
# Affected apps only
|
||||
nx show projects --affected --type app
|
||||
|
||||
# Affected excluding e2e projects
|
||||
nx show projects --affected --exclude="*-e2e"
|
||||
|
||||
# Affected by uncommitted changes
|
||||
nx show projects --affected --uncommitted
|
||||
|
||||
# Affected by untracked files
|
||||
nx show projects --affected --untracked
|
||||
```
|
||||
|
||||
## Common Exploration Patterns
|
||||
|
||||
### "What's in this workspace?"
|
||||
|
||||
```bash
|
||||
nx show projects
|
||||
nx show projects --type app
|
||||
nx show projects --type lib
|
||||
```
|
||||
|
||||
### "How do I build/test/lint project X?"
|
||||
|
||||
```bash
|
||||
nx show project X --json | jq '.targets | keys'
|
||||
nx show project X --json | jq '.targets.build'
|
||||
```
|
||||
|
||||
### "What depends on library Y?"
|
||||
|
||||
```bash
|
||||
# Find projects that may depend on Y by searching for imports
|
||||
# (Nx doesn't have a direct "dependents" command via CLI)
|
||||
grep -r "from '@myorg/Y'" --include="*.ts" --include="*.tsx" apps/ libs/
|
||||
```
|
||||
|
||||
### "What configuration options are available?"
|
||||
|
||||
```bash
|
||||
cat node_modules/nx/schemas/nx-schema.json | jq '.properties | keys'
|
||||
cat node_modules/nx/schemas/project-schema.json | jq '.properties | keys'
|
||||
```
|
||||
|
||||
### "Why is project X affected?"
|
||||
|
||||
```bash
|
||||
# Check what files changed
|
||||
git diff --name-only main
|
||||
|
||||
# See which project owns those files
|
||||
nx show project X --json | jq '.root'
|
||||
```
|
||||
@@ -0,0 +1,92 @@
|
||||
name: Banner Content Monitor
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '*/15 * * * *'
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
BANNER_URL: ${{ vars.BANNER_URL }}
|
||||
|
||||
jobs:
|
||||
check-and-deploy:
|
||||
if: ${{ github.repository_owner == 'nrwl' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Fetch banner content and compute hash
|
||||
id: banner
|
||||
run: |
|
||||
if [ -z "$BANNER_URL" ]; then
|
||||
echo "BANNER_URL is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Fetch content and compute hash
|
||||
CONTENT_HASH=$(curl -sf "$BANNER_URL" | sha256sum | cut -d' ' -f1)
|
||||
|
||||
if [ -z "$CONTENT_HASH" ]; then
|
||||
echo "Failed to fetch banner content"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "current_hash=$CONTENT_HASH" >> $GITHUB_OUTPUT
|
||||
echo "Current banner hash: $CONTENT_HASH"
|
||||
|
||||
- name: Restore cached hash
|
||||
id: cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: .banner-hash
|
||||
key: banner-content-hash-
|
||||
restore-keys: |
|
||||
banner-content-hash-
|
||||
|
||||
- name: Compare hashes
|
||||
id: compare
|
||||
run: |
|
||||
CURRENT_HASH="${{ steps.banner.outputs.current_hash }}"
|
||||
|
||||
if [ -f .banner-hash ]; then
|
||||
CACHED_HASH=$(cat .banner-hash)
|
||||
echo "Cached hash: $CACHED_HASH"
|
||||
else
|
||||
CACHED_HASH=""
|
||||
echo "No cached hash found"
|
||||
fi
|
||||
|
||||
if [ "$CURRENT_HASH" != "$CACHED_HASH" ]; then
|
||||
echo "changed=true" >> $GITHUB_OUTPUT
|
||||
echo "Banner content has changed!"
|
||||
else
|
||||
echo "changed=false" >> $GITHUB_OUTPUT
|
||||
echo "Banner content unchanged"
|
||||
fi
|
||||
|
||||
- name: Trigger Netlify deploys
|
||||
if: steps.compare.outputs.changed == 'true'
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
run: |
|
||||
npm install -g netlify-cli
|
||||
|
||||
echo "Triggering nx-docs deploy..."
|
||||
netlify deploy --trigger --prod -s nx-docs
|
||||
|
||||
echo "Triggering nx-dev deploy..."
|
||||
netlify deploy --trigger --prod -s nx-dev
|
||||
|
||||
echo "Both deploys triggered successfully"
|
||||
|
||||
- name: Save new hash to cache
|
||||
if: steps.compare.outputs.changed == 'true'
|
||||
run: |
|
||||
echo "${{ steps.banner.outputs.current_hash }}" > .banner-hash
|
||||
|
||||
- name: Update cache
|
||||
if: steps.compare.outputs.changed == 'true'
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: .banner-hash
|
||||
key: banner-content-hash-${{ github.run_id }}
|
||||
@@ -18,6 +18,7 @@ jobs:
|
||||
main-linux:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NX_BATCH_MODE: 'true'
|
||||
NX_E2E_CI_CACHE_KEY: e2e-github-linux
|
||||
NX_DAEMON: 'true'
|
||||
NX_PERF_LOGGING: 'false'
|
||||
@@ -29,6 +30,9 @@ jobs:
|
||||
NX_ALLOW_NON_CACHEABLE_DTE: 'true'
|
||||
NX_CLOUD_USE_NEW_TASK_APIS: 'true'
|
||||
NX_CLOUD_USE_NEW_STREAM_OUTPUT: 'true'
|
||||
NX_CLOUD_EXPERIMENTAL_POLLING: 'true'
|
||||
NX_CLOUD_CONTINUOUS_ASSIGNMENT: 'false'
|
||||
NX_CLOUD_VERBOSE_LOGGING: 'true'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -47,7 +51,7 @@ jobs:
|
||||
main-branch-name: 'master'
|
||||
|
||||
- name: Start CI Run
|
||||
run: npx nx-cloud@next start-ci-run --auto-apply-fixes="*format:check*,*sync:check*,*conformance:check*,*format-native*,*lint-native*,*lint*,*astro-docs:validate-links*" --distribute-on="./.nx/workflows/dynamic-changesets.yaml" --stop-agents-after="e2e"
|
||||
run: npx nx-cloud@next start-ci-run --distribute-on="./.nx/workflows/dynamic-changesets.yaml" --stop-agents-after="e2e"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@@ -74,7 +78,7 @@ jobs:
|
||||
pnpm playwright install --with-deps
|
||||
|
||||
- name: Nx Report
|
||||
run:
|
||||
run:
|
||||
pnpm nx report
|
||||
|
||||
- name: Run Checks/Lint/Test/Build
|
||||
@@ -93,7 +97,7 @@ jobs:
|
||||
pnpm nx run-many -t check-imports check-lock-files check-codeowners --parallel=1 --no-dte &
|
||||
pids+=($!)
|
||||
|
||||
pnpm nx affected --targets=lint,test,test-kt,build,e2e,e2e-ci,format-native,lint-native &
|
||||
pnpm nx affected --targets=lint,test,build,e2e,e2e-ci,format-native,lint-native,gradle:build-ci &
|
||||
pids+=($!)
|
||||
|
||||
for pid in "${pids[@]}"; do
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d # v4.1.0
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 10.11.1
|
||||
version: 10.28.2
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
|
||||
- uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d # v4.1.0
|
||||
with:
|
||||
version: 10.11.1
|
||||
version: 10.28.2
|
||||
|
||||
- name: Use Node.js ${{ matrix.node_version }}
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
|
||||
@@ -75,7 +75,7 @@ const matrixData: MatrixData = {
|
||||
os_name: 'Linux',
|
||||
os_timeout: 60,
|
||||
package_managers: ['npm', 'pnpm', 'yarn'],
|
||||
node_versions: ['20.19.0', '22.12.0', '24.0.0'],
|
||||
node_versions: ['20.19.0', '22.13.0', '24.0.0'],
|
||||
excluded: ['e2e-detox', 'e2e-react-native', 'e2e-expo']
|
||||
},
|
||||
// Docker is not supported on ARM-based macOS runners (no nested virtualization)
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
|
||||
- uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d # v4.1.0
|
||||
with:
|
||||
version: 10.11.1 # Aligned with root package.json (pnpm/action-setup will helpfully error if out of sync)
|
||||
version: 10.28.2 # Aligned with root package.json (pnpm/action-setup will helpfully error if out of sync)
|
||||
|
||||
- name: Run a security audit
|
||||
run: pnpm dlx audit-ci --critical --report-type summary
|
||||
|
||||
@@ -22,7 +22,7 @@ env:
|
||||
NX_RUN_GROUP: ${{ github.run_id }}-${{ github.run_attempt }}
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
NODE_VERSION: 22.16.0
|
||||
PNPM_VERSION: 10.11.1 # Aligned with root package.json (pnpm/action-setup will helpfully error if out of sync)
|
||||
PNPM_VERSION: 10.28.2 # Aligned with root package.json (pnpm/action-setup will helpfully error if out of sync)
|
||||
|
||||
jobs:
|
||||
# We first need to determine the version we are releasing, and if we need a custom repo or ref to use for the git checkout in subsequent steps.
|
||||
@@ -174,7 +174,7 @@ jobs:
|
||||
bash -c "
|
||||
set -e
|
||||
echo 'https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories
|
||||
apk add --no-cache curl xz openjdk21
|
||||
apk add --no-cache curl xz openjdk21 build-base lld
|
||||
|
||||
# Set up Java 21
|
||||
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk
|
||||
@@ -194,6 +194,10 @@ jobs:
|
||||
npm i -g pnpm@${PNPM_VERSION} --force
|
||||
pnpm --version
|
||||
|
||||
# Help clang find GCC runtime (crtbeginS.o, libgcc) and use lld for jemalloc build
|
||||
GCC_DIR=\$(dirname \$(find /usr/lib/gcc -name crtbeginS.o | head -1))
|
||||
export CFLAGS=\"\${CFLAGS} -fuse-ld=lld --gcc-install-dir=\${GCC_DIR}\"
|
||||
|
||||
# Install deps and run native build
|
||||
pnpm install --frozen-lockfile
|
||||
rustup target add x86_64-unknown-linux-musl
|
||||
@@ -230,6 +234,9 @@ jobs:
|
||||
node --version
|
||||
npm --version
|
||||
|
||||
# Help clang find GCC runtime (crtbeginS.o, libgcc) and use lld for jemalloc build
|
||||
export CFLAGS="${CFLAGS} -fuse-ld=lld --gcc-toolchain=/usr/aarch64-unknown-linux-gnu"
|
||||
|
||||
npm i -g pnpm@${PNPM_VERSION} --force
|
||||
pnpm --version
|
||||
|
||||
@@ -260,7 +267,7 @@ jobs:
|
||||
bash -c "
|
||||
set -e
|
||||
echo 'https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories
|
||||
apk add --no-cache curl xz openjdk21
|
||||
apk add --no-cache curl xz openjdk21 build-base lld
|
||||
|
||||
# Set up Java 21
|
||||
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk
|
||||
@@ -280,6 +287,10 @@ jobs:
|
||||
npm i -g pnpm@${PNPM_VERSION} --force
|
||||
pnpm --version
|
||||
|
||||
# Help clang find GCC runtime (crtbeginS.o, libgcc) and use lld for jemalloc build
|
||||
GCC_DIR=\$(dirname \$(find /aarch64-linux-musl-cross/lib/gcc -name crtbeginS.o | head -1))
|
||||
export CFLAGS=\"\${CFLAGS} -fuse-ld=lld --gcc-install-dir=\${GCC_DIR}\"
|
||||
|
||||
# Install deps and run native build
|
||||
pnpm install --frozen-lockfile
|
||||
rustup target add aarch64-unknown-linux-musl
|
||||
@@ -354,12 +365,24 @@ jobs:
|
||||
architecture: x86
|
||||
|
||||
- name: Build in docker
|
||||
uses: addnab/docker-run-action@4f65fabd2431ebc8d299f8e5a018d79a769ae185 # v3
|
||||
if: ${{ matrix.settings.docker }}
|
||||
with:
|
||||
image: ${{ matrix.settings.docker }}
|
||||
options: --user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build
|
||||
run: ${{ matrix.settings.build }}
|
||||
shell: bash
|
||||
env:
|
||||
BUILD_SCRIPT: ${{ matrix.settings.build }}
|
||||
run: |
|
||||
SCRIPT_FILE=$(mktemp)
|
||||
echo "$BUILD_SCRIPT" > "$SCRIPT_FILE"
|
||||
docker run --rm \
|
||||
--user 0:0 \
|
||||
-e PNPM_VERSION \
|
||||
-v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db \
|
||||
-v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache \
|
||||
-v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index \
|
||||
-v ${{ github.workspace }}:/build \
|
||||
-v "$SCRIPT_FILE:/build-script.sh" \
|
||||
-w /build \
|
||||
${{ matrix.settings.docker }} \
|
||||
bash /build-script.sh
|
||||
|
||||
- name: Build
|
||||
run: ${{ matrix.settings.build }}
|
||||
@@ -406,7 +429,7 @@ jobs:
|
||||
env
|
||||
whoami
|
||||
sudo pkg install -y -f node libnghttp2 www/npm git openjdk17
|
||||
sudo npm install --location=global --ignore-scripts pnpm@10.11.1
|
||||
sudo npm install --location=global --ignore-scripts pnpm@10.28.2
|
||||
# Set up Java 17
|
||||
export JAVA_HOME=/usr/local/openjdk17
|
||||
export PATH="$JAVA_HOME/bin:$PATH"
|
||||
@@ -472,11 +495,27 @@ jobs:
|
||||
rm -rf ~/.rustup/toolchains/*/share || true
|
||||
# Remove other development tool caches
|
||||
rm -rf ~/.cache/* || true
|
||||
|
||||
# Remove unnecessary workspace directories
|
||||
rm -rf docs astro-docs nx-dev || true
|
||||
|
||||
echo "Checking disk space after cleanup"
|
||||
df -h
|
||||
|
||||
echo "Building FreeBSD bindings"
|
||||
pnpm nx run-many --verbose --outputStyle stream --target=build-native -- --target=x86_64-unknown-freebsd
|
||||
BUILD_EXIT=0
|
||||
pnpm nx run-many --verbose --outputStyle stream --target=build-native -- --target=x86_64-unknown-freebsd || BUILD_EXIT=$?
|
||||
|
||||
echo "=== Disk usage after build ==="
|
||||
df -h
|
||||
|
||||
if [ "$BUILD_EXIT" -ne 0 ]; then
|
||||
echo "Build failed with exit code $BUILD_EXIT"
|
||||
exit $BUILD_EXIT
|
||||
fi
|
||||
|
||||
echo "Build succeeded"
|
||||
|
||||
echo "Cleaning up"
|
||||
pnpm nx reset
|
||||
rm -rf node_modules
|
||||
|
||||
@@ -29,7 +29,7 @@ jest.debug.config.js
|
||||
# Banner JSON files are generated during static builds
|
||||
/nx-dev/nx-dev/lib/banner.json
|
||||
/astro-docs/src/content/banner.json
|
||||
**/tests/temp-db
|
||||
**/tests/temp-db*
|
||||
|
||||
# Issues scraper creates these files, stored by github's cache
|
||||
/scripts/issues-scraper/cached
|
||||
@@ -71,7 +71,7 @@ dependency-reduced-pom.xml
|
||||
*.wasm
|
||||
/wasi-sdk*
|
||||
|
||||
vite.config.*.timestamp*
|
||||
*.config.timestamp*
|
||||
|
||||
storybook-static
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ common-env-vars: &common-env-vars
|
||||
# These are need for build and link validation for next.js and astro apps
|
||||
NEXT_PUBLIC_ASTRO_URL: 'https://master--nx-docs.netlify.app'
|
||||
NX_DEV_URL: 'https://canary.nx.dev'
|
||||
NX_CLOUD_IO_TRACING_DIRECTORY: '~/io-tracing'
|
||||
|
||||
common-init-steps: &common-init-steps
|
||||
- name: Checkout
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
distribute-on:
|
||||
default: auto linux-large, 3 linux-extra-large
|
||||
extra-small-changeset: 6 linux-large, 3 linux-extra-large
|
||||
small-changeset: 6 linux-large, 4 linux-extra-large
|
||||
medium-changeset: 6 linux-large, 5 linux-extra-large
|
||||
large-changeset: 6 linux-large, 6 linux-extra-large
|
||||
extra-large-changeset: 8 linux-large, 8 linux-extra-large
|
||||
assignment-rules:
|
||||
- projects:
|
||||
- e2e-gradle
|
||||
@@ -8,6 +12,23 @@ assignment-rules:
|
||||
run-on:
|
||||
- agent: linux-extra-large
|
||||
parallelism: 1
|
||||
- projects:
|
||||
- e2e-next
|
||||
- e2e-plugin
|
||||
targets:
|
||||
- e2e-ci**
|
||||
run-on:
|
||||
- agent: linux-extra-large
|
||||
parallelism: 2
|
||||
- projects:
|
||||
- e2e-angular
|
||||
- e2e-node
|
||||
- e2e-react
|
||||
targets:
|
||||
- e2e-ci**
|
||||
run-on:
|
||||
- agent: linux-extra-large
|
||||
parallelism: 1
|
||||
|
||||
- projects:
|
||||
- nx
|
||||
@@ -25,15 +46,14 @@ assignment-rules:
|
||||
|
||||
- projects:
|
||||
- e2e-release
|
||||
- e2e-angular
|
||||
- e2e-react
|
||||
- e2e-next
|
||||
- e2e-nuxt
|
||||
- e2e-web
|
||||
- e2e-eslint
|
||||
- e2e-remix
|
||||
- e2e-cypress
|
||||
- e2e-docker
|
||||
- e2e-js
|
||||
- e2e-nx
|
||||
- e2e-nx-init
|
||||
- e2e-dotnet
|
||||
- e2e-workspace-create
|
||||
@@ -44,7 +64,7 @@ assignment-rules:
|
||||
- agent: linux-large
|
||||
parallelism: 1
|
||||
- agent: linux-extra-large
|
||||
parallelism: 1
|
||||
parallelism: 2
|
||||
|
||||
# All other e2e tests can run in parallel
|
||||
- targets:
|
||||
@@ -80,6 +100,15 @@ assignment-rules:
|
||||
- agent: linux-extra-large
|
||||
parallelism: 6
|
||||
|
||||
# TODO(altan): remove when scheduling issue resolved
|
||||
- projects:
|
||||
- nx-dev
|
||||
targets:
|
||||
- prebuild-banner
|
||||
run-on:
|
||||
- agent: linux-extra-large
|
||||
parallelism: 6
|
||||
|
||||
- targets:
|
||||
- "*"
|
||||
run-on:
|
||||
|
||||
@@ -3,3 +3,10 @@ nx-dev/**/jest.config.js
|
||||
_files
|
||||
_solution
|
||||
nx-dev/tutorial/**/templates
|
||||
|
||||
# Generated by napi-rs (outputs of build-native)
|
||||
packages/nx/src/native/index.d.ts
|
||||
packages/nx/src/native/native-bindings.js
|
||||
|
||||
# Workaround for ignore-files crate bug with prefix matching
|
||||
**/target/
|
||||
|
||||
@@ -0,0 +1,479 @@
|
||||
---
|
||||
description: Polls Nx Cloud CI pipeline and self-healing status. Returns structured state when actionable. Spawned by /nx-cloud-ci-monitor command to monitor CI Attempt status.
|
||||
mode: subagent
|
||||
---
|
||||
|
||||
# CI Watcher Subagent
|
||||
|
||||
You are a CI monitoring subagent responsible for polling Nx Cloud CI Attempt status and self-healing state. You report status back to the main agent - you do NOT make apply/reject decisions.
|
||||
|
||||
## Your Responsibilities
|
||||
|
||||
1. Poll CI status using the `ci_information` MCP tool
|
||||
2. Implement exponential backoff between polls
|
||||
3. Return structured state when an actionable condition is reached
|
||||
4. Track iteration count and elapsed time
|
||||
5. Output status updates based on verbosity level
|
||||
|
||||
## Input Parameters (from Main Agent)
|
||||
|
||||
The main agent may provide these optional parameters in the prompt:
|
||||
|
||||
| Parameter | Description |
|
||||
| ------------------- | -------------------------------------------------------- |
|
||||
| `branch` | Branch to monitor (auto-detected if not provided) |
|
||||
| `expectedCommitSha` | Commit SHA that should trigger a new CI Attempt |
|
||||
| `previousCipeUrl` | CI Attempt URL before the action (to detect change) |
|
||||
| `subagentTimeout` | Polling timeout in minutes (default: 60) |
|
||||
| `verbosity` | Output level: minimal, medium, verbose (default: medium) |
|
||||
|
||||
When `expectedCommitSha` or `previousCipeUrl` is provided, you must detect whether a new CI Attempt has spawned.
|
||||
|
||||
## MCP Tool Reference
|
||||
|
||||
### `ci_information`
|
||||
|
||||
**Input:**
|
||||
|
||||
```json
|
||||
{
|
||||
"branch": "string (optional, defaults to current git branch)",
|
||||
"select": "string (optional, comma-separated field names)",
|
||||
"pageToken": "number (optional, 0-based pagination for long strings)"
|
||||
}
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
```json
|
||||
{
|
||||
"cipeStatus": "NOT_STARTED | IN_PROGRESS | SUCCEEDED | FAILED | CANCELED | TIMED_OUT",
|
||||
"cipeUrl": "string",
|
||||
"branch": "string",
|
||||
"commitSha": "string | null",
|
||||
"failedTaskIds": "string[]",
|
||||
"verifiedTaskIds": "string[]",
|
||||
"selfHealingEnabled": "boolean",
|
||||
"selfHealingStatus": "NOT_STARTED | IN_PROGRESS | COMPLETED | FAILED | NOT_EXECUTABLE | null",
|
||||
"verificationStatus": "NOT_STARTED | IN_PROGRESS | COMPLETED | FAILED | NOT_EXECUTABLE | null",
|
||||
"userAction": "NONE | APPLIED | REJECTED | APPLIED_LOCALLY | APPLIED_AUTOMATICALLY | null",
|
||||
"failureClassification": "string | null",
|
||||
"taskOutputSummary": "string | null",
|
||||
"suggestedFixReasoning": "string | null",
|
||||
"suggestedFixDescription": "string | null",
|
||||
"suggestedFix": "string | null",
|
||||
"shortLink": "string | null",
|
||||
"couldAutoApplyTasks": "boolean | null",
|
||||
"confidence": "number | null",
|
||||
"confidenceReasoning": "string | null"
|
||||
}
|
||||
```
|
||||
|
||||
**Select Parameter:**
|
||||
|
||||
| Usage | Returns |
|
||||
| --------------- | ----------------------------------------------------------- |
|
||||
| No `select` | Formatted overview (truncated, not recommended for polling) |
|
||||
| Single field | Raw value with pagination for long strings |
|
||||
| Multiple fields | Object with requested field values |
|
||||
|
||||
**Field Sets for Efficient Polling:**
|
||||
|
||||
```yaml
|
||||
WAIT_FIELDS:
|
||||
'cipeUrl,commitSha,cipeStatus'
|
||||
# Minimal fields for detecting new CI Attempt
|
||||
|
||||
LIGHT_FIELDS:
|
||||
'cipeStatus,cipeUrl,branch,commitSha,selfHealingStatus,verificationStatus,userAction,failedTaskIds,verifiedTaskIds,selfHealingEnabled,failureClassification,couldAutoApplyTasks,shortLink,confidence,confidenceReasoning'
|
||||
# Status fields for determining actionable state
|
||||
|
||||
HEAVY_FIELDS:
|
||||
'taskOutputSummary,suggestedFix,suggestedFixReasoning,suggestedFixDescription'
|
||||
# Large content fields - fetch only when returning to main agent
|
||||
```
|
||||
|
||||
## Initial Wait
|
||||
|
||||
Before first poll, wait based on context:
|
||||
|
||||
- **Fresh start (no expected CIPE):** Wait 60 seconds to allow CI to start
|
||||
- **Expecting new CIPE:** Wait 30 seconds (action already triggered)
|
||||
|
||||
**IMPORTANT:** Always run sleep in foreground, NOT as background command.
|
||||
|
||||
```bash
|
||||
sleep 60 # or 30 if expecting new CIPE (FOREGROUND, not background)
|
||||
```
|
||||
|
||||
## Two-Phase Operation
|
||||
|
||||
The subagent operates in one of two modes depending on input:
|
||||
|
||||
### Mode 1: Fresh Start (no `expectedCommitSha` or `previousCipeUrl`)
|
||||
|
||||
Normal polling - process whatever CIPE is returned by `ci_information`.
|
||||
|
||||
### Mode 2: Wait-for-New-CIPE (when `expectedCommitSha` or `previousCipeUrl` provided)
|
||||
|
||||
**CRITICAL**: When expecting a new CIPE, the subagent must **completely ignore** the old/stale CIPE. Do NOT process its status, do NOT return actionable states based on it.
|
||||
|
||||
#### Phase A: Wait Mode
|
||||
|
||||
1. Start a **new-CIPE timeout** timer (default: 30 minutes)
|
||||
2. On each poll of `ci_information`:
|
||||
- Check if CIPE is NEW:
|
||||
- `cipeUrl` differs from `previousCipeUrl` → **new CIPE detected**
|
||||
- `commitSha` matches `expectedCommitSha` → **correct CIPE detected**
|
||||
- If still OLD CIPE: **ignore all status fields**, just wait and poll again
|
||||
- Do NOT return `fix_available`, `ci_success`, etc. based on old CIPE!
|
||||
3. Output wait status (see below)
|
||||
4. If timeout (30 min) reached → return `no_new_cipe`
|
||||
|
||||
#### Phase B: Normal Polling (after new CIPE detected)
|
||||
|
||||
Once new CIPE is detected:
|
||||
|
||||
1. Clear the new-CIPE timeout
|
||||
2. Switch to normal polling mode
|
||||
3. Process the NEW CIPE's status normally
|
||||
4. Return when actionable state reached
|
||||
|
||||
### Wait Mode Output
|
||||
|
||||
While in wait mode, output clearly that you're waiting (not processing):
|
||||
|
||||
```
|
||||
[CI Monitor] ═══════════════════════════════════════════════════════
|
||||
[CI Monitor] WAIT MODE - Expecting new CI Attempt
|
||||
[CI Monitor] Expected SHA: <expectedCommitSha>
|
||||
[CI Monitor] Previous CI Attempt: <previousCipeUrl>
|
||||
[CI Monitor] ═══════════════════════════════════════════════════════
|
||||
|
||||
[CI Monitor] Polling... (elapsed: 0m 30s)
|
||||
[CI Monitor] Still seeing previous CI Attempt (ignoring): <oldCipeUrl>
|
||||
|
||||
[CI Monitor] Polling... (elapsed: 1m 30s)
|
||||
[CI Monitor] Still seeing previous CI Attempt (ignoring): <oldCipeUrl>
|
||||
|
||||
[CI Monitor] Polling... (elapsed: 2m 30s)
|
||||
[CI Monitor] ✓ New CI Attempt detected! URL: <newCipeUrl>, SHA: <newCommitSha>
|
||||
[CI Monitor] Switching to normal polling mode...
|
||||
```
|
||||
|
||||
### Why This Matters (Context Preservation)
|
||||
|
||||
**The problem**: Stale CIPE data can be very large:
|
||||
|
||||
- `taskOutputSummary`: potentially thousands of characters of build/test output
|
||||
- `suggestedFix`: entire patch files
|
||||
- `suggestedFixReasoning`: detailed explanation
|
||||
|
||||
If subagent returns stale CIPE data to main agent, it **pollutes main agent's context** with useless information (we already processed that CIPE). This wastes valuable context window.
|
||||
|
||||
**Without wait mode:**
|
||||
|
||||
1. Poll `ci_information` → get old CIPE with huge data
|
||||
2. Return to main agent with all that stale data
|
||||
3. Main agent's context gets polluted with useless info
|
||||
4. Main agent has to process/ignore it anyway
|
||||
|
||||
**With wait mode:**
|
||||
|
||||
1. Poll `ci_information` → get old CIPE → **ignore it, don't return**
|
||||
2. Keep waiting internally (stale data stays in subagent)
|
||||
3. New CIPE appears → switch to normal mode
|
||||
4. Return to main agent with only the NEW, relevant CIPE data
|
||||
|
||||
## Polling Loop
|
||||
|
||||
### Subagent State Management
|
||||
|
||||
Maintain internal accumulated state across polls:
|
||||
|
||||
```
|
||||
accumulated_state = {}
|
||||
```
|
||||
|
||||
### Call `ci_information` MCP Tool
|
||||
|
||||
**Wait Mode (expecting new CI Attempt):**
|
||||
|
||||
```
|
||||
ci_information({
|
||||
branch: "<branch_name>",
|
||||
select: "cipeUrl,commitSha,cipeStatus"
|
||||
})
|
||||
```
|
||||
|
||||
Only fetch minimal fields needed to detect CI Attempt change. Do NOT fetch heavy fields - stale data wastes context.
|
||||
|
||||
**Normal Mode (processing CI Attempt):**
|
||||
|
||||
```
|
||||
ci_information({
|
||||
branch: "<branch_name>",
|
||||
select: "cipeStatus,cipeUrl,branch,commitSha,selfHealingStatus,verificationStatus,userAction,failedTaskIds,verifiedTaskIds,selfHealingEnabled,failureClassification,couldAutoApplyTasks,shortLink,confidence,confidenceReasoning"
|
||||
})
|
||||
```
|
||||
|
||||
Merge response into `accumulated_state` after each poll.
|
||||
|
||||
### Analyze Response
|
||||
|
||||
**If in Wait Mode** (expecting new CIPE):
|
||||
|
||||
1. Check if CIPE is new (see Two-Phase Operation above)
|
||||
2. If old CIPE → **ignore status**, output wait message, poll again
|
||||
3. If new CIPE → switch to normal mode, continue below
|
||||
|
||||
**If in Normal Mode**:
|
||||
Based on the response, decide whether to **keep polling** or **return to main agent**.
|
||||
|
||||
### Keep Polling When
|
||||
|
||||
Continue polling (with backoff) if ANY of these conditions are true:
|
||||
|
||||
| Condition | Reason |
|
||||
| --------------------------------------- | ---------------------------------------- |
|
||||
| `cipeStatus == 'IN_PROGRESS'` | CI still running |
|
||||
| `cipeStatus == 'NOT_STARTED'` | CI hasn't started yet |
|
||||
| `selfHealingStatus == 'IN_PROGRESS'` | Self-healing agent working |
|
||||
| `selfHealingStatus == 'NOT_STARTED'` | Self-healing not started yet |
|
||||
| `failureClassification == 'FLAKY_TASK'` | Auto-rerun in progress |
|
||||
| `userAction == 'APPLIED_AUTOMATICALLY'` | New CI Attempt spawning after auto-apply |
|
||||
|
||||
When `couldAutoApplyTasks == true`:
|
||||
|
||||
- `verificationStatus` = `NOT_STARTED`, `IN_PROGRESS` → keep polling (verification still in progress)
|
||||
- `verificationStatus` = `COMPLETED` → return `fix_auto_applying` (auto-apply will happen, main agent spawns wait mode subagent)
|
||||
- `verificationStatus` = `FAILED`, `NOT_EXECUTABLE` → return `fix_available` (auto-apply won't happen, needs manual action)
|
||||
|
||||
### Exponential Backoff
|
||||
|
||||
Between polls, wait with exponential backoff:
|
||||
|
||||
| Poll Attempt | Wait Time |
|
||||
| ------------ | ----------------- |
|
||||
| 1st | 60 seconds |
|
||||
| 2nd | 90 seconds |
|
||||
| 3rd+ | 120 seconds (cap) |
|
||||
|
||||
Reset to 60 seconds when state changes significantly.
|
||||
|
||||
**IMPORTANT:** Run sleep in foreground (NOT as background command). Background sleep causes "What should Claude do?" prompts when completed.
|
||||
|
||||
```bash
|
||||
# Example backoff - run in FOREGROUND
|
||||
sleep 60 # First wait
|
||||
sleep 90 # Second wait
|
||||
sleep 120 # Third and subsequent waits (capped)
|
||||
```
|
||||
|
||||
### Fetch Heavy Fields on Actionable State
|
||||
|
||||
Before returning to main agent, fetch heavy fields if the status requires them:
|
||||
|
||||
| Status | Heavy Fields Needed |
|
||||
| ------------------- | ------------------------------------------------------------------------------ |
|
||||
| `ci_success` | None |
|
||||
| `fix_auto_applying` | None |
|
||||
| `fix_available` | `taskOutputSummary,suggestedFix,suggestedFixReasoning,suggestedFixDescription` |
|
||||
| `fix_failed` | `taskOutputSummary` |
|
||||
| `no_fix` | `taskOutputSummary` |
|
||||
| `environment_issue` | None |
|
||||
| `no_new_cipe` | None |
|
||||
| `polling_timeout` | None |
|
||||
| `cipe_canceled` | None |
|
||||
| `cipe_timed_out` | None |
|
||||
|
||||
```
|
||||
# Example: fetching heavy fields for fix_available
|
||||
ci_information({
|
||||
branch: "<branch_name>",
|
||||
select: "taskOutputSummary,suggestedFix,suggestedFixReasoning,suggestedFixDescription"
|
||||
})
|
||||
```
|
||||
|
||||
Merge response into `accumulated_state`, then return merged state to main agent.
|
||||
|
||||
**Pagination:** Heavy string fields return first page only. If `hasMore` indicated, include in return format so main agent knows more content available.
|
||||
|
||||
### Return to Main Agent When
|
||||
|
||||
Return immediately with structured state if ANY of these conditions are true:
|
||||
|
||||
| Status | Condition |
|
||||
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ci_success` | `cipeStatus == 'SUCCEEDED'` |
|
||||
| `fix_auto_applying` | `selfHealingStatus == 'COMPLETED'` AND `couldAutoApplyTasks == true` AND `verificationStatus == 'COMPLETED'` |
|
||||
| `fix_available` | `selfHealingStatus == 'COMPLETED'` AND `suggestedFix != null` AND (`couldAutoApplyTasks != true` OR `verificationStatus` in (`FAILED`, `NOT_EXECUTABLE`)) |
|
||||
| `fix_failed` | `selfHealingStatus == 'FAILED'` |
|
||||
| `environment_issue` | `failureClassification == 'ENVIRONMENT_STATE'` |
|
||||
| `no_fix` | `cipeStatus == 'FAILED'` AND (`selfHealingEnabled == false` OR `selfHealingStatus == 'NOT_EXECUTABLE'`) |
|
||||
| `no_new_cipe` | `expectedCommitSha` or `previousCipeUrl` provided, but no new CI Attempt detected after 30 min |
|
||||
| `polling_timeout` | Subagent has been polling for > configured timeout (default 60 min) |
|
||||
| `cipe_canceled` | `cipeStatus == 'CANCELED'` |
|
||||
| `cipe_timed_out` | `cipeStatus == 'TIMED_OUT'` |
|
||||
|
||||
## Subagent Timeout
|
||||
|
||||
Track elapsed time. If you have been polling for more than **60 minutes** (configurable via main agent), return with `status: polling_timeout`.
|
||||
|
||||
## Return Format
|
||||
|
||||
When returning to the main agent, provide a structured response with accumulated state:
|
||||
|
||||
```
|
||||
## CI Monitor Result
|
||||
|
||||
**Status:** <status>
|
||||
**Iterations:** <count>
|
||||
**Elapsed:** <minutes>m <seconds>s
|
||||
|
||||
### CI Attempt Details
|
||||
- **Status:** <cipeStatus>
|
||||
- **URL:** <cipeUrl>
|
||||
- **Branch:** <branch>
|
||||
- **Commit:** <commitSha>
|
||||
- **Failed Tasks:** <failedTaskIds>
|
||||
- **Verified Tasks:** <verifiedTaskIds>
|
||||
|
||||
### Self-Healing Details
|
||||
- **Enabled:** <selfHealingEnabled>
|
||||
- **Status:** <selfHealingStatus>
|
||||
- **Verification:** <verificationStatus>
|
||||
- **User Action:** <userAction>
|
||||
- **Classification:** <failureClassification>
|
||||
- **Confidence:** <confidence>
|
||||
- **Confidence Reasoning:** <confidenceReasoning>
|
||||
|
||||
### Fix Information (if available)
|
||||
- **Short Link:** <shortLink>
|
||||
- **Description:** <suggestedFixDescription>
|
||||
- **Reasoning:** <suggestedFixReasoning>
|
||||
|
||||
### Task Output Summary (first page)
|
||||
<taskOutputSummary>
|
||||
[MORE_CONTENT_AVAILABLE: taskOutputSummary, pageToken: 1]
|
||||
|
||||
### Suggested Fix (first page)
|
||||
<suggestedFix>
|
||||
[MORE_CONTENT_AVAILABLE: suggestedFix, pageToken: 1]
|
||||
```
|
||||
|
||||
### Pagination Indicators
|
||||
|
||||
When a heavy field has more content available, append indicator:
|
||||
|
||||
```
|
||||
[MORE_CONTENT_AVAILABLE: <fieldName>, pageToken: <nextPage>]
|
||||
```
|
||||
|
||||
Main agent can fetch additional pages if needed using:
|
||||
|
||||
```
|
||||
ci_information({ select: "<fieldName>", pageToken: <nextPage> })
|
||||
```
|
||||
|
||||
Fields that may have pagination:
|
||||
|
||||
- `taskOutputSummary` (reverse pagination - page 0 = most recent)
|
||||
- `suggestedFix` (forward pagination - page 0 = start)
|
||||
- `suggestedFixReasoning`
|
||||
|
||||
### Return Format for `no_new_cipe`
|
||||
|
||||
When returning with `status: no_new_cipe`, include additional context:
|
||||
|
||||
```
|
||||
## CI Monitor Result
|
||||
|
||||
**Status:** no_new_cipe
|
||||
**Iterations:** <count>
|
||||
**Elapsed:** <minutes>m <seconds>s
|
||||
|
||||
### Expected CI Attempt Not Found
|
||||
- **Expected Commit SHA:** <expectedCommitSha>
|
||||
- **Previous CI Attempt URL:** <previousCipeUrl>
|
||||
- **Last Seen CI Attempt URL:** <cipeUrl>
|
||||
- **Last Seen Commit SHA:** <commitSha>
|
||||
- **New CI Attempt Timeout:** 30 minutes (exceeded)
|
||||
|
||||
### Likely Cause
|
||||
CI workflow failed before Nx tasks could run (e.g., install step, checkout, auth).
|
||||
Check your CI provider logs for the commit <expectedCommitSha>.
|
||||
|
||||
### Last Known CI Attempt State
|
||||
- **Status:** <cipeStatus>
|
||||
- **Branch:** <branch>
|
||||
```
|
||||
|
||||
## Status Reporting (Verbosity-Controlled)
|
||||
|
||||
Output is controlled by the `verbosity` parameter from the main agent:
|
||||
|
||||
| Level | What to Output |
|
||||
| --------- | ----------------------------------------------------------------- |
|
||||
| `minimal` | No intermediate output. Only return final result when actionable. |
|
||||
| `medium` | Output only on significant state changes (not every poll). |
|
||||
| `verbose` | Output detailed phase information after every poll. |
|
||||
|
||||
### Minimal Verbosity
|
||||
|
||||
No output during polling. Poll silently and return when done.
|
||||
|
||||
### Medium Verbosity (Default)
|
||||
|
||||
Output **only when state changes significantly** to save context tokens:
|
||||
|
||||
- `cipeStatus` changes (e.g., IN_PROGRESS → FAILED)
|
||||
- `selfHealingStatus` changes (e.g., IN_PROGRESS → COMPLETED)
|
||||
- New CI Attempt detected (in wait mode)
|
||||
|
||||
Format: single line, no decorators:
|
||||
|
||||
```
|
||||
[CI Monitor] CI: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 4m
|
||||
```
|
||||
|
||||
### Verbose Verbosity
|
||||
|
||||
Output detailed phase box after every poll:
|
||||
|
||||
```
|
||||
[CI Monitor] ─────────────────────────────────────────────────────
|
||||
[CI Monitor] Iteration <N> | Elapsed: <X>m <Y>s
|
||||
[CI Monitor]
|
||||
[CI Monitor] CI Status: <cipeStatus>
|
||||
[CI Monitor] Self-Healing: <selfHealingStatus>
|
||||
[CI Monitor] Verification: <verificationStatus>
|
||||
[CI Monitor] Classification: <failureClassification>
|
||||
[CI Monitor]
|
||||
[CI Monitor] → <human-readable phase description>
|
||||
[CI Monitor] ─────────────────────────────────────────────────────
|
||||
```
|
||||
|
||||
### Phase Descriptions (for verbose output)
|
||||
|
||||
| Status Combo | Description |
|
||||
| ----------------------------------------------------------------------------------------- | ------------------------------------------- |
|
||||
| `cipeStatus: IN_PROGRESS` | "CI running..." |
|
||||
| `cipeStatus: NOT_STARTED` | "Waiting for CI to start..." |
|
||||
| `cipeStatus: FAILED` + `selfHealingStatus: NOT_STARTED` | "CI failed. Self-healing starting..." |
|
||||
| `cipeStatus: FAILED` + `selfHealingStatus: IN_PROGRESS` | "CI failed. Self-healing generating fix..." |
|
||||
| `cipeStatus: FAILED` + `selfHealingStatus: COMPLETED` + `verificationStatus: IN_PROGRESS` | "Fix generated! Verification running..." |
|
||||
| `cipeStatus: FAILED` + `selfHealingStatus: COMPLETED` + `verificationStatus: COMPLETED` | "Fix ready! Verified successfully." |
|
||||
| `cipeStatus: FAILED` + `selfHealingStatus: COMPLETED` + `verificationStatus: FAILED` | "Fix generated but verification failed." |
|
||||
| `cipeStatus: FAILED` + `selfHealingStatus: FAILED` | "Self-healing could not generate a fix." |
|
||||
| `cipeStatus: SUCCEEDED` | "CI passed!" |
|
||||
|
||||
## Important Notes
|
||||
|
||||
- You do NOT make apply/reject decisions - that's the main agent's job
|
||||
- You do NOT perform git operations
|
||||
- You only poll and report state
|
||||
- Respect the `verbosity` parameter for output (default: medium)
|
||||
- If `ci_information` returns an error, wait and retry (count as failed poll)
|
||||
- Track consecutive failures - if 5 consecutive failures, return with `status: error`
|
||||
- When expecting new CI Attempt, track the 30-minute new-CI-Attempt timeout separately from the main polling timeout
|
||||
@@ -0,0 +1,437 @@
|
||||
---
|
||||
description: Monitor Nx Cloud CI pipeline and handle self-healing fixes automatically. Checks for Nx Cloud connection before starting.
|
||||
argument-hint: '[instructions] [--max-cycles N] [--timeout MINUTES] [--verbosity minimal|medium|verbose] [--branch BRANCH] [--fresh] [--auto-fix-workflow] [--new-cipe-timeout MINUTES]'
|
||||
---
|
||||
|
||||
# CI Monitor Command
|
||||
|
||||
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
|
||||
|
||||
## Context
|
||||
|
||||
- **Current Branch:** !`git branch --show-current`
|
||||
- **Current Commit:** !`git rev-parse --short HEAD`
|
||||
- **Remote Status:** !`git status -sb | head -1`
|
||||
|
||||
## User Instructions
|
||||
|
||||
$ARGUMENTS
|
||||
|
||||
**Important:** If user provides specific instructions, respect them over default behaviors described below.
|
||||
|
||||
## Configuration Defaults
|
||||
|
||||
| Setting | Default | Description |
|
||||
| ------------------------- | ------------- | ------------------------------------------------------------------- |
|
||||
| `--max-cycles` | 10 | Maximum CIPE cycles before timeout |
|
||||
| `--timeout` | 120 | Maximum duration in minutes |
|
||||
| `--verbosity` | medium | Output level: minimal, medium, verbose |
|
||||
| `--branch` | (auto-detect) | Branch to monitor |
|
||||
| `--subagent-timeout` | 60 | Subagent polling timeout in minutes |
|
||||
| `--fresh` | false | Ignore previous context, start fresh |
|
||||
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
|
||||
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
|
||||
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
|
||||
|
||||
Parse any overrides from `$ARGUMENTS` and merge with defaults.
|
||||
|
||||
## Nx Cloud Connection Check
|
||||
|
||||
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
|
||||
|
||||
### Step 0: Verify Nx Cloud Connection
|
||||
|
||||
1. **Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
|
||||
2. **If `nx.json` missing OR neither property exists** → exit with:
|
||||
```
|
||||
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
|
||||
```
|
||||
3. **If connected** → continue to main loop
|
||||
|
||||
## Session Context Behavior
|
||||
|
||||
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
|
||||
|
||||
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
|
||||
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
|
||||
- **For a completely clean slate:** Exit Claude Code and restart `claude`
|
||||
|
||||
## Default Behaviors by Status
|
||||
|
||||
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
|
||||
|
||||
| Status | Default Behavior |
|
||||
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ci_success` | Exit with success. Log "CI passed successfully!" |
|
||||
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
|
||||
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
|
||||
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
|
||||
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
|
||||
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
|
||||
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
|
||||
| `polling_timeout` | Subagent polling timeout reached. Exit with timeout. |
|
||||
| `cipe_canceled` | CIPE was canceled. Exit with canceled status. |
|
||||
| `cipe_timed_out` | CIPE timed out. Exit with timeout status. |
|
||||
| `error` | Increment `no_progress_count`. If >= 3 → exit with circuit breaker. Otherwise wait 60s and loop. |
|
||||
|
||||
### Fix Available Decision Logic
|
||||
|
||||
When subagent returns `fix_available`, main agent compares `failedTaskIds` vs `verifiedTaskIds`:
|
||||
|
||||
#### Step 1: Categorize Tasks
|
||||
|
||||
1. **Verified tasks** = tasks in both `failedTaskIds` AND `verifiedTaskIds`
|
||||
2. **Unverified tasks** = tasks in `failedTaskIds` but NOT in `verifiedTaskIds`
|
||||
3. **E2E tasks** = unverified tasks where target contains "e2e" (task format: `<project>:<target>` or `<project>:<target>:<config>`)
|
||||
4. **Verifiable tasks** = unverified tasks that are NOT e2e
|
||||
|
||||
#### Step 2: Determine Path
|
||||
|
||||
| Condition | Path |
|
||||
| --------------------------------------- | ---------------------------------------- |
|
||||
| No unverified tasks (all verified) | Apply via MCP |
|
||||
| Unverified tasks exist, but ALL are e2e | Apply via MCP (treat as verified enough) |
|
||||
| Verifiable tasks exist | Local verification flow |
|
||||
|
||||
#### Step 3a: Apply via MCP (fully/e2e-only verified)
|
||||
|
||||
- Call `update_self_healing_fix({ shortLink, action: "APPLY" })`
|
||||
- Record `last_cipe_url`, spawn subagent in wait mode
|
||||
|
||||
#### Step 3b: Local Verification Flow
|
||||
|
||||
When verifiable (non-e2e) unverified tasks exist:
|
||||
|
||||
1. **Detect package manager:**
|
||||
- `pnpm-lock.yaml` exists → `pnpm nx`
|
||||
- `yarn.lock` exists → `yarn nx`
|
||||
- Otherwise → `npx nx`
|
||||
|
||||
2. **Run verifiable tasks in parallel:**
|
||||
- Spawn `general` subagents to run each task concurrently
|
||||
- Each subagent runs: `<pm> nx run <taskId>`
|
||||
- Collect pass/fail results from all subagents
|
||||
|
||||
3. **Evaluate results:**
|
||||
|
||||
| Result | Action |
|
||||
| ------------------------- | ---------------------------- |
|
||||
| ALL verifiable tasks pass | Apply via MCP |
|
||||
| ANY verifiable task fails | Apply-locally + enhance flow |
|
||||
|
||||
4. **Apply-locally + enhance flow:**
|
||||
- Run `nx apply-locally <shortLink>`
|
||||
- Enhance the code to fix failing tasks
|
||||
- Run failing tasks again to verify fix
|
||||
- If still failing → increment `local_verify_count`, loop back to enhance
|
||||
- If passing → commit and push, record `expected_commit_sha`, spawn subagent in wait mode
|
||||
|
||||
5. **Track attempts** (wraps step 4):
|
||||
- Increment `local_verify_count` after each enhance cycle
|
||||
- If `local_verify_count >= local_verify_attempts` (default: 3):
|
||||
- Get code in commit-able state
|
||||
- Commit and push with message indicating local verification failed
|
||||
- Report to user:
|
||||
```
|
||||
[ci-monitor] Local verification failed after <N> attempts. Pushed to CI for final validation. Failed: <taskIds>
|
||||
```
|
||||
- Record `expected_commit_sha`, spawn subagent in wait mode (let CI be final judge)
|
||||
|
||||
#### Commit Message Format
|
||||
|
||||
```bash
|
||||
git commit -m "fix(<projects>): <brief description>
|
||||
|
||||
Failed tasks: <taskId1>, <taskId2>
|
||||
Local verification: passed|enhanced|failed-pushing-to-ci"
|
||||
```
|
||||
|
||||
### Unverified Fix Flow (No Verification Attempted)
|
||||
|
||||
When `verificationStatus` is `FAILED`, `NOT_EXECUTABLE`, or fix has `couldAutoApplyTasks != true` with no verification:
|
||||
|
||||
- Analyze fix content (`suggestedFix`, `suggestedFixReasoning`, `taskOutputSummary`)
|
||||
- If fix looks correct → apply via MCP
|
||||
- If fix needs enhancement → use Apply Locally + Enhance Flow above
|
||||
- If fix is wrong → reject via MCP, fix from scratch, commit, push
|
||||
|
||||
### Auto-Apply Eligibility
|
||||
|
||||
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
|
||||
|
||||
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
|
||||
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
|
||||
|
||||
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
|
||||
|
||||
### Apply vs Reject vs Apply Locally
|
||||
|
||||
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
|
||||
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
|
||||
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
|
||||
|
||||
### Apply Locally + Enhance Flow
|
||||
|
||||
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
|
||||
|
||||
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
|
||||
2. Make additional changes as needed
|
||||
3. Commit and push:
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix: resolve <failedTaskIds>"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
4. Loop to poll for new CIPE
|
||||
|
||||
### Reject + Fix From Scratch Flow
|
||||
|
||||
When the fix is completely wrong:
|
||||
|
||||
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
|
||||
2. Fix the issue from scratch locally
|
||||
3. Commit and push:
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix: resolve <failedTaskIds>"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
4. Loop to poll for new CIPE
|
||||
|
||||
### Environment Issue Handling
|
||||
|
||||
When `failureClassification == 'ENVIRONMENT_STATE'`:
|
||||
|
||||
1. Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`
|
||||
2. New CIPE spawns automatically (no local git operations needed)
|
||||
3. Loop to poll for new CIPE with `previousCipeUrl` set
|
||||
|
||||
### No-New-CIPE Handling
|
||||
|
||||
When `status == 'no_new_cipe'`:
|
||||
|
||||
This means the expected CIPE was never created - CI likely failed before Nx tasks could run.
|
||||
|
||||
1. **Report to user:**
|
||||
|
||||
```
|
||||
[ci-monitor] No CI attempt for <sha> after 10 min. Check CI provider for pre-Nx failures (install, checkout, auth). Last CI attempt: <previousCipeUrl>
|
||||
```
|
||||
|
||||
2. **If user configured auto-fix attempts** (e.g., `--auto-fix-workflow`):
|
||||
- Detect package manager: check for `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`
|
||||
- Run install to update lockfile:
|
||||
```bash
|
||||
pnpm install # or npm install / yarn install
|
||||
```
|
||||
- If lockfile changed:
|
||||
```bash
|
||||
git add pnpm-lock.yaml # or appropriate lockfile
|
||||
git commit -m "chore: update lockfile"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
- Record new commit SHA, loop to poll with `expectedCommitSha`
|
||||
|
||||
3. **Otherwise:** Exit with `no_new_cipe` status, providing guidance for user to investigate
|
||||
|
||||
## Exit Conditions
|
||||
|
||||
Exit the monitoring loop when ANY of these conditions are met:
|
||||
|
||||
| Condition | Exit Type |
|
||||
| ------------------------------------------- | ---------------- |
|
||||
| CI passes (`cipeStatus == 'SUCCEEDED'`) | Success |
|
||||
| Max CIPE cycles reached | Timeout |
|
||||
| Max duration reached | Timeout |
|
||||
| 3 consecutive no-progress iterations | Circuit breaker |
|
||||
| No fix available and local fix not possible | Failure |
|
||||
| No new CIPE and auto-fix not configured | Pre-CIPE failure |
|
||||
| User cancels | Cancelled |
|
||||
|
||||
## Main Loop
|
||||
|
||||
### Step 1: Initialize Tracking
|
||||
|
||||
```
|
||||
cycle_count = 0
|
||||
start_time = now()
|
||||
no_progress_count = 0
|
||||
local_verify_count = 0
|
||||
last_state = null
|
||||
last_cipe_url = null
|
||||
expected_commit_sha = null
|
||||
```
|
||||
|
||||
### Step 2: Spawn Subagent
|
||||
|
||||
Spawn the `ci-watcher` subagent to poll CI status:
|
||||
|
||||
**Fresh start (first spawn, no expected CIPE):**
|
||||
|
||||
```
|
||||
Task(
|
||||
agent: "ci-watcher",
|
||||
prompt: "Monitor CI for branch '<branch>'.
|
||||
Subagent timeout: <subagent-timeout> minutes.
|
||||
New-CIPE timeout: <new-cipe-timeout> minutes.
|
||||
Verbosity: <verbosity>."
|
||||
)
|
||||
```
|
||||
|
||||
**After action that triggers new CIPE (wait mode):**
|
||||
|
||||
```
|
||||
Task(
|
||||
agent: "ci-watcher",
|
||||
prompt: "Monitor CI for branch '<branch>'.
|
||||
Subagent timeout: <subagent-timeout> minutes.
|
||||
New-CIPE timeout: <new-cipe-timeout> minutes.
|
||||
Verbosity: <verbosity>.
|
||||
|
||||
WAIT MODE: A new CIPE should spawn. Ignore old CIPE until new one appears.
|
||||
Expected commit SHA: <expected_commit_sha>
|
||||
Previous CIPE URL: <last_cipe_url>"
|
||||
)
|
||||
```
|
||||
|
||||
### Step 3: Handle Subagent Response
|
||||
|
||||
When subagent returns:
|
||||
|
||||
1. Check the returned status
|
||||
2. Look up default behavior in the table above
|
||||
3. Check if user instructions override the default
|
||||
4. Execute the appropriate action
|
||||
5. **If action expects new CIPE**, update tracking (see Step 3a)
|
||||
6. If action results in looping, go to Step 2
|
||||
|
||||
### Step 3a: Track State for New-CIPE Detection
|
||||
|
||||
After actions that should trigger a new CIPE, record state before looping:
|
||||
|
||||
| Action | What to Track | Subagent Mode |
|
||||
| ----------------------------- | --------------------------------------------- | ------------- |
|
||||
| Fix auto-applying | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| Apply via MCP | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| Apply locally + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Reject + fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Fix failed + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| No fix + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Environment rerun | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| No-new-CIPE + auto-fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
|
||||
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
|
||||
|
||||
- Subagent will **completely ignore** the old/stale CIPE
|
||||
- Subagent will only wait for new CIPE to appear
|
||||
- Subagent will NOT return to main agent with stale CIPE data
|
||||
- Once new CIPE detected, subagent switches to normal polling
|
||||
|
||||
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
|
||||
|
||||
### Step 4: Progress Tracking
|
||||
|
||||
After each action:
|
||||
|
||||
- If state changed significantly → reset `no_progress_count = 0`
|
||||
- If state unchanged → `no_progress_count++`
|
||||
- On new CI attempt detected → reset `local_verify_count = 0`
|
||||
|
||||
## Status Reporting
|
||||
|
||||
Based on verbosity level:
|
||||
|
||||
| Level | What to Report |
|
||||
| --------- | -------------------------------------------------------------------------- |
|
||||
| `minimal` | Only final result (success/failure/timeout) |
|
||||
| `medium` | State changes + periodic updates ("Cycle N \| Elapsed: Xm \| Status: ...") |
|
||||
| `verbose` | All of medium + full subagent responses, git outputs, MCP responses |
|
||||
|
||||
## User Instruction Examples
|
||||
|
||||
Users can override default behaviors:
|
||||
|
||||
| Instruction | Effect |
|
||||
| ------------------------------------------------ | --------------------------------------------- |
|
||||
| "never auto-apply" | Always prompt before applying any fix |
|
||||
| "always ask before git push" | Prompt before each push |
|
||||
| "reject any fix for e2e tasks" | Auto-reject if `failedTaskIds` contains e2e |
|
||||
| "apply all fixes regardless of verification" | Skip verification check, apply everything |
|
||||
| "if confidence < 70, reject" | Check confidence field before applying |
|
||||
| "run 'nx affected -t typecheck' before applying" | Add local verification step |
|
||||
| "auto-fix workflow failures" | Attempt lockfile updates on pre-CIPE failures |
|
||||
| "wait 45 min for new CIPE" | Override new-CIPE timeout (default: 10 min) |
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | Action |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------- |
|
||||
| Git rebase conflict | Report to user, exit |
|
||||
| `nx apply-locally` fails | Report to user, attempt manual patch or exit |
|
||||
| MCP tool error | Retry once, if fails report to user |
|
||||
| Subagent spawn failure | Retry once, if fails exit with error |
|
||||
| No new CIPE detected | If `--auto-fix-workflow`, try lockfile update; otherwise report to user with guidance |
|
||||
| Lockfile auto-fix fails | Report to user, exit with guidance to check CI logs |
|
||||
|
||||
## Example Session
|
||||
|
||||
### Example 1: Normal Flow with Self-Healing (medium verbosity)
|
||||
|
||||
```
|
||||
[ci-monitor] Starting CI monitor for branch 'feature/add-auth'
|
||||
[ci-monitor] Config: max-cycles=5, timeout=120m, verbosity=medium
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] CI attempt: IN_PROGRESS | Self-Healing: NOT_STARTED | Elapsed: 1m
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 3m
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 5m
|
||||
|
||||
[ci-monitor] Fix available! Verification: COMPLETED
|
||||
[ci-monitor] Applying fix via MCP...
|
||||
[ci-monitor] Fix applied in CI. Waiting for new CI attempt...
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] New CI attempt detected!
|
||||
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 8m
|
||||
|
||||
[ci-monitor] CI passed successfully!
|
||||
|
||||
[ci-monitor] Summary:
|
||||
- Total cycles: 2
|
||||
- Total time: 12m 34s
|
||||
- Fixes applied: 1
|
||||
- Result: SUCCESS
|
||||
```
|
||||
|
||||
### Example 2: Pre-CI Failure (medium verbosity)
|
||||
|
||||
```
|
||||
[ci-monitor] Starting CI monitor for branch 'feature/add-products'
|
||||
[ci-monitor] Config: max-cycles=5, timeout=120m, auto-fix-workflow=true
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 2m
|
||||
|
||||
[ci-monitor] Applying fix locally, enhancing, and pushing...
|
||||
[ci-monitor] Committed: abc1234
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] Waiting for new CI attempt... (expected SHA: abc1234)
|
||||
[CI Monitor] ⚠️ CI attempt timeout (10 min). Returning no_new_cipe.
|
||||
|
||||
[ci-monitor] Status: no_new_cipe
|
||||
[ci-monitor] --auto-fix-workflow enabled. Attempting lockfile update...
|
||||
[ci-monitor] Lockfile updated. Committed: def5678
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] New CI attempt detected!
|
||||
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 18m
|
||||
|
||||
[ci-monitor] CI passed successfully!
|
||||
|
||||
[ci-monitor] Summary:
|
||||
- Total cycles: 3
|
||||
- Total time: 22m 15s
|
||||
- Fixes applied: 1 (self-healing) + 1 (lockfile)
|
||||
- Result: SUCCESS
|
||||
```
|
||||
@@ -0,0 +1,437 @@
|
||||
---
|
||||
name: ci-monitor
|
||||
description: Monitor Nx Cloud CI pipeline and handle self-healing fixes automatically. Checks for Nx Cloud connection before starting.
|
||||
---
|
||||
|
||||
# CI Monitor Command
|
||||
|
||||
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
|
||||
|
||||
## Context
|
||||
|
||||
- **Current Branch:** !`git branch --show-current`
|
||||
- **Current Commit:** !`git rev-parse --short HEAD`
|
||||
- **Remote Status:** !`git status -sb | head -1`
|
||||
|
||||
## User Instructions
|
||||
|
||||
$ARGUMENTS
|
||||
|
||||
**Important:** If user provides specific instructions, respect them over default behaviors described below.
|
||||
|
||||
## Configuration Defaults
|
||||
|
||||
| Setting | Default | Description |
|
||||
| ------------------------- | ------------- | ------------------------------------------------------------------- |
|
||||
| `--max-cycles` | 10 | Maximum CIPE cycles before timeout |
|
||||
| `--timeout` | 120 | Maximum duration in minutes |
|
||||
| `--verbosity` | medium | Output level: minimal, medium, verbose |
|
||||
| `--branch` | (auto-detect) | Branch to monitor |
|
||||
| `--subagent-timeout` | 60 | Subagent polling timeout in minutes |
|
||||
| `--fresh` | false | Ignore previous context, start fresh |
|
||||
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
|
||||
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
|
||||
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
|
||||
|
||||
Parse any overrides from `$ARGUMENTS` and merge with defaults.
|
||||
|
||||
## Nx Cloud Connection Check
|
||||
|
||||
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
|
||||
|
||||
### Step 0: Verify Nx Cloud Connection
|
||||
|
||||
1. **Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
|
||||
2. **If `nx.json` missing OR neither property exists** → exit with:
|
||||
```
|
||||
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
|
||||
```
|
||||
3. **If connected** → continue to main loop
|
||||
|
||||
## Session Context Behavior
|
||||
|
||||
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
|
||||
|
||||
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
|
||||
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
|
||||
- **For a completely clean slate:** Exit Claude Code and restart `claude`
|
||||
|
||||
## Default Behaviors by Status
|
||||
|
||||
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
|
||||
|
||||
| Status | Default Behavior |
|
||||
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ci_success` | Exit with success. Log "CI passed successfully!" |
|
||||
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
|
||||
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
|
||||
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
|
||||
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
|
||||
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
|
||||
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
|
||||
| `polling_timeout` | Subagent polling timeout reached. Exit with timeout. |
|
||||
| `cipe_canceled` | CIPE was canceled. Exit with canceled status. |
|
||||
| `cipe_timed_out` | CIPE timed out. Exit with timeout status. |
|
||||
| `error` | Increment `no_progress_count`. If >= 3 → exit with circuit breaker. Otherwise wait 60s and loop. |
|
||||
|
||||
### Fix Available Decision Logic
|
||||
|
||||
When subagent returns `fix_available`, main agent compares `failedTaskIds` vs `verifiedTaskIds`:
|
||||
|
||||
#### Step 1: Categorize Tasks
|
||||
|
||||
1. **Verified tasks** = tasks in both `failedTaskIds` AND `verifiedTaskIds`
|
||||
2. **Unverified tasks** = tasks in `failedTaskIds` but NOT in `verifiedTaskIds`
|
||||
3. **E2E tasks** = unverified tasks where target contains "e2e" (task format: `<project>:<target>` or `<project>:<target>:<config>`)
|
||||
4. **Verifiable tasks** = unverified tasks that are NOT e2e
|
||||
|
||||
#### Step 2: Determine Path
|
||||
|
||||
| Condition | Path |
|
||||
| --------------------------------------- | ---------------------------------------- |
|
||||
| No unverified tasks (all verified) | Apply via MCP |
|
||||
| Unverified tasks exist, but ALL are e2e | Apply via MCP (treat as verified enough) |
|
||||
| Verifiable tasks exist | Local verification flow |
|
||||
|
||||
#### Step 3a: Apply via MCP (fully/e2e-only verified)
|
||||
|
||||
- Call `update_self_healing_fix({ shortLink, action: "APPLY" })`
|
||||
- Record `last_cipe_url`, spawn subagent in wait mode
|
||||
|
||||
#### Step 3b: Local Verification Flow
|
||||
|
||||
When verifiable (non-e2e) unverified tasks exist:
|
||||
|
||||
1. **Detect package manager:**
|
||||
- `pnpm-lock.yaml` exists → `pnpm nx`
|
||||
- `yarn.lock` exists → `yarn nx`
|
||||
- Otherwise → `npx nx`
|
||||
|
||||
2. **Run verifiable tasks in parallel:**
|
||||
- Spawn `general` subagents to run each task concurrently
|
||||
- Each subagent runs: `<pm> nx run <taskId>`
|
||||
- Collect pass/fail results from all subagents
|
||||
|
||||
3. **Evaluate results:**
|
||||
|
||||
| Result | Action |
|
||||
| ------------------------- | ---------------------------- |
|
||||
| ALL verifiable tasks pass | Apply via MCP |
|
||||
| ANY verifiable task fails | Apply-locally + enhance flow |
|
||||
|
||||
4. **Apply-locally + enhance flow:**
|
||||
- Run `nx apply-locally <shortLink>`
|
||||
- Enhance the code to fix failing tasks
|
||||
- Run failing tasks again to verify fix
|
||||
- If still failing → increment `local_verify_count`, loop back to enhance
|
||||
- If passing → commit and push, record `expected_commit_sha`, spawn subagent in wait mode
|
||||
|
||||
5. **Track attempts** (wraps step 4):
|
||||
- Increment `local_verify_count` after each enhance cycle
|
||||
- If `local_verify_count >= local_verify_attempts` (default: 3):
|
||||
- Get code in commit-able state
|
||||
- Commit and push with message indicating local verification failed
|
||||
- Report to user:
|
||||
```
|
||||
[ci-monitor] Local verification failed after <N> attempts. Pushed to CI for final validation. Failed: <taskIds>
|
||||
```
|
||||
- Record `expected_commit_sha`, spawn subagent in wait mode (let CI be final judge)
|
||||
|
||||
#### Commit Message Format
|
||||
|
||||
```bash
|
||||
git commit -m "fix(<projects>): <brief description>
|
||||
|
||||
Failed tasks: <taskId1>, <taskId2>
|
||||
Local verification: passed|enhanced|failed-pushing-to-ci"
|
||||
```
|
||||
|
||||
### Unverified Fix Flow (No Verification Attempted)
|
||||
|
||||
When `verificationStatus` is `FAILED`, `NOT_EXECUTABLE`, or fix has `couldAutoApplyTasks != true` with no verification:
|
||||
|
||||
- Analyze fix content (`suggestedFix`, `suggestedFixReasoning`, `taskOutputSummary`)
|
||||
- If fix looks correct → apply via MCP
|
||||
- If fix needs enhancement → use Apply Locally + Enhance Flow above
|
||||
- If fix is wrong → reject via MCP, fix from scratch, commit, push
|
||||
|
||||
### Auto-Apply Eligibility
|
||||
|
||||
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
|
||||
|
||||
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
|
||||
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
|
||||
|
||||
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
|
||||
|
||||
### Apply vs Reject vs Apply Locally
|
||||
|
||||
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
|
||||
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
|
||||
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
|
||||
|
||||
### Apply Locally + Enhance Flow
|
||||
|
||||
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
|
||||
|
||||
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
|
||||
2. Make additional changes as needed
|
||||
3. Commit and push:
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix: resolve <failedTaskIds>"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
4. Loop to poll for new CIPE
|
||||
|
||||
### Reject + Fix From Scratch Flow
|
||||
|
||||
When the fix is completely wrong:
|
||||
|
||||
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
|
||||
2. Fix the issue from scratch locally
|
||||
3. Commit and push:
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix: resolve <failedTaskIds>"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
4. Loop to poll for new CIPE
|
||||
|
||||
### Environment Issue Handling
|
||||
|
||||
When `failureClassification == 'ENVIRONMENT_STATE'`:
|
||||
|
||||
1. Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`
|
||||
2. New CIPE spawns automatically (no local git operations needed)
|
||||
3. Loop to poll for new CIPE with `previousCipeUrl` set
|
||||
|
||||
### No-New-CIPE Handling
|
||||
|
||||
When `status == 'no_new_cipe'`:
|
||||
|
||||
This means the expected CIPE was never created - CI likely failed before Nx tasks could run.
|
||||
|
||||
1. **Report to user:**
|
||||
|
||||
```
|
||||
[ci-monitor] No CI attempt for <sha> after 10 min. Check CI provider for pre-Nx failures (install, checkout, auth). Last CI attempt: <previousCipeUrl>
|
||||
```
|
||||
|
||||
2. **If user configured auto-fix attempts** (e.g., `--auto-fix-workflow`):
|
||||
- Detect package manager: check for `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`
|
||||
- Run install to update lockfile:
|
||||
```bash
|
||||
pnpm install # or npm install / yarn install
|
||||
```
|
||||
- If lockfile changed:
|
||||
```bash
|
||||
git add pnpm-lock.yaml # or appropriate lockfile
|
||||
git commit -m "chore: update lockfile"
|
||||
git push origin $(git branch --show-current)
|
||||
```
|
||||
- Record new commit SHA, loop to poll with `expectedCommitSha`
|
||||
|
||||
3. **Otherwise:** Exit with `no_new_cipe` status, providing guidance for user to investigate
|
||||
|
||||
## Exit Conditions
|
||||
|
||||
Exit the monitoring loop when ANY of these conditions are met:
|
||||
|
||||
| Condition | Exit Type |
|
||||
| ------------------------------------------- | ---------------- |
|
||||
| CI passes (`cipeStatus == 'SUCCEEDED'`) | Success |
|
||||
| Max CIPE cycles reached | Timeout |
|
||||
| Max duration reached | Timeout |
|
||||
| 3 consecutive no-progress iterations | Circuit breaker |
|
||||
| No fix available and local fix not possible | Failure |
|
||||
| No new CIPE and auto-fix not configured | Pre-CIPE failure |
|
||||
| User cancels | Cancelled |
|
||||
|
||||
## Main Loop
|
||||
|
||||
### Step 1: Initialize Tracking
|
||||
|
||||
```
|
||||
cycle_count = 0
|
||||
start_time = now()
|
||||
no_progress_count = 0
|
||||
local_verify_count = 0
|
||||
last_state = null
|
||||
last_cipe_url = null
|
||||
expected_commit_sha = null
|
||||
```
|
||||
|
||||
### Step 2: Spawn Subagent
|
||||
|
||||
Spawn the `ci-watcher` subagent to poll CI status:
|
||||
|
||||
**Fresh start (first spawn, no expected CIPE):**
|
||||
|
||||
```
|
||||
Task(
|
||||
agent: "ci-watcher",
|
||||
prompt: "Monitor CI for branch '<branch>'.
|
||||
Subagent timeout: <subagent-timeout> minutes.
|
||||
New-CIPE timeout: <new-cipe-timeout> minutes.
|
||||
Verbosity: <verbosity>."
|
||||
)
|
||||
```
|
||||
|
||||
**After action that triggers new CIPE (wait mode):**
|
||||
|
||||
```
|
||||
Task(
|
||||
agent: "ci-watcher",
|
||||
prompt: "Monitor CI for branch '<branch>'.
|
||||
Subagent timeout: <subagent-timeout> minutes.
|
||||
New-CIPE timeout: <new-cipe-timeout> minutes.
|
||||
Verbosity: <verbosity>.
|
||||
|
||||
WAIT MODE: A new CIPE should spawn. Ignore old CIPE until new one appears.
|
||||
Expected commit SHA: <expected_commit_sha>
|
||||
Previous CIPE URL: <last_cipe_url>"
|
||||
)
|
||||
```
|
||||
|
||||
### Step 3: Handle Subagent Response
|
||||
|
||||
When subagent returns:
|
||||
|
||||
1. Check the returned status
|
||||
2. Look up default behavior in the table above
|
||||
3. Check if user instructions override the default
|
||||
4. Execute the appropriate action
|
||||
5. **If action expects new CIPE**, update tracking (see Step 3a)
|
||||
6. If action results in looping, go to Step 2
|
||||
|
||||
### Step 3a: Track State for New-CIPE Detection
|
||||
|
||||
After actions that should trigger a new CIPE, record state before looping:
|
||||
|
||||
| Action | What to Track | Subagent Mode |
|
||||
| ----------------------------- | --------------------------------------------- | ------------- |
|
||||
| Fix auto-applying | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| Apply via MCP | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| Apply locally + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Reject + fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Fix failed + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| No fix + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
| Environment rerun | `last_cipe_url = current cipeUrl` | Wait mode |
|
||||
| No-new-CIPE + auto-fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
|
||||
|
||||
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
|
||||
|
||||
- Subagent will **completely ignore** the old/stale CIPE
|
||||
- Subagent will only wait for new CIPE to appear
|
||||
- Subagent will NOT return to main agent with stale CIPE data
|
||||
- Once new CIPE detected, subagent switches to normal polling
|
||||
|
||||
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
|
||||
|
||||
### Step 4: Progress Tracking
|
||||
|
||||
After each action:
|
||||
|
||||
- If state changed significantly → reset `no_progress_count = 0`
|
||||
- If state unchanged → `no_progress_count++`
|
||||
- On new CI attempt detected → reset `local_verify_count = 0`
|
||||
|
||||
## Status Reporting
|
||||
|
||||
Based on verbosity level:
|
||||
|
||||
| Level | What to Report |
|
||||
| --------- | -------------------------------------------------------------------------- |
|
||||
| `minimal` | Only final result (success/failure/timeout) |
|
||||
| `medium` | State changes + periodic updates ("Cycle N \| Elapsed: Xm \| Status: ...") |
|
||||
| `verbose` | All of medium + full subagent responses, git outputs, MCP responses |
|
||||
|
||||
## User Instruction Examples
|
||||
|
||||
Users can override default behaviors:
|
||||
|
||||
| Instruction | Effect |
|
||||
| ------------------------------------------------ | --------------------------------------------- |
|
||||
| "never auto-apply" | Always prompt before applying any fix |
|
||||
| "always ask before git push" | Prompt before each push |
|
||||
| "reject any fix for e2e tasks" | Auto-reject if `failedTaskIds` contains e2e |
|
||||
| "apply all fixes regardless of verification" | Skip verification check, apply everything |
|
||||
| "if confidence < 70, reject" | Check confidence field before applying |
|
||||
| "run 'nx affected -t typecheck' before applying" | Add local verification step |
|
||||
| "auto-fix workflow failures" | Attempt lockfile updates on pre-CIPE failures |
|
||||
| "wait 45 min for new CIPE" | Override new-CIPE timeout (default: 10 min) |
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | Action |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------- |
|
||||
| Git rebase conflict | Report to user, exit |
|
||||
| `nx apply-locally` fails | Report to user, attempt manual patch or exit |
|
||||
| MCP tool error | Retry once, if fails report to user |
|
||||
| Subagent spawn failure | Retry once, if fails exit with error |
|
||||
| No new CIPE detected | If `--auto-fix-workflow`, try lockfile update; otherwise report to user with guidance |
|
||||
| Lockfile auto-fix fails | Report to user, exit with guidance to check CI logs |
|
||||
|
||||
## Example Session
|
||||
|
||||
### Example 1: Normal Flow with Self-Healing (medium verbosity)
|
||||
|
||||
```
|
||||
[ci-monitor] Starting CI monitor for branch 'feature/add-auth'
|
||||
[ci-monitor] Config: max-cycles=5, timeout=120m, verbosity=medium
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] CI attempt: IN_PROGRESS | Self-Healing: NOT_STARTED | Elapsed: 1m
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 3m
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 5m
|
||||
|
||||
[ci-monitor] Fix available! Verification: COMPLETED
|
||||
[ci-monitor] Applying fix via MCP...
|
||||
[ci-monitor] Fix applied in CI. Waiting for new CI attempt...
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] New CI attempt detected!
|
||||
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 8m
|
||||
|
||||
[ci-monitor] CI passed successfully!
|
||||
|
||||
[ci-monitor] Summary:
|
||||
- Total cycles: 2
|
||||
- Total time: 12m 34s
|
||||
- Fixes applied: 1
|
||||
- Result: SUCCESS
|
||||
```
|
||||
|
||||
### Example 2: Pre-CI Failure (medium verbosity)
|
||||
|
||||
```
|
||||
[ci-monitor] Starting CI monitor for branch 'feature/add-products'
|
||||
[ci-monitor] Config: max-cycles=5, timeout=120m, auto-fix-workflow=true
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 2m
|
||||
|
||||
[ci-monitor] Applying fix locally, enhancing, and pushing...
|
||||
[ci-monitor] Committed: abc1234
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] Waiting for new CI attempt... (expected SHA: abc1234)
|
||||
[CI Monitor] ⚠️ CI attempt timeout (10 min). Returning no_new_cipe.
|
||||
|
||||
[ci-monitor] Status: no_new_cipe
|
||||
[ci-monitor] --auto-fix-workflow enabled. Attempting lockfile update...
|
||||
[ci-monitor] Lockfile updated. Committed: def5678
|
||||
|
||||
[ci-monitor] Spawning subagent to poll CI status...
|
||||
[CI Monitor] New CI attempt detected!
|
||||
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 18m
|
||||
|
||||
[ci-monitor] CI passed successfully!
|
||||
|
||||
[ci-monitor] Summary:
|
||||
- Total cycles: 3
|
||||
- Total time: 22m 15s
|
||||
- Fixes applied: 1 (self-healing) + 1 (lockfile)
|
||||
- Result: SUCCESS
|
||||
```
|
||||
@@ -0,0 +1,228 @@
|
||||
---
|
||||
name: nx-generate
|
||||
description: Generate code using nx generators. USE WHEN scaffolding code or transforming existing code - for example creating libraries or applications, or anything else that is boilerplate code or automates repetitive tasks. ALWAYS use this first when generating code with Nx instead of calling MCP tools or running nx generate immediately.
|
||||
---
|
||||
|
||||
# Run Nx Generator
|
||||
|
||||
Nx generators are powerful tools that scaffold projects, make automated code migrations or automate repetitive tasks in a monorepo. They ensure consistency across the codebase and reduce boilerplate work.
|
||||
|
||||
This skill applies when the user wants to:
|
||||
|
||||
- Create new projects like libraries or applications
|
||||
- Scaffold features or boilerplate code
|
||||
- Run workspace-specific or custom generators
|
||||
- Do anything else that an nx generator exists for
|
||||
|
||||
## Generator Discovery Flow
|
||||
|
||||
### Step 1: List Available Generators
|
||||
|
||||
Use the Nx CLI to discover available generators:
|
||||
|
||||
- List all generators for a plugin: `npx nx list @nx/react`
|
||||
- View available plugins: `npx nx list`
|
||||
|
||||
This includes:
|
||||
|
||||
- Plugin generators (e.g., `@nx/react:library`, `@nx/js:library`)
|
||||
- Local workspace generators (defined in the repo's own plugins)
|
||||
|
||||
### Step 2: Match Generator to User Request
|
||||
|
||||
Based on the user's request, identify which generator(s) could fulfill their needs. Consider:
|
||||
|
||||
- What artifact type they want to create (library, application, etc.)
|
||||
- Which framework or technology stack is relevant
|
||||
- Whether they mentioned specific generator names
|
||||
|
||||
**IMPORTANT**: When both a local workspace generator and an external plugin generator could satisfy the request, **always prefer the local workspace generator**. Local generators are customized for the specific repo's patterns and conventions.
|
||||
|
||||
It's possible that the user request is something that no Nx generator exists for whatsoever. In this case, you can stop using this skill and try to help the user another way. HOWEVER, the burden of proof for this is high. Before aborting, carefully consider each and every generator that's available. Look into details for any that could be related in any way before making this decision.
|
||||
|
||||
## Pre-Execution Checklist
|
||||
|
||||
Before running any generator, complete these steps:
|
||||
|
||||
### 1. Fetch Generator Schema
|
||||
|
||||
Use the `--help` flag to understand all available options:
|
||||
|
||||
```bash
|
||||
npx nx g @nx/react:library --help
|
||||
```
|
||||
|
||||
Pay attention to:
|
||||
|
||||
- Required options that must be provided
|
||||
- Optional options that may be relevant to the user's request
|
||||
- Default values that might need to be overridden
|
||||
|
||||
### 2. Read Generator Source Code
|
||||
|
||||
Understanding what the generator actually does helps you:
|
||||
|
||||
- Know what files will be created/modified
|
||||
- Understand any side effects (updating configs, installing deps, etc.)
|
||||
- Identify options that might not be obvious from the schema
|
||||
|
||||
To find generator source code:
|
||||
|
||||
- For plugin generators: Use `node -e "console.log(require.resolve('@nx/<plugin>/generators.json'));"` to find the generators.json, then locate the source from there
|
||||
- If that fails, read directly from `node_modules/<plugin>/generators.json`
|
||||
- For local generators: They are typically in `tools/generators/` or a local plugin directory. You can search the repo for the generator name to find it.
|
||||
|
||||
### 2.5 Reevaluate if the generator is right
|
||||
|
||||
Once you have built up an understanding of what the selected generator does, reconsider: Is this the right generator to service the user request?
|
||||
If not, it's okay to go back to the Generator Discovery Flow and select a different generator before proceeding. If you do, make sure to go through the entire pre-execution checklist once more.
|
||||
|
||||
### 3. Understand Repo Context
|
||||
|
||||
Before generating, examine the target area of the codebase:
|
||||
|
||||
- Look at similar existing artifacts (other libraries, applications, etc.)
|
||||
- Identify patterns and conventions used in the repo
|
||||
- Note naming conventions, file structures, and configuration patterns
|
||||
- Try to match these patterns when configuring the generator
|
||||
|
||||
For example, if similar libraries are using a specific test runner, build tool or linter, try to match that if possible.
|
||||
If projects or other artifacts are organized with a specific naming convention, try to match it.
|
||||
|
||||
### 4. Validate Required Options
|
||||
|
||||
Ensure all required options have values:
|
||||
|
||||
- Map the user's request to generator options
|
||||
- Infer values from context where possible
|
||||
- Ask the user for any critical missing information
|
||||
|
||||
## Execution
|
||||
|
||||
Keep in mind that you might have to prefix things with npx/pnpx/yarn if the user doesn't have nx installed globally.
|
||||
Many generators will behave differently based on where they are executed. For example, first-party nx library generators use the cwd to determine the directory that the library should be placed in. This is highly important.
|
||||
|
||||
### Consider Dry-Run (Optional)
|
||||
|
||||
Running with `--dry-run` first is strongly encouraged but not mandatory. Use your judgment:
|
||||
|
||||
- For complex generators or unfamiliar territory: do a dry-run first
|
||||
- For simple, well-understood generators: may proceed directly
|
||||
- Dry-run shows file names and created/deleted/modified markers, but not content
|
||||
- There are cases where a generator does not support dry-run (for example if it had to install an npm package) - in that case --dry-run might fail. Don't be discouraged but simply move on to running the generator for real and iterating from there.
|
||||
|
||||
### Running the Generator
|
||||
|
||||
Execute the generator with:
|
||||
|
||||
```bash
|
||||
nx generate <generator-name> <options> --no-interactive
|
||||
```
|
||||
|
||||
**CRITICAL**: Always include `--no-interactive` to prevent prompts that would hang the execution.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
nx generate @nx/react:library --name=my-utils --no-interactive
|
||||
```
|
||||
|
||||
### Handling Generator Failures
|
||||
|
||||
If the generator fails:
|
||||
|
||||
1. **Diagnose the error** - Read the error message carefully
|
||||
2. **Identify the cause** - Missing options, invalid values, conflicts, etc.
|
||||
3. **Attempt automatic fix** - Adjust options or resolve conflicts
|
||||
4. **Retry** - Run the generator again with corrected options
|
||||
|
||||
Common failure reasons:
|
||||
|
||||
- Missing required options
|
||||
- Invalid option values
|
||||
- Conflicting with existing files
|
||||
- Missing dependencies
|
||||
- Generator doesn't support certain flag combinations
|
||||
|
||||
## Post-Generation
|
||||
|
||||
### 1. Modify Generated Code (If Needed)
|
||||
|
||||
Generators provide a starting point, but the output may need adjustment to match the user's specific requirements:
|
||||
|
||||
- Add or modify functionality as requested
|
||||
- Adjust imports, exports, or configurations
|
||||
- Integrate with existing code patterns in the repo
|
||||
|
||||
### 2. Format Code
|
||||
|
||||
Run formatting on all generated/modified files:
|
||||
|
||||
```bash
|
||||
nx format --fix
|
||||
```
|
||||
|
||||
Languages other than javascript/typescript might need other formatting invocations too.
|
||||
|
||||
### 3. Run Verification
|
||||
|
||||
Verify that the generated code works correctly. What this looks like will vary depending on the type of generator and the targets available.
|
||||
If the generator created a new project, run its targets directly
|
||||
Use your best judgement to determine what needs to be verified.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
nx lint <new-project>
|
||||
nx test <new-project>
|
||||
nx build <new-project>
|
||||
```
|
||||
|
||||
### 4. Handle Verification Failures
|
||||
|
||||
When verification fails:
|
||||
|
||||
**If scope is manageable** (a few lint errors, minor type issues):
|
||||
|
||||
- Fix the issues
|
||||
- Re-run verification to confirm
|
||||
|
||||
**If issues are extensive** (many errors, complex problems):
|
||||
|
||||
- Attempt simple, obvious fixes first
|
||||
- If still failing, escalate to the user with:
|
||||
- Description of what was generated
|
||||
- What verification is failing
|
||||
- What you've attempted to fix
|
||||
- Remaining issues that need user input
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Generator Failures
|
||||
|
||||
- Check the error message for specific causes
|
||||
- Verify all required options are provided
|
||||
- Check for conflicts with existing files
|
||||
- Ensure the generator name and options are correct
|
||||
|
||||
### Missing Options
|
||||
|
||||
- Consult the generator schema for required fields
|
||||
- Infer values from context when reasonable
|
||||
- Ask the user for values that cannot be inferred
|
||||
|
||||
## Key Principles
|
||||
|
||||
1. **Local generators first** - Always prefer workspace/local generators over external plugin generators when both could work
|
||||
|
||||
2. **Understand before running** - Read both the schema AND the source code to fully understand what will happen
|
||||
|
||||
3. **No prompts** - Always use `--no-interactive` to prevent hanging
|
||||
|
||||
4. **Generators are starting points** - Modify the output as needed to fully satisfy the user's requirements
|
||||
|
||||
5. **Verify changes work** - Don't just generate; ensure the code builds, lints, and tests pass
|
||||
|
||||
6. **Be proactive about fixes** - Don't just report errors; attempt to resolve them automatically when possible
|
||||
|
||||
7. **Match repo patterns** - Study existing similar code in the repo and match its conventions
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
name: nx-plugins
|
||||
description: Find and add Nx plugins. USE WHEN user wants to discover available plugins, install a new plugin, or add support for a specific framework or technology to the workspace.
|
||||
---
|
||||
|
||||
## Finding and Installing new plugins
|
||||
|
||||
- List plugins: `pnpm nx list`
|
||||
- Install plugins `pnpm nx add <plugin>`. Example: `pnpm nx add @nx/react`.
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
name: nx-run-tasks
|
||||
description: Helps with running tasks in an Nx workspace. USE WHEN the user wants to execute build, test, lint, serve, or run any other tasks defined in the workspace.
|
||||
---
|
||||
|
||||
You can run tasks with Nx in the following way.
|
||||
|
||||
Keep in mind that you might have to prefix things with npx/pnpx/yarn if the user doesn't have nx installed globally. Look at the package.json or lockfile to determine which package manager is in use.
|
||||
|
||||
For more details on any command, run it with `--help` (e.g. `nx run-many --help`, `nx affected --help`).
|
||||
|
||||
## Understand which tasks can be run
|
||||
|
||||
You can check those via `nx show project <projectname> --json`, for example `nx show project myapp --json`. It contains a `targets` section which has information about targets that can be run. You can also just look at the `package.json` scripts or `project.json` targets, but you might miss out on inferred tasks by Nx plugins.
|
||||
|
||||
## Run a single task
|
||||
|
||||
```
|
||||
nx run <project>:<task>
|
||||
```
|
||||
|
||||
where `project` is the project name defined in `package.json` or `project.json` (if present).
|
||||
|
||||
## Run multiple tasks
|
||||
|
||||
```
|
||||
nx run-many -t build test lint typecheck
|
||||
```
|
||||
|
||||
You can pass a `-p` flag to filter to specific projects, otherwise it runs on all projects. You can also use `--exclude` to exclude projects, and `--parallel` to control the number of parallel processes (default is 3).
|
||||
|
||||
Examples:
|
||||
|
||||
- `nx run-many -t test -p proj1 proj2` — test specific projects
|
||||
- `nx run-many -t test --projects=*-app --exclude=excluded-app` — test projects matching a pattern
|
||||
- `nx run-many -t test --projects=tag:api-*` — test projects by tag
|
||||
|
||||
## Run tasks for affected projects
|
||||
|
||||
Use `nx affected` to only run tasks on projects that have been changed and projects that depend on changed projects. This is especially useful in CI and for large workspaces.
|
||||
|
||||
```
|
||||
nx affected -t build test lint
|
||||
```
|
||||
|
||||
By default it compares against the base branch. You can customize this:
|
||||
|
||||
- `nx affected -t test --base=main --head=HEAD` — compare against a specific base and head
|
||||
- `nx affected -t test --files=libs/mylib/src/index.ts` — specify changed files directly
|
||||
|
||||
## Useful flags
|
||||
|
||||
These flags work with `run`, `run-many`, and `affected`:
|
||||
|
||||
- `--skipNxCache` — rerun tasks even when results are cached
|
||||
- `--verbose` — print additional information such as stack traces
|
||||
- `--nxBail` — stop execution after the first failed task
|
||||
- `--configuration=<name>` — use a specific configuration (e.g. `production`)
|
||||
@@ -0,0 +1,186 @@
|
||||
---
|
||||
name: nx-workspace
|
||||
description: "Explore and understand Nx workspaces. USE WHEN answering any questions about the nx workspace, the projects in it or tasks to run. EXAMPLES: 'What projects are in this workspace?', 'How is project X configured?', 'What targets can I run?', 'What's affected by my changes?', 'Which projects depend on library Y?', or any questions about Nx workspace structure, project configuration, or available tasks."
|
||||
---
|
||||
|
||||
# Nx Workspace Exploration
|
||||
|
||||
This skill provides read-only exploration of Nx workspaces. Use it to understand workspace structure, project configuration, available targets, and dependencies.
|
||||
|
||||
Keep in mind that you might have to prefix commands with `npx`/`pnpx`/`yarn` if nx isn't installed globally. Check the lockfile to determine the package manager in use.
|
||||
|
||||
## Listing Projects
|
||||
|
||||
Use `nx show projects` to list projects in the workspace.
|
||||
|
||||
```bash
|
||||
# List all projects
|
||||
nx show projects
|
||||
|
||||
# Filter by pattern (glob)
|
||||
nx show projects --projects "apps/*"
|
||||
nx show projects --projects "shared-*"
|
||||
|
||||
# Filter by project type
|
||||
nx show projects --type app
|
||||
nx show projects --type lib
|
||||
nx show projects --type e2e
|
||||
|
||||
# Filter by target (projects that have a specific target)
|
||||
nx show projects --withTarget build
|
||||
nx show projects --withTarget e2e
|
||||
|
||||
# Find affected projects (changed since base branch)
|
||||
nx show projects --affected
|
||||
nx show projects --affected --base=main
|
||||
nx show projects --affected --type app
|
||||
|
||||
# Combine filters
|
||||
nx show projects --type lib --withTarget test
|
||||
nx show projects --affected --exclude="*-e2e"
|
||||
|
||||
# Output as JSON
|
||||
nx show projects --json
|
||||
```
|
||||
|
||||
## Project Configuration
|
||||
|
||||
Use `nx show project <name> --json` to get the full resolved configuration for a project.
|
||||
|
||||
**Important**: Do NOT read `project.json` directly - it only contains partial configuration. The `nx show project` command returns the full resolved config including inferred targets from plugins.
|
||||
|
||||
You can read the full project schema at `node_modules/nx/schemas/project-schema.json` to understand nx project configuration options.
|
||||
|
||||
```bash
|
||||
# Get full project configuration
|
||||
nx show project my-app --json
|
||||
|
||||
# Extract specific parts from the JSON
|
||||
nx show project my-app --json | jq '.targets'
|
||||
nx show project my-app --json | jq '.targets.build'
|
||||
nx show project my-app --json | jq '.targets | keys'
|
||||
|
||||
|
||||
# Check project metadata
|
||||
nx show project my-app --json | jq '{name, root, sourceRoot, projectType, tags}'
|
||||
```
|
||||
|
||||
## Target Information
|
||||
|
||||
Targets define what tasks can be run on a project.
|
||||
|
||||
```bash
|
||||
# List all targets for a project
|
||||
nx show project my-app --json | jq '.targets | keys'
|
||||
|
||||
# Get full target configuration
|
||||
nx show project my-app --json | jq '.targets.build'
|
||||
|
||||
# Check target executor/command
|
||||
nx show project my-app --json | jq '.targets.build.executor'
|
||||
nx show project my-app --json | jq '.targets.build.command'
|
||||
|
||||
# View target options
|
||||
nx show project my-app --json | jq '.targets.build.options'
|
||||
|
||||
# Check target inputs/outputs (for caching)
|
||||
nx show project my-app --json | jq '.targets.build.inputs'
|
||||
nx show project my-app --json | jq '.targets.build.outputs'
|
||||
|
||||
# Find projects with a specific target
|
||||
nx show projects --withTarget serve
|
||||
nx show projects --withTarget e2e
|
||||
```
|
||||
|
||||
## Workspace Configuration
|
||||
|
||||
Read `nx.json` directly for workspace-level configuration.
|
||||
You can read the full project schema at `node_modules/nx/schemas/nx-schema.json` to understand nx project configuration options.
|
||||
|
||||
```bash
|
||||
# Read the full nx.json
|
||||
cat nx.json
|
||||
|
||||
# Or use jq for specific sections
|
||||
cat nx.json | jq '.targetDefaults'
|
||||
cat nx.json | jq '.namedInputs'
|
||||
cat nx.json | jq '.plugins'
|
||||
cat nx.json | jq '.generators'
|
||||
```
|
||||
|
||||
Key nx.json sections:
|
||||
|
||||
- `targetDefaults` - Default configuration applied to all targets of a given name
|
||||
- `namedInputs` - Reusable input definitions for caching
|
||||
- `plugins` - Nx plugins and their configuration
|
||||
- ...and much more, read the schema or nx.json for details
|
||||
|
||||
## Affected Projects
|
||||
|
||||
Find projects affected by changes in the current branch.
|
||||
|
||||
```bash
|
||||
# Affected since base branch (auto-detected)
|
||||
nx show projects --affected
|
||||
|
||||
# Affected with explicit base
|
||||
nx show projects --affected --base=main
|
||||
nx show projects --affected --base=origin/main
|
||||
|
||||
# Affected between two commits
|
||||
nx show projects --affected --base=abc123 --head=def456
|
||||
|
||||
# Affected apps only
|
||||
nx show projects --affected --type app
|
||||
|
||||
# Affected excluding e2e projects
|
||||
nx show projects --affected --exclude="*-e2e"
|
||||
|
||||
# Affected by uncommitted changes
|
||||
nx show projects --affected --uncommitted
|
||||
|
||||
# Affected by untracked files
|
||||
nx show projects --affected --untracked
|
||||
```
|
||||
|
||||
## Common Exploration Patterns
|
||||
|
||||
### "What's in this workspace?"
|
||||
|
||||
```bash
|
||||
nx show projects
|
||||
nx show projects --type app
|
||||
nx show projects --type lib
|
||||
```
|
||||
|
||||
### "How do I build/test/lint project X?"
|
||||
|
||||
```bash
|
||||
nx show project X --json | jq '.targets | keys'
|
||||
nx show project X --json | jq '.targets.build'
|
||||
```
|
||||
|
||||
### "What depends on library Y?"
|
||||
|
||||
```bash
|
||||
# Find projects that may depend on Y by searching for imports
|
||||
# (Nx doesn't have a direct "dependents" command via CLI)
|
||||
grep -r "from '@myorg/Y'" --include="*.ts" --include="*.tsx" apps/ libs/
|
||||
```
|
||||
|
||||
### "What configuration options are available?"
|
||||
|
||||
```bash
|
||||
cat node_modules/nx/schemas/nx-schema.json | jq '.properties | keys'
|
||||
cat node_modules/nx/schemas/project-schema.json | jq '.properties | keys'
|
||||
```
|
||||
|
||||
### "Why is project X affected?"
|
||||
|
||||
```bash
|
||||
# Check what files changed
|
||||
git diff --name-only main
|
||||
|
||||
# See which project owns those files
|
||||
nx show project X --json | jq '.root'
|
||||
```
|
||||
@@ -206,9 +206,8 @@ Fixes #ISSUE_NUMBER
|
||||
|
||||
- When running tasks (for example build, lint, test, e2e, etc.), always prefer running the task through `nx` (i.e. `nx run`, `nx run-many`, `nx affected`) instead of using the underlying tooling directly
|
||||
- You have access to the Nx MCP server and its tools, use them to help the user
|
||||
- When answering questions about the repository, use the `nx_workspace` tool first to gain an understanding of the workspace architecture where applicable.
|
||||
- When working in individual projects, use the `nx_project_details` mcp tool to analyze and understand the specific project structure and dependencies
|
||||
- For questions around nx configuration, best practices or if you're unsure, use the `nx_docs` tool to get relevant, up-to-date docs. Always use this instead of assuming things about nx configuration
|
||||
- If the user needs help with an Nx configuration or project graph error, use the `nx_workspace` tool to get any errors
|
||||
- For understanding the workspace structure, projects, or available tasks, use the `/nx-workspace` skill which provides guidance on exploring Nx workspaces
|
||||
- For questions around nx configuration, best practices or if you're unsure, use the `nx_docs` MCP tool to get relevant, up-to-date docs. Always use this instead of assuming things about nx configuration
|
||||
- For Nx plugin best practices, check `node_modules/@nx/<plugin>/PLUGIN.md`. Not all plugins have this file - proceed without it if unavailable.
|
||||
|
||||
<!-- nx configuration end-->
|
||||
|
||||
@@ -206,9 +206,8 @@ Fixes #ISSUE_NUMBER
|
||||
|
||||
- When running tasks (for example build, lint, test, e2e, etc.), always prefer running the task through `nx` (i.e. `nx run`, `nx run-many`, `nx affected`) instead of using the underlying tooling directly
|
||||
- You have access to the Nx MCP server and its tools, use them to help the user
|
||||
- When answering questions about the repository, use the `nx_workspace` tool first to gain an understanding of the workspace architecture where applicable.
|
||||
- When working in individual projects, use the `nx_project_details` mcp tool to analyze and understand the specific project structure and dependencies
|
||||
- For questions around nx configuration, best practices or if you're unsure, use the `nx_docs` tool to get relevant, up-to-date docs. Always use this instead of assuming things about nx configuration
|
||||
- If the user needs help with an Nx configuration or project graph error, use the `nx_workspace` tool to get any errors
|
||||
- For understanding the workspace structure, projects, or available tasks, use the `/nx-workspace` skill which provides guidance on exploring Nx workspaces
|
||||
- For questions around nx configuration, best practices or if you're unsure, use the `nx_docs` MCP tool to get relevant, up-to-date docs. Always use this instead of assuming things about nx configuration
|
||||
- For Nx plugin best practices, check `node_modules/@nx/<plugin>/PLUGIN.md`. Not all plugins have this file - proceed without it if unavailable.
|
||||
|
||||
<!-- nx configuration end-->
|
||||
|
||||
@@ -2,19 +2,10 @@
|
||||
|
||||
We would love for you to contribute to Nx! Read this document to see how to do it.
|
||||
|
||||
## How to Get Started Video
|
||||
|
||||
Watch this 5-minute video:
|
||||
|
||||
<a href="https://www.youtube.com/watch?v=8LCA_4qxc08" target="_blank" rel="noreferrer">
|
||||
<p style="text-align: center;"><img src="./images/how-to-contribute.png" width="600" alt="Nx - How to contribute"></p>
|
||||
</a>
|
||||
|
||||
## Got a Question?
|
||||
|
||||
We are trying to keep GitHub issues for bug reports and feature requests. Using the `nrwl` tag
|
||||
on [Stack Overflow](https://stackoverflow.com/questions/tagged/nrwl) is a much better place to ask general questions
|
||||
about how to use Nx.
|
||||
We are trying to keep GitHub issues for bug reports and feature requests.
|
||||
You can join our [Discord](https://go.nx.dev/community) for general questions and seeking help from others.
|
||||
|
||||
## Found an Issue?
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2017-2025 Narwhal Technologies Inc.
|
||||
Copyright (c) 2017-2026 Narwhal Technologies Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<p style="text-align: center;">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./images/nx-dark.svg">
|
||||
<img alt="Nx - Smart Repos · Fast Builds" src="./images/nx-light.svg" width="100%">
|
||||
<img alt="Nx - Smart Monorepos · Fast Builds" src="./images/nx-light.svg" width="100%">
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
@@ -19,9 +19,7 @@
|
||||
|
||||
<hr>
|
||||
|
||||
# Smart Repos · Fast Builds
|
||||
|
||||
Get to green PRs in half the time. Nx optimizes your builds, scales your CI, and fixes failed PRs. Built for developers and AI agents.
|
||||
# The Monorepo Platform that amplifies both developers and AI agents. Nx optimizes your builds, scales your CI, and fixes failed PRs automatically. Ship in half the time.
|
||||
|
||||
Create a new Nx workspace with
|
||||
|
||||
@@ -58,7 +56,7 @@ Learn more in the [Nx CI docs »](https://nx.dev/ci/getting-started/intro?u
|
||||
- [Our Twitter/X](https://x.com/nxdevtools)
|
||||
|
||||
<p style="text-align: center;"><a href="https://www.youtube.com/@nxdevtools/videos" target="_blank" rel="noreferrer"><img src="./images/nx-courses-and-videos.svg"
|
||||
width="100%" alt="Nx - Smart Repos · Fast Builds"></a></p>
|
||||
width="100%" alt="Nx - Smart Monorepos · Fast Builds"></a></p>
|
||||
|
||||
## Want to help?
|
||||
|
||||
|
||||
@@ -13,3 +13,15 @@ Instead, please report them to the Security Team at security@nrwl.io.
|
||||
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message.
|
||||
|
||||
Nx follows the principle of Coordinated Vulnerability Disclosure.
|
||||
|
||||
## What Should Be Reported
|
||||
|
||||
The security email is for **demonstrable, verified vulnerabilities within the Nx codebase itself**.
|
||||
|
||||
**Please do not use the security email for:**
|
||||
|
||||
- Reports about outdated dependencies (e.g., "package X has a newer version available")
|
||||
- Reports about dependencies with known CVEs that do not directly affect Nx functionality
|
||||
- General vulnerability scanner output
|
||||
|
||||
If you have a concern about an outdated dependency that you believe impacts Nx users, please open a [GitHub issue](https://github.com/nrwl/nx/issues/new/choose) instead.
|
||||
|
||||
@@ -22,6 +22,48 @@ This documentation site leverages Astro's static site generation capabilities wi
|
||||
- Dynamic API documentation generation from Nx packages and CLI commands
|
||||
- Community plugin registry
|
||||
|
||||
## Information Architecture Principles
|
||||
|
||||
When creating or reorganizing documentation, follow these 5 principles to determine where content belongs.
|
||||
|
||||
### 1. Progressive Disclosure (The "Journey" Rule)
|
||||
|
||||
- **Concept:** Don't overwhelm the user. Reveal complexity only as they advance in their journey.
|
||||
- **The Test:** _Is this for the First 30 Minutes (Getting Started), the First 30 Days (Features), or Forever (Reference)?_
|
||||
|
||||
### 2. Category Homogeneity (The "Scan" Rule)
|
||||
|
||||
- **Concept:** Items in a list must be of the same "type" (noun, verb, or concept) to reduce cognitive load.
|
||||
- **The Test:** _Does this list mix Concepts (Mental Model), Tasks (Update Nx), and Products (React)? If yes, split it._
|
||||
|
||||
### 3. Type-Based Navigation (The "Intent" Rule)
|
||||
|
||||
- **Concept:** Separate **Learning** (Narrative/Guides) from **Looking Up** (Reference/API).
|
||||
- **The Test:** _Is the user here to learn a workflow (Guide) or look up a flag syntax (Reference)?_
|
||||
|
||||
### 4. The Pen & Paper Test (The "Theory" Rule)
|
||||
|
||||
- **Concept:** Distinguish Architecture from Features to keep "Core Concepts" pure.
|
||||
- **The Test:** _Can I explain this using only a pen and paper?_
|
||||
- **Yes:** It goes in **How Nx Works** (Architecture).
|
||||
- **No (I need a terminal):** It goes in **Platform Features** (Feature).
|
||||
|
||||
### 5. Universal vs. Specific (The "Placement" Rule)
|
||||
|
||||
- **Concept:** Distinguish Platform features from Ecosystem tools to prevent "Features" from becoming a junk drawer.
|
||||
- **The Test:** _Does this feature apply to EVERY user (e.g., Caching, Agents)?_
|
||||
- **Yes:** **Platform Features**.
|
||||
- **No (Only React users):** **Technologies**.
|
||||
|
||||
### Sidebar Structure
|
||||
|
||||
The sidebar has 4 top-level sections that follow the user journey:
|
||||
|
||||
1. **Getting Started** - Essential setup, tutorials, and core concepts (How Nx Works, Platform Features)
|
||||
2. **Technologies** - Framework and tool-specific guides (React, Angular, Node, build tools, test tools)
|
||||
3. **Knowledge Base** - Recipes, troubleshooting, and topic-specific guides
|
||||
4. **Reference** - Exhaustive facts, no narrative (CLI commands, configuration, API docs)
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,388 @@
|
||||
# Nx Documentation Style Guide
|
||||
|
||||
This document defines the standards for Nx documentation on nx.dev, including voice, grammar, formatting, and terminology.
|
||||
|
||||
For automated enforcement, see the [Vale configuration](#vale-configuration) section.
|
||||
|
||||
## Information architecture
|
||||
|
||||
When creating or reorganizing documentation, follow these five principles to determine where content belongs.
|
||||
|
||||
### 1. Progressive disclosure (the "journey" rule)
|
||||
|
||||
Don't overwhelm the user. Reveal complexity only as they advance in their journey.
|
||||
|
||||
**The test:** Is this for the first 30 minutes (Getting Started), the first 30 days (Features), or forever (Reference)?
|
||||
|
||||
### 2. Category homogeneity (the "scan" rule)
|
||||
|
||||
Items in a list must be of the same "type" (noun, verb, or concept) to reduce cognitive load.
|
||||
|
||||
**The test:** Does this list mix concepts (mental model), tasks (update Nx), and products (React)? If yes, split it.
|
||||
|
||||
### 3. Type-based navigation (the "intent" rule)
|
||||
|
||||
Separate learning (narrative/guides) from looking up (reference/API).
|
||||
|
||||
**The test:** Is the user here to learn a workflow (guide) or look up a flag syntax (reference)?
|
||||
|
||||
### 4. The pen and paper test (the "theory" rule)
|
||||
|
||||
Distinguish architecture from features to keep "core concepts" pure.
|
||||
|
||||
**The test:** Can I explain this using only a pen and paper?
|
||||
|
||||
- Yes: It goes in **How Nx Works** (architecture).
|
||||
- No (I need a terminal): It goes in **Platform Features** (feature).
|
||||
|
||||
### 5. Universal vs. specific (the "placement" rule)
|
||||
|
||||
Distinguish platform features from ecosystem tools to prevent "Features" from becoming a junk drawer.
|
||||
|
||||
**The test:** Does this feature apply to every user (e.g., caching, Nx Agents)?
|
||||
|
||||
- Yes: **Platform Features**.
|
||||
- No (only React users): **Technologies**.
|
||||
|
||||
### Sidebar structure
|
||||
|
||||
The sidebar has four top-level sections that follow the user journey:
|
||||
|
||||
1. **Getting Started** - Essential setup, tutorials, and core concepts (How Nx Works, Platform Features)
|
||||
2. **Technologies** - Framework and tool-specific guides (React, Angular, Node, build tools, test tools)
|
||||
3. **Knowledge Base** - Recipes, troubleshooting, and topic-specific guides
|
||||
4. **Reference** - Exhaustive facts, no narrative (CLI commands, configuration, API docs)
|
||||
|
||||
## The Nx voice
|
||||
|
||||
Nx documentation is **direct, practical, and confident**. We write like a knowledgeable colleague pairing with you — not like a textbook, not like a marketing page, and not like a chatbot.
|
||||
|
||||
The voice should be:
|
||||
|
||||
- **Conversational but efficient.** Use contractions. Get to the point. Don't pad sentences.
|
||||
- **Second person.** Write "you" — address the reader directly.
|
||||
- **Action-oriented.** Lead with what the reader can _do_, not what Nx _is_.
|
||||
- **Honest about tradeoffs.** Don't oversell. If something has limitations, say so.
|
||||
|
||||
### Voice do's and don'ts
|
||||
|
||||
| Do | Don't |
|
||||
| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
|
||||
| "You can speed up builds by enabling remote caching." | "Nx allows you to speed up builds." |
|
||||
| "Run `nx build` to build your project." | "In order to build your project, you can run the `nx build` command." |
|
||||
| "This works best with fewer than 50 projects." | "This feature can easily scale to any number of projects." |
|
||||
| "Nx reads your `vite.config.ts` and infers build targets automatically." | "Nx provides a robust and comprehensive mechanism for inferring build targets." |
|
||||
| "If the cache is stale, delete `.nx/cache` and retry." | "Should you encounter issues with caching, you may want to consider clearing your cache directory." |
|
||||
|
||||
### Anti-AI language
|
||||
|
||||
Documentation must not read like it was generated by an AI assistant. Even when AI tools are used in the writing process, the output must be edited to sound like a human wrote it.
|
||||
|
||||
**Never use these phrases:**
|
||||
|
||||
- "It's important to note that..."
|
||||
- "It's worth noting that..." / "It should be noted that..."
|
||||
- "In this section, we will explore..."
|
||||
- "Let's dive into..." / "Let's take a closer look at..."
|
||||
- "Whether you're a beginner or an experienced developer..."
|
||||
- "In today's fast-paced development environment..."
|
||||
- "Unlock the power of..." / "Harness the power of..."
|
||||
- "Take your workspace to the next level"
|
||||
- "Streamline your workflow" (as a generic claim without specifics)
|
||||
- "This comprehensive guide will..."
|
||||
- "Without further ado..."
|
||||
- "In conclusion..." / "To summarize..." / "As we've seen..."
|
||||
- "Game-changer" / "Cutting-edge" / "Groundbreaking"
|
||||
- "Seamless" / "Seamlessly" (unless describing an actual integration)
|
||||
|
||||
**Avoid hedging words unless genuinely needed:**
|
||||
|
||||
- "Essentially" / "Basically" / "Effectively"
|
||||
- "Generally speaking"
|
||||
- "It is worth mentioning"
|
||||
- "Arguably"
|
||||
- "Needless to say"
|
||||
- "As a matter of fact"
|
||||
|
||||
**Watch for AI-style sentence patterns:**
|
||||
|
||||
- Sentences that start with "This allows you to..." or "This enables you to..." — rewrite to lead with the reader's action.
|
||||
- Paragraphs that start with a general claim and then restate it slightly differently. Say it once.
|
||||
- Excessive use of "robust", "leverage", "utilize", "facilitate", "comprehensive", "aforementioned."
|
||||
- Lists where every item starts with the same grammatical structure repeated 5+ times with slight variation. Vary your phrasing.
|
||||
|
||||
### Self-referential writing
|
||||
|
||||
Don't write about the document itself.
|
||||
|
||||
Do:
|
||||
|
||||
- "Nx uses a project graph to determine task dependencies."
|
||||
|
||||
Don't:
|
||||
|
||||
- "This page explains how Nx uses a project graph."
|
||||
- "In this guide, we'll walk through..."
|
||||
- "This document covers..."
|
||||
|
||||
Get right to the point. The reader already knows they're on a page — they want the information.
|
||||
|
||||
### Building trust
|
||||
|
||||
Don't use filler words that undermine the reader's trust.
|
||||
|
||||
- Don't use "easily", "simply", "just", or "straightforward" — if something were truly simple, you wouldn't need to document it. These words also make readers feel bad when they struggle.
|
||||
- Don't use marketing language: "This feature will save you hours" or "Nx makes CI effortless."
|
||||
- Be specific instead: "Remote caching can reduce CI times from 45 minutes to under 5 minutes for cache-hit builds."
|
||||
|
||||
### Customer perspective
|
||||
|
||||
Focus on what the reader can do, not what Nx does.
|
||||
|
||||
Do:
|
||||
|
||||
- "Use `nx affected` to run tasks only for projects impacted by your changes."
|
||||
|
||||
Don't:
|
||||
|
||||
- "Nx allows you to run affected tasks."
|
||||
- "Nx provides the ability to run tasks selectively."
|
||||
|
||||
Words like "allow" and "enable" are signals you're writing from the product's perspective instead of the reader's.
|
||||
|
||||
## Language
|
||||
|
||||
Write in US English.
|
||||
|
||||
### Active voice
|
||||
|
||||
Use active voice in most cases.
|
||||
|
||||
Do: "Nx caches the build output."
|
||||
Don't: "The build output is cached by Nx."
|
||||
|
||||
Exception: When "Nx" as the subject sounds awkward, passive voice is fine. "The output is stored in `.nx/cache`" is better than "Nx stores the output in `.nx/cache`" if Nx isn't the focus of the sentence.
|
||||
|
||||
### Contractions
|
||||
|
||||
Use contractions. They make the text feel natural.
|
||||
|
||||
- "You'll need to configure..." not "You will need to configure..."
|
||||
- "It doesn't support..." not "It does not support..."
|
||||
|
||||
Don't contract for emphasis in warnings or error descriptions:
|
||||
|
||||
- "**Do not** delete the `nx.json` file."
|
||||
- "Requests to localhost **are not** allowed."
|
||||
|
||||
Don't contract proper nouns: "the Vite plugin is..." not "Vite's a plugin..."
|
||||
|
||||
### Capitalization
|
||||
|
||||
Use sentence case for headings. Capitalize proper nouns only.
|
||||
|
||||
- `# Use remote caching to speed up CI`
|
||||
- `## Configure the Vite plugin`
|
||||
|
||||
Feature names are lowercase unless they are a proper product name:
|
||||
|
||||
| Correct | Incorrect |
|
||||
| -------------- | -------------- |
|
||||
| remote caching | Remote Caching |
|
||||
| task pipeline | Task Pipeline |
|
||||
| project graph | Project Graph |
|
||||
| Nx Cloud | nx cloud |
|
||||
| Nx Console | nx console |
|
||||
| Nx Agents | nx agents |
|
||||
| Nx Replay | nx replay |
|
||||
|
||||
### Acronyms
|
||||
|
||||
Spell out acronyms on first use per page. Don't spell out widely-known ones: CI, CD, API, URL, CLI, PR, IDE.
|
||||
|
||||
Don't make acronyms plural with apostrophes. Use `APIs`, not `API's`.
|
||||
|
||||
### Numbers
|
||||
|
||||
Spell out zero through nine. Use numerals for 10 and above. Always use numerals with units: "5 minutes", "3 projects."
|
||||
|
||||
### Possessives
|
||||
|
||||
Don't use possessives on product names. "the Docker CLI", not "Docker's CLI." "the Nx configuration", not "Nx's configuration."
|
||||
|
||||
## Text
|
||||
|
||||
### Headings
|
||||
|
||||
- Don't skip heading levels (e.g., `##` to `####`).
|
||||
- Don't use code in headings unless it's essential (like a CLI command).
|
||||
- Don't use bold text in headings.
|
||||
- Keep headings short and scannable. Lead with keywords.
|
||||
|
||||
### Line length
|
||||
|
||||
- Wrap lines at approximately 100 characters for readability in diffs.
|
||||
- Start each new sentence on a new line.
|
||||
- Exception: Don't break links across lines.
|
||||
|
||||
### Punctuation
|
||||
|
||||
- Use serial (Oxford) commas: "React, Angular, and Vue."
|
||||
- Use one space between sentences.
|
||||
- Don't use semicolons. Use two sentences instead.
|
||||
- Don't use em dashes or en dashes. Use commas or separate sentences.
|
||||
|
||||
### Placeholder text
|
||||
|
||||
Use `<` and `>` for values the reader must replace:
|
||||
|
||||
```shell
|
||||
nx run <project-name>:build
|
||||
```
|
||||
|
||||
If the placeholder is inline, wrap it in a single backtick: `<your-project>`.
|
||||
|
||||
### Bold
|
||||
|
||||
Use bold for:
|
||||
|
||||
- UI elements: "Select **Add Connection**."
|
||||
- Navigation paths: "Go to **Settings** > **Workspace**."
|
||||
|
||||
Don't use bold for emphasis or keywords. If you need emphasis, rewrite the sentence to be clearer.
|
||||
|
||||
### Inline code
|
||||
|
||||
Use inline code (single backticks) for:
|
||||
|
||||
- Commands and CLI arguments: `nx build`, `--parallel`
|
||||
- File names and paths: `nx.json`, `.nx/cache`
|
||||
- Configuration keys: `targetDefaults`, `namedInputs`
|
||||
- Short outputs and values: `true`, `false`, `success`
|
||||
|
||||
### Code blocks
|
||||
|
||||
Use triple backticks with a language identifier:
|
||||
|
||||
````markdown
|
||||
```json
|
||||
{
|
||||
"targetDefaults": {
|
||||
"build": {
|
||||
"cache": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
````
|
||||
|
||||
- Always specify a syntax language. Use `plaintext` if nothing else fits.
|
||||
- Add a blank line before and after code blocks.
|
||||
- For long config files, show only the relevant section and use comments to indicate omitted parts:
|
||||
|
||||
```json
|
||||
{
|
||||
// ... other config
|
||||
"targetDefaults": {
|
||||
"build": {
|
||||
"cache": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
Links help readers find related information, but too many links make text hard to read.
|
||||
|
||||
### General rules
|
||||
|
||||
- Don't duplicate links. If you link to a page once, don't link to it again on the same page.
|
||||
- Don't use links in headings.
|
||||
- Avoid more than 15 links to other pages on any single page.
|
||||
- Avoid multiple links in a single paragraph when possible.
|
||||
|
||||
### Link text
|
||||
|
||||
Use descriptive text, not "here" or "this page."
|
||||
|
||||
Do:
|
||||
|
||||
- "For more information, see [remote caching](/features/cache)."
|
||||
- "To configure task pipelines, see [task pipeline configuration](/concepts/task-pipeline-configuration)."
|
||||
|
||||
Don't:
|
||||
|
||||
- "For more information, see [this page](/features/cache)."
|
||||
- "Click [here](/features/cache) to learn more."
|
||||
- "For more information, see the [Remote Caching](/features/cache) documentation."
|
||||
|
||||
Standard patterns:
|
||||
|
||||
- `For more information, see [link text](url).`
|
||||
- `To <do this thing>, see [link text](url).`
|
||||
|
||||
### External links
|
||||
|
||||
Minimize external links. They break over time and are hard to maintain. When you must link externally, prefer official documentation (e.g., Vite docs, Webpack docs) over blog posts or third-party guides.
|
||||
|
||||
## Lists
|
||||
|
||||
- Use ordered lists for sequences of steps.
|
||||
- Use unordered lists when order doesn't matter.
|
||||
- Use dashes (`-`) for unordered lists.
|
||||
- Start ordered list items with `1.` (Markdown auto-increments).
|
||||
- Make list items parallel in structure.
|
||||
- Add a colon after the introductory phrase.
|
||||
- Don't use list items to complete an introductory sentence.
|
||||
|
||||
Do:
|
||||
|
||||
```markdown
|
||||
You can clear the cache in the following ways:
|
||||
|
||||
- Delete the `.nx/cache` directory manually.
|
||||
- Run `nx reset` to clear all cached results.
|
||||
```
|
||||
|
||||
Don't:
|
||||
|
||||
```markdown
|
||||
You can clear the cache by:
|
||||
|
||||
- Deleting the `.nx/cache` directory manually.
|
||||
- Running `nx reset`.
|
||||
```
|
||||
|
||||
## Tables
|
||||
|
||||
Use tables for structured data that benefits from a matrix layout. For simple lists of items with descriptions, use a regular list instead.
|
||||
|
||||
- Don't leave cells empty. Use "N/A" or "None."
|
||||
- Use sentence case for headers.
|
||||
- Keep the header and delimiter rows the same length.
|
||||
|
||||
## Nx-specific terminology
|
||||
|
||||
Use these terms consistently. When writing about Nx concepts, use the exact term from this list.
|
||||
|
||||
| Term | Usage notes |
|
||||
| -------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| workspace | The root directory managed by Nx. Not "repo" or "monorepo" when referring to Nx's context. |
|
||||
| project | An app or library within the workspace. |
|
||||
| target | A task that can be run for a project (e.g., `build`, `test`, `lint`). |
|
||||
| executor | The implementation behind a target. Not "builder." |
|
||||
| generator | Code scaffolding tool. Not "schematic." |
|
||||
| plugin | An Nx plugin that provides executors, generators, or graph inference. |
|
||||
| task | A specific invocation of a target for a project (e.g., `myapp:build`). |
|
||||
| task pipeline | The dependency graph between tasks. Not "task orchestration" or "task graph" in user-facing docs. |
|
||||
| project graph | The dependency graph between projects. |
|
||||
| affected | Projects impacted by a code change. |
|
||||
| cache / cached | Not "memoized" or "stored results." |
|
||||
| remote caching | Sharing cached results across machines. Specific product: "Nx Replay." |
|
||||
| Nx Cloud | The hosted CI/CD product. Always capitalized. |
|
||||
| Nx Console | The IDE extension. Always capitalized. |
|
||||
| Nx Agents | Distributed task execution product. Always capitalized. |
|
||||
| Nx Replay | Remote caching product. Always capitalized. |
|
||||
| `nx.json` | Always in code style. |
|
||||
| `project.json` | Always in code style. |
|
||||
@@ -6,14 +6,17 @@ import react from '@astrojs/react';
|
||||
import markdoc from '@astrojs/markdoc';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { sidebar } from './sidebar.mts';
|
||||
import rehypeTableOptionLinks from './src/plugins/utils/rehype-table-option-links.ts';
|
||||
import { resolveNxDevUrl } from './src/utils/resolve-nx-dev-url.ts';
|
||||
|
||||
// Always resolve NX_DEV_URL so downstream consumers (Footer, Header) pick it up.
|
||||
// For deploy previews this overrides any site-level env var to point to the matching preview.
|
||||
process.env.NX_DEV_URL = resolveNxDevUrl();
|
||||
|
||||
const BASE = '/docs';
|
||||
|
||||
// This is exposed as window.__CONFIG
|
||||
const PUBLIC_CONFIG = {
|
||||
cookiebotDisabled: process.env.COOKIEBOT_DISABLED === 'true',
|
||||
cookiebotId: process.env.COOKIEBOT_ID ?? null,
|
||||
gaMeasurementId: 'UA-88380372-10',
|
||||
gtmMeasurementId: 'GTM-KW8423B6',
|
||||
isProd: process.env.NODE_ENV === 'production',
|
||||
};
|
||||
@@ -33,6 +36,9 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
},
|
||||
markdown: {
|
||||
rehypePlugins: [rehypeTableOptionLinks],
|
||||
},
|
||||
trailingSlash: 'never',
|
||||
// This adapter doesn't support local previews, so only load it on Netlify.
|
||||
adapter: process.env['NETLIFY'] ? netlify() : undefined,
|
||||
@@ -56,22 +62,6 @@ export default defineConfig({
|
||||
tag: 'script',
|
||||
content: `window.__CONFIG = ${JSON.stringify(PUBLIC_CONFIG)};`,
|
||||
},
|
||||
...(process.env.COOKIEBOT_ID &&
|
||||
process.env.COOKIEBOT_DISABLED !== 'true'
|
||||
? [
|
||||
{
|
||||
/** @type {"script"} */
|
||||
tag: 'script',
|
||||
attrs: {
|
||||
id: 'Cookiebot',
|
||||
src: 'https://consent.cookiebot.com/uc.js',
|
||||
'data-cbid': process.env.COOKIEBOT_ID,
|
||||
'data-blockingmode': 'auto',
|
||||
type: 'text/javascript',
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
tag: 'script',
|
||||
attrs: {
|
||||
@@ -87,17 +77,13 @@ export default defineConfig({
|
||||
// since the sidebar doesn't auto generate w/ dynamic routes from src/pages/reference
|
||||
// only the src/content/docs/reference files
|
||||
'./src/plugins/sidebar-reference-updater.middleware.ts',
|
||||
'./src/plugins/sidebar-icons.middleware.ts',
|
||||
'./src/plugins/og.middleware.ts',
|
||||
'./src/plugins/github-stars.middleware.ts',
|
||||
'./src/plugins/raw-content.middleware.ts',
|
||||
'./src/plugins/canonical.middleware.ts',
|
||||
],
|
||||
markdown: {
|
||||
// this breaks the renderMarkdown function in the plugin loader due to starlight path normalization
|
||||
// as to _why_ it has to normalize a path?
|
||||
// idk just working around the issue for now but we'll want to have linked headers so will need to fix
|
||||
headingLinks: false,
|
||||
headingLinks: true,
|
||||
},
|
||||
social: [
|
||||
{ icon: 'github', label: 'GitHub', href: 'https://github.com/nrwl/nx' },
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('CLI sub-command formatting', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/docs/reference/nx-commands');
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Nx Commands' })
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('parent commands render as h2 and sub-commands as h3', async ({
|
||||
page,
|
||||
}) => {
|
||||
const mainContent = page.getByTestId('main-pane');
|
||||
|
||||
// "nx show" should be an h2 (top-level parent command)
|
||||
const showHeading = mainContent.getByRole('heading', {
|
||||
name: 'nx show',
|
||||
level: 2,
|
||||
exact: true,
|
||||
});
|
||||
await expect(showHeading).toBeVisible();
|
||||
|
||||
// "nx show projects" should be an h3 (sub-command nested under parent)
|
||||
const showProjectsHeading = mainContent.getByRole('heading', {
|
||||
name: 'nx show projects',
|
||||
level: 3,
|
||||
exact: true,
|
||||
});
|
||||
await expect(showProjectsHeading).toBeVisible();
|
||||
|
||||
// "nx show project" should also be an h3
|
||||
const showProjectHeading = mainContent.getByRole('heading', {
|
||||
name: 'nx show project',
|
||||
level: 3,
|
||||
exact: true,
|
||||
});
|
||||
await expect(showProjectHeading).toBeVisible();
|
||||
});
|
||||
|
||||
test('sub-command usage blocks show the full command name', async ({
|
||||
page,
|
||||
}) => {
|
||||
const mainContent = page.getByTestId('main-pane');
|
||||
|
||||
// Find the "nx show projects" section and verify its usage block
|
||||
// The usage code block should contain "nx show projects", not "nx projects"
|
||||
const showProjectsHeading = mainContent.getByRole('heading', {
|
||||
name: 'nx show projects',
|
||||
level: 3,
|
||||
exact: true,
|
||||
});
|
||||
await expect(showProjectsHeading).toBeVisible();
|
||||
|
||||
// Get the section between "nx show projects" heading and the next heading.
|
||||
// We look for a code block containing the correct usage pattern.
|
||||
const codeBlocks = mainContent.locator('pre code');
|
||||
const allCodeTexts = await codeBlocks.allTextContents();
|
||||
|
||||
// There should be a usage block with "nx show projects" (full sub-command name)
|
||||
expect(allCodeTexts.some((text) => text.includes('nx show projects'))).toBe(
|
||||
true
|
||||
);
|
||||
|
||||
// There should NOT be a usage block with just "nx projects" (missing parent)
|
||||
expect(
|
||||
allCodeTexts.some(
|
||||
(text) => text.match(/^nx projects/) || text.match(/\nnx projects/)
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test('release sub-commands use correct heading and usage format', async ({
|
||||
page,
|
||||
}) => {
|
||||
const mainContent = page.getByTestId('main-pane');
|
||||
|
||||
// "nx release" should be h2
|
||||
const releaseHeading = mainContent.getByRole('heading', {
|
||||
name: 'nx release',
|
||||
level: 2,
|
||||
exact: true,
|
||||
});
|
||||
await expect(releaseHeading).toBeVisible();
|
||||
|
||||
// "nx release version" should be h3
|
||||
const releaseVersionHeading = mainContent.getByRole('heading', {
|
||||
name: 'nx release version',
|
||||
level: 3,
|
||||
exact: true,
|
||||
});
|
||||
await expect(releaseVersionHeading).toBeVisible();
|
||||
|
||||
// Verify usage block includes the full command
|
||||
const codeBlocks = mainContent.locator('pre code');
|
||||
const allCodeTexts = await codeBlocks.allTextContents();
|
||||
|
||||
expect(
|
||||
allCodeTexts.some((text) => text.includes('nx release version'))
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('options and examples are h4 headings excluded from the TOC', async ({
|
||||
page,
|
||||
}) => {
|
||||
const mainContent = page.getByTestId('main-pane');
|
||||
|
||||
// Options/Examples should be h4 headings — linkable but below the TOC threshold (h2-h3)
|
||||
const sharedOptionsHeading = mainContent.getByRole('heading', {
|
||||
name: 'Shared Options',
|
||||
level: 4,
|
||||
});
|
||||
await expect(sharedOptionsHeading.first()).toBeVisible();
|
||||
|
||||
const optionsHeading = mainContent.getByRole('heading', {
|
||||
name: 'Options',
|
||||
level: 4,
|
||||
});
|
||||
await expect(optionsHeading.first()).toBeVisible();
|
||||
|
||||
// They should NOT appear as h2 or h3 (which would put them in the TOC)
|
||||
await expect(
|
||||
mainContent.getByRole('heading', { name: 'Options', level: 2 })
|
||||
).toHaveCount(0);
|
||||
await expect(
|
||||
mainContent.getByRole('heading', { name: 'Options', level: 3 })
|
||||
).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,7 @@ test('links in descriptions of properties should correctly link to the same page
|
||||
|
||||
await page
|
||||
.getByTestId('main-pane')
|
||||
.getByRole('link', { name: 'nxCloudAccessToken' })
|
||||
.getByRole('link', { name: 'nxCloudAccessToken', exact: true })
|
||||
.click();
|
||||
|
||||
await expect(
|
||||
|
||||
@@ -4,9 +4,15 @@ import {
|
||||
Markdoc,
|
||||
} from '@astrojs/markdoc/config';
|
||||
import starlightMarkdoc from '@astrojs/starlight-markdoc';
|
||||
import { transformOptionsTable } from './src/utils/markdoc-table-option-links';
|
||||
|
||||
export default defineMarkdocConfig({
|
||||
extends: [starlightMarkdoc()],
|
||||
nodes: {
|
||||
table: {
|
||||
transform: transformOptionsTable,
|
||||
},
|
||||
},
|
||||
tags: {
|
||||
call_to_action: {
|
||||
render: component('./src/components/markdoc/CallToAction.astro'),
|
||||
@@ -238,6 +244,15 @@ export default defineMarkdocConfig({
|
||||
},
|
||||
},
|
||||
},
|
||||
sidebar_group_cards: {
|
||||
render: component('./src/components/markdoc/SidebarGroupCards.astro'),
|
||||
attributes: {
|
||||
group: {
|
||||
type: 'String',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
metrics: {
|
||||
render: component('./src/components/markdoc/Metrics.astro'),
|
||||
attributes: {
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
NX_GRADLE_DISABLE = "true"
|
||||
NX_MAVEN_DISABLE = "true"
|
||||
|
||||
# Edge functions are auto-discovered from netlify/edge-functions/
|
||||
# Path configuration is in each function's inline `config` export
|
||||
|
||||
# Permanent redirects (301 by default)
|
||||
|
||||
# Storybook docs consolidation
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import type { Context } from 'https://edge.netlify.com';
|
||||
|
||||
/**
|
||||
* Content negotiation for LLM-friendly docs access.
|
||||
* See: https://llmstxt.org/
|
||||
*/
|
||||
export default async function handler(
|
||||
request: Request,
|
||||
context: Context
|
||||
): Promise<Response | URL> {
|
||||
const url = new URL(request.url);
|
||||
const pathname = url.pathname;
|
||||
|
||||
const acceptHeader = request.headers.get('accept') || '';
|
||||
|
||||
// Serve markdown for LLM tools that explicitly request it
|
||||
// Or if there are no accept headers passed (e.g. Cursor)
|
||||
if (!acceptHeader || acceptHeader.includes('text/markdown')) {
|
||||
const mdPath = pathname.replace(/\/?$/, '.md');
|
||||
return new URL(mdPath, request.url);
|
||||
}
|
||||
|
||||
const response = await context.next();
|
||||
|
||||
const contentType = response.headers.get('content-type') || '';
|
||||
if (!contentType.includes('text/html')) {
|
||||
return response;
|
||||
}
|
||||
|
||||
const mdPath = pathname.replace(/\/?$/, '.md');
|
||||
|
||||
const linkHeader = [
|
||||
`<${mdPath}>; rel="alternate"; type="text/markdown"`,
|
||||
`</docs/llms.txt>; rel="alternate"; type="text/markdown"; title="LLM Index"`,
|
||||
`</docs/llms-full.txt>; rel="alternate"; type="text/markdown"; title="Full Documentation"`,
|
||||
].join(', ');
|
||||
|
||||
// Netlify responses are immutable
|
||||
const newHeaders = new Headers(response.headers);
|
||||
newHeaders.set('Link', linkHeader);
|
||||
|
||||
return new Response(response.body, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: newHeaders,
|
||||
});
|
||||
}
|
||||
|
||||
export const config = {
|
||||
path: ['/docs/*'],
|
||||
excludedPath: [
|
||||
'/docs/*.md',
|
||||
'/docs/*.js',
|
||||
'/docs/*.txt',
|
||||
'/docs/images/*',
|
||||
// _astro and other asset paths
|
||||
'/docs/_*',
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,118 @@
|
||||
import type { Context } from 'https://edge.netlify.com';
|
||||
|
||||
// Configuration - set these in Netlify environment variables
|
||||
const GA_MEASUREMENT_ID =
|
||||
Netlify.env.get('GA_MEASUREMENT_ID') || 'G-XXXXXXXXXX';
|
||||
const GA_API_SECRET = Netlify.env.get('GA_API_SECRET') || '';
|
||||
|
||||
function getClientId(request: Request): string {
|
||||
// Try to extract existing GA client ID from cookie
|
||||
const cookies = request.headers.get('cookie') || '';
|
||||
const gaMatch = cookies.match(/_ga=GA\d+\.\d+\.(\d+\.\d+)/);
|
||||
if (gaMatch) {
|
||||
return gaMatch[1];
|
||||
}
|
||||
|
||||
// Generate a new client ID for this request
|
||||
// For non-browser clients (AI tools), this creates a session-based ID
|
||||
const timestamp = Date.now();
|
||||
const random = Math.floor(Math.random() * 1000000000);
|
||||
return `${random}.${timestamp}`;
|
||||
}
|
||||
|
||||
async function sendToGA4(
|
||||
request: Request,
|
||||
context: Context,
|
||||
pathname: string
|
||||
): Promise<void> {
|
||||
if (!GA_API_SECRET) {
|
||||
console.warn('GA_API_SECRET not configured, skipping analytics');
|
||||
return;
|
||||
}
|
||||
|
||||
const clientId = getClientId(request);
|
||||
const userAgent = request.headers.get('user-agent') || 'unknown';
|
||||
|
||||
// Anthropic: ClaudeBot (training), Claude-User (user fetch), Claude-SearchBot (search index),
|
||||
// Claude-Web (web crawler), anthropic-ai (legacy training)
|
||||
// OpenAI: GPTBot (training), ChatGPT-User (user browsing), OAI-SearchBot (search index)
|
||||
// Perplexity: PerplexityBot (search index), Perplexity-User (user fetch)
|
||||
// Google: Google-Extended (AI/Gemini training)
|
||||
// Other: Bytespider (ByteDance training)
|
||||
const isAITool =
|
||||
/ClaudeBot|Claude-User|Claude-SearchBot|Claude-Web|anthropic-ai|GPTBot|ChatGPT-User|OAI-SearchBot|PerplexityBot|Perplexity-User|Google-Extended|Bytespider/i.test(
|
||||
userAgent
|
||||
);
|
||||
// Generic bots (SEO crawlers, social previews, etc.)
|
||||
const isGenericBot =
|
||||
/Googlebot|Amazonbot|CCBot|BingBot|YandexBot|DuckDuckBot|Applebot|crawler|spider|slurp|facebook|twitter|linkedin|slack|discord|telegram/i.test(
|
||||
userAgent
|
||||
);
|
||||
|
||||
const payload = {
|
||||
client_id: clientId,
|
||||
events: [
|
||||
{
|
||||
name: 'server_page_view',
|
||||
params: {
|
||||
page_location: request.url,
|
||||
page_title: pathname,
|
||||
page_path: pathname,
|
||||
// Custom parameters for filtering
|
||||
content_type: pathname.endsWith('.txt')
|
||||
? 'text/plain'
|
||||
: 'text/markdown',
|
||||
file_extension: pathname.substring(pathname.lastIndexOf('.')),
|
||||
user_agent: userAgent,
|
||||
is_ai_tool: isAITool ? 'true' : 'false',
|
||||
is_bot: isGenericBot ? 'true' : 'false',
|
||||
country: context.geo?.country?.code || 'unknown',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
console.log(`Tracked asset path: ${pathname}`);
|
||||
|
||||
const endpoint = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
|
||||
|
||||
try {
|
||||
await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
} catch (error) {
|
||||
// Log but don't fail the request
|
||||
console.error('Failed to send to GA4:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export default async function handler(
|
||||
request: Request,
|
||||
context: Context
|
||||
): Promise<Response> {
|
||||
const url = new URL(request.url);
|
||||
const pathname = url.pathname;
|
||||
|
||||
// Send analytics in background (non-blocking)
|
||||
context.waitUntil(sendToGA4(request, context, pathname));
|
||||
|
||||
// Continue to serve the actual file
|
||||
const response = await context.next();
|
||||
|
||||
// Netlify Edge Function responses are immutable, so create a new Response
|
||||
const newHeaders = new Headers(response.headers);
|
||||
newHeaders.set('x-nx-edge-function', 'track-asset-requests');
|
||||
|
||||
return new Response(response.body, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: newHeaders,
|
||||
});
|
||||
}
|
||||
|
||||
export const config = {
|
||||
path: ['/**/*.txt', '/**/*.md'],
|
||||
// Something is adding .png.md and .svg.md to get image paths, exclude those.
|
||||
excludedPath: ['/docs/og/*', '/docs/*.svg.md', '/docs/*.png.md'],
|
||||
};
|
||||
@@ -0,0 +1,128 @@
|
||||
import type { Context } from 'https://edge.netlify.com';
|
||||
|
||||
const GA_MEASUREMENT_ID =
|
||||
Netlify.env.get('GA_MEASUREMENT_ID') || 'G-XXXXXXXXXX';
|
||||
const GA_API_SECRET = Netlify.env.get('GA_API_SECRET') || '';
|
||||
|
||||
function getClientId(request: Request): string {
|
||||
const cookies = request.headers.get('cookie') || '';
|
||||
const gaMatch = cookies.match(/_ga=GA\d+\.\d+\.(\d+\.\d+)/);
|
||||
if (gaMatch) return gaMatch[1];
|
||||
|
||||
const timestamp = Date.now();
|
||||
const random = Math.floor(Math.random() * 1000000000);
|
||||
return `${random}.${timestamp}`;
|
||||
}
|
||||
|
||||
async function sendToGA4(
|
||||
request: Request,
|
||||
context: Context,
|
||||
pathname: string
|
||||
): Promise<void> {
|
||||
if (!GA_API_SECRET) {
|
||||
console.warn('GA_API_SECRET not configured, skipping analytics');
|
||||
return;
|
||||
}
|
||||
|
||||
const clientId = getClientId(request);
|
||||
const userAgent = request.headers.get('user-agent') || 'unknown';
|
||||
// Anthropic: ClaudeBot (training), Claude-User (user fetch), Claude-SearchBot (search index),
|
||||
// Claude-Web (web crawler), anthropic-ai (legacy training)
|
||||
// OpenAI: GPTBot (training), ChatGPT-User (user browsing), OAI-SearchBot (search index)
|
||||
// Perplexity: PerplexityBot (search index), Perplexity-User (user fetch)
|
||||
// Google: Google-Extended (AI/Gemini training)
|
||||
// Other: Bytespider (ByteDance training)
|
||||
const isAITool =
|
||||
/ClaudeBot|Claude-User|Claude-SearchBot|Claude-Web|anthropic-ai|GPTBot|ChatGPT-User|OAI-SearchBot|PerplexityBot|Perplexity-User|Google-Extended|Bytespider/i.test(
|
||||
userAgent
|
||||
);
|
||||
// Generic bots (SEO crawlers, social previews, etc.)
|
||||
const isGenericBot =
|
||||
/Googlebot|Amazonbot|CCBot|BingBot|YandexBot|DuckDuckBot|Applebot|crawler|spider|slurp|facebook|twitter|linkedin|slack|discord|telegram/i.test(
|
||||
userAgent
|
||||
);
|
||||
|
||||
const payload = {
|
||||
client_id: clientId,
|
||||
events: [
|
||||
{
|
||||
name: 'server_page_view',
|
||||
params: {
|
||||
page_location: request.url,
|
||||
page_title: pathname,
|
||||
page_path: pathname,
|
||||
content_type: 'text/html',
|
||||
file_extension: '.html',
|
||||
user_agent: userAgent,
|
||||
is_ai_tool: isAITool ? 'true' : 'false',
|
||||
is_bot: isGenericBot ? 'true' : 'false',
|
||||
country: context.geo?.country?.code || 'unknown',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
console.log(`Tracked HTML page: ${pathname}`);
|
||||
|
||||
const endpoint = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
|
||||
|
||||
try {
|
||||
await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to send to GA4:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export default async function handler(
|
||||
request: Request,
|
||||
context: Context
|
||||
): Promise<Response> {
|
||||
const pathname = new URL(request.url).pathname;
|
||||
|
||||
// Always track - filtering is done at config level via `accept: ['text/html']`
|
||||
context.waitUntil(sendToGA4(request, context, pathname));
|
||||
|
||||
const response = await context.next();
|
||||
const newHeaders = new Headers(response.headers);
|
||||
newHeaders.set('x-nx-edge-function', 'track-page-requests');
|
||||
|
||||
return new Response(response.body, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: newHeaders,
|
||||
});
|
||||
}
|
||||
|
||||
export const config = {
|
||||
path: ['/docs/*'],
|
||||
// Only track requests from clients that want HTML (browsers)
|
||||
// This filters out curl, AI agents, and other non-browser clients
|
||||
accept: ['text/html'],
|
||||
excludedPath: [
|
||||
// Text/code files (handled by track-asset-requests or not tracked)
|
||||
'/docs/*.md',
|
||||
'/docs/*.js',
|
||||
'/docs/*.txt',
|
||||
// Images
|
||||
'/docs/*.svg',
|
||||
'/docs/*.png',
|
||||
'/docs/*.jpg',
|
||||
'/docs/*.jpeg',
|
||||
'/docs/*.gif',
|
||||
'/docs/*.webp',
|
||||
'/docs/*.ico',
|
||||
'/docs/images/*',
|
||||
'/docs/og/*',
|
||||
// Fonts
|
||||
'/docs/fonts/*',
|
||||
'/docs/*.woff',
|
||||
'/docs/*.woff2',
|
||||
// Search index (pagefind)
|
||||
'/docs/pagefind/*',
|
||||
// Astro build assets
|
||||
'/docs/_*',
|
||||
],
|
||||
};
|
||||
@@ -16,6 +16,7 @@
|
||||
"@nx/nx-dev-ui-icons": "workspace:*",
|
||||
"@nx/nx-dev-ui-markdoc": "workspace:*",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@types/hast": "^3.0.4",
|
||||
"astro": "^5.10.1",
|
||||
"astro-og-canvas": "^0.7.0",
|
||||
"canvaskit-wasm": "^0.40.0",
|
||||
|
||||
@@ -38,31 +38,9 @@
|
||||
const config = window.__CONFIG || {};
|
||||
if (!config.isProd) return;
|
||||
|
||||
const isCookiebotDisabled = config.cookiebotDisabled ?? false;
|
||||
const gaMeasurementId = config.gaMeasurementId ?? 'UA-88380372-10';
|
||||
const gtmMeasurementId = config.gtmMeasurementId ?? 'GTM-KW8423B6';
|
||||
|
||||
// Initialize global objects
|
||||
window.Cookiebot = window.Cookiebot || {};
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
window.gtag =
|
||||
window.gtag ||
|
||||
function () {
|
||||
window.dataLayer.push(arguments);
|
||||
};
|
||||
|
||||
const loadGoogleAnalytics = () => {
|
||||
const script = document.createElement('script');
|
||||
script.src = `https://www.googletagmanager.com/gtag/js?id=${gaMeasurementId}`;
|
||||
script.async = true;
|
||||
document.head.appendChild(script);
|
||||
|
||||
// Initialize gtag
|
||||
window.gtag('js', new Date());
|
||||
window.gtag('config', gaMeasurementId, {
|
||||
page_path: window.location.pathname,
|
||||
});
|
||||
};
|
||||
|
||||
const loadGTM = () => {
|
||||
if (!gtmMeasurementId) return;
|
||||
@@ -79,70 +57,82 @@
|
||||
})(window, document, 'script', 'dataLayer', gtmMeasurementId);
|
||||
};
|
||||
|
||||
const loadHubSpot = () => {
|
||||
const hsScript = document.createElement('script');
|
||||
hsScript.src = 'https://js.hs-scripts.com/2757427.js';
|
||||
hsScript.async = true;
|
||||
hsScript.defer = true;
|
||||
document.head.appendChild(hsScript);
|
||||
|
||||
// Load HubSpot Forms
|
||||
const hsFormsScript = document.createElement('script');
|
||||
hsFormsScript.src = '//js.hsforms.net/forms/v2.js';
|
||||
hsFormsScript.async = true;
|
||||
hsFormsScript.defer = true;
|
||||
document.head.appendChild(hsFormsScript);
|
||||
// GA4 events are dispatched via GTM dataLayer.
|
||||
const pushGtmEvent = (eventName, payload) => {
|
||||
window.dataLayer.push({ event: eventName, ...payload });
|
||||
};
|
||||
|
||||
const loadApollo = () => {
|
||||
const n = Math.random().toString(36).substring(7);
|
||||
const script = document.createElement('script');
|
||||
script.src = `https://assets.apollo.io/micro/website-tracker/tracker.iife.js?nocache=${n}`;
|
||||
script.async = true;
|
||||
script.defer = true;
|
||||
script.onload = function () {
|
||||
if (window.trackingFunctions?.onLoad) {
|
||||
window.trackingFunctions.onLoad({
|
||||
appId: '65e1db2f1976f30300fd8b26',
|
||||
// Scroll depth tracking
|
||||
const SCROLL_THRESHOLDS = [10, 25, 50, 75, 90];
|
||||
let firedThresholds = new Set();
|
||||
let scrollTrackingEnabled = false;
|
||||
let scrollRafId = null;
|
||||
|
||||
function getScrollPercentage() {
|
||||
const scrollTop = window.scrollY || document.documentElement.scrollTop;
|
||||
const scrollHeight = document.documentElement.scrollHeight;
|
||||
const clientHeight = window.innerHeight;
|
||||
return (scrollTop + clientHeight) / scrollHeight;
|
||||
}
|
||||
|
||||
function handleScrollTracking() {
|
||||
if (!scrollTrackingEnabled) return;
|
||||
|
||||
const scrollPercentage = getScrollPercentage() * 100;
|
||||
|
||||
// Fire events for all thresholds we've passed but haven't fired yet
|
||||
for (const threshold of SCROLL_THRESHOLDS) {
|
||||
if (scrollPercentage >= threshold && !firedThresholds.has(threshold)) {
|
||||
firedThresholds.add(threshold);
|
||||
sendSearchEvent(`scroll_${threshold}`, {
|
||||
event_category: 'scroll',
|
||||
event_label: window.location.pathname,
|
||||
});
|
||||
}
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const loadHotjar = () => {
|
||||
(function (h, o, t, j, a, r) {
|
||||
h.hj =
|
||||
h.hj ||
|
||||
function () {
|
||||
(h.hj.q = h.hj.q || []).push(arguments);
|
||||
};
|
||||
h._hjSettings = { hjid: 2774127, hjsv: 6 };
|
||||
a = o.getElementsByTagName('head')[0];
|
||||
r = o.createElement('script');
|
||||
r.async = 1;
|
||||
r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv;
|
||||
a.appendChild(r);
|
||||
})(window, document, 'https://static.hotjar.com/c/hotjar-', '.js?sv=');
|
||||
};
|
||||
function throttledScrollHandler() {
|
||||
if (scrollRafId !== null) return;
|
||||
|
||||
const loadTwitterPixel = () => {
|
||||
!(function (e, t, n, s, u, a) {
|
||||
e.twq ||
|
||||
((s = e.twq =
|
||||
function () {
|
||||
s.exe ? s.exe.apply(s, arguments) : s.queue.push(arguments);
|
||||
}),
|
||||
(s.version = '1.1'),
|
||||
(s.queue = []),
|
||||
(u = t.createElement(n)),
|
||||
(u.async = !0),
|
||||
(u.src = 'https://static.ads-twitter.com/uwt.js'),
|
||||
(a = t.getElementsByTagName(n)[0]),
|
||||
a.parentNode.insertBefore(u, a));
|
||||
})(window, document, 'script');
|
||||
window.twq('config', 'obtp4');
|
||||
};
|
||||
scrollRafId = requestAnimationFrame(() => {
|
||||
handleScrollTracking();
|
||||
scrollRafId = null;
|
||||
});
|
||||
}
|
||||
|
||||
function attachScrollListener() {
|
||||
window.addEventListener('scroll', throttledScrollHandler, {
|
||||
passive: true,
|
||||
});
|
||||
}
|
||||
|
||||
function setupScrollTracking() {
|
||||
// Reset scroll depth on navigation (for SPA-like behavior via View Transitions)
|
||||
firedThresholds = new Set();
|
||||
scrollTrackingEnabled = false;
|
||||
|
||||
// Delay tracking start to avoid false triggers during navigation
|
||||
setTimeout(() => {
|
||||
scrollTrackingEnabled = true;
|
||||
// Immediately check current scroll position to capture thresholds
|
||||
// that may have been passed during the delay
|
||||
handleScrollTracking();
|
||||
}, 500);
|
||||
|
||||
attachScrollListener();
|
||||
|
||||
// Handle Astro View Transitions - reset on navigation
|
||||
document.addEventListener('astro:after-swap', () => {
|
||||
firedThresholds = new Set();
|
||||
scrollTrackingEnabled = false;
|
||||
setTimeout(() => {
|
||||
scrollTrackingEnabled = true;
|
||||
// Immediately check current scroll position after navigation
|
||||
handleScrollTracking();
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
const SEARCH_DEBOUNCE_MS = 1000;
|
||||
let searchDebounceTimer;
|
||||
@@ -151,8 +141,7 @@
|
||||
let inputHandler = null;
|
||||
|
||||
function sendSearchEvent(eventType, data) {
|
||||
if (typeof window.gtag !== 'undefined')
|
||||
window.gtag('event', eventType, data);
|
||||
pushGtmEvent(eventType, data);
|
||||
}
|
||||
|
||||
function trackSearchQuery(query) {
|
||||
@@ -203,31 +192,11 @@
|
||||
});
|
||||
}
|
||||
|
||||
const checkAndLoadScripts = () => {
|
||||
if (isCookiebotDisabled) {
|
||||
loadGoogleAnalytics();
|
||||
loadGTM();
|
||||
loadHubSpot();
|
||||
setupSearchTracking();
|
||||
} else if (window.Cookiebot && window.Cookiebot.consent) {
|
||||
// Statistics cookies (Google Analytics, GTM, Search Tracking)
|
||||
if (window.Cookiebot.consent.statistics) {
|
||||
loadGoogleAnalytics();
|
||||
loadGTM();
|
||||
setupSearchTracking();
|
||||
}
|
||||
|
||||
// Marketing cookies (HubSpot, Apollo, Hotjar, Twitter)
|
||||
if (window.Cookiebot.consent.marketing) {
|
||||
loadHubSpot();
|
||||
loadApollo();
|
||||
loadHotjar();
|
||||
loadTwitterPixel();
|
||||
}
|
||||
} else {
|
||||
// Wait for Cookiebot to load
|
||||
setTimeout(checkAndLoadScripts, 100);
|
||||
}
|
||||
const initializeAnalytics = () => {
|
||||
if (!gtmMeasurementId) return;
|
||||
loadGTM();
|
||||
setupSearchTracking();
|
||||
setupScrollTracking();
|
||||
};
|
||||
|
||||
// Add GTM noscript iframe to body
|
||||
@@ -244,11 +213,8 @@
|
||||
document.body.insertBefore(noscript, document.body.firstChild);
|
||||
};
|
||||
|
||||
// Listen for user consent to cookies
|
||||
window.addEventListener('CookiebotOnAccept', checkAndLoadScripts);
|
||||
|
||||
// Initial check
|
||||
checkAndLoadScripts();
|
||||
initializeAnalytics();
|
||||
|
||||
// Add GTM noscript on DOM ready
|
||||
if (document.readyState === 'loading') {
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
<svg width="980" height="720" viewBox="0 0 980 720" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Defs: arrow markers -->
|
||||
<defs>
|
||||
<marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto" markerUnits="strokeWidth">
|
||||
<polygon points="0 0, 10 3.5, 0 7" fill="#476088"/>
|
||||
</marker>
|
||||
<marker id="arrowGreen" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto" markerUnits="strokeWidth">
|
||||
<polygon points="0 0, 10 3.5, 0 7" fill="#70AF40"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- Outer dotted border: Synthetic Monorepo boundary -->
|
||||
<rect x="40" y="40" width="900" height="590" rx="24" ry="24"
|
||||
stroke="#94A3B8" stroke-width="3" stroke-dasharray="12 6" fill="none"/>
|
||||
|
||||
<!-- "Synthetic Monorepo" label at bottom inside box -->
|
||||
<text x="490" y="665" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="26" font-weight="700" fill="#94A3B8">Synthetic Monorepo</text>
|
||||
|
||||
<!-- ============================================ -->
|
||||
<!-- Monorepo A (top-left): a rounded rect with 3 internal projects -->
|
||||
<!-- ============================================ -->
|
||||
<rect x="80" y="70" width="260" height="200" rx="16" ry="16"
|
||||
stroke="#9CA3AF" stroke-width="2" fill="none"/>
|
||||
<text x="210" y="100" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="14" font-weight="600" fill="#9CA3AF">Monorepo A</text>
|
||||
|
||||
<!-- App 1 (blue) -->
|
||||
<circle cx="140" cy="165" r="38" fill="#476088"/>
|
||||
<text x="140" y="161" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="600" fill="white">App 1</text>
|
||||
<text x="140" y="178" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="11" fill="#CBD5E1">frontend</text>
|
||||
|
||||
<!-- Lib A (green) -->
|
||||
<circle cx="270" cy="145" r="32" fill="#70AF40"/>
|
||||
<text x="270" y="141" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="600" fill="white">Lib A</text>
|
||||
<text x="270" y="157" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="11" fill="#E8F5E9">utils</text>
|
||||
|
||||
<!-- Lib B (green) -->
|
||||
<circle cx="248" cy="222" r="28" fill="#70AF40"/>
|
||||
<text x="248" y="218" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="12" font-weight="600" fill="white">Lib B</text>
|
||||
<text x="248" y="233" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="10" fill="#E8F5E9">ui</text>
|
||||
|
||||
<!-- Internal arrows within Monorepo A -->
|
||||
<line x1="175" y1="155" x2="238" y2="143" stroke="#9CA3AF" stroke-width="1.5" marker-end="url(#arrowBlue)" opacity="0.4"/>
|
||||
<line x1="165" y1="190" x2="225" y2="212" stroke="#9CA3AF" stroke-width="1.5" marker-end="url(#arrowBlue)" opacity="0.4"/>
|
||||
|
||||
<!-- ============================================ -->
|
||||
<!-- Monorepo B (right): a rounded rect with 2 internal projects -->
|
||||
<!-- ============================================ -->
|
||||
<rect x="640" y="70" width="260" height="200" rx="16" ry="16"
|
||||
stroke="#9CA3AF" stroke-width="2" fill="none"/>
|
||||
<text x="770" y="100" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="14" font-weight="600" fill="#9CA3AF">Monorepo B</text>
|
||||
|
||||
<!-- App 2 (blue) -->
|
||||
<circle cx="710" cy="170" r="38" fill="#476088"/>
|
||||
<text x="710" y="166" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="600" fill="white">App 2</text>
|
||||
<text x="710" y="183" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="11" fill="#CBD5E1">backend</text>
|
||||
|
||||
<!-- Lib C (green) -->
|
||||
<circle cx="840" cy="185" r="32" fill="#70AF40"/>
|
||||
<text x="840" y="181" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="600" fill="white">Lib C</text>
|
||||
<text x="840" y="197" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="11" fill="#E8F5E9">auth</text>
|
||||
|
||||
<!-- Internal arrow within Monorepo B -->
|
||||
<line x1="745" y1="175" x2="808" y2="182" stroke="#9CA3AF" stroke-width="1.5" marker-end="url(#arrowBlue)" opacity="0.4"/>
|
||||
|
||||
<!-- ============================================ -->
|
||||
<!-- Standalone repos -->
|
||||
<!-- ============================================ -->
|
||||
|
||||
<!-- Lib D (green, standalone, CENTER - pushed down to middle row) -->
|
||||
<circle cx="490" cy="340" r="38" fill="#70AF40"/>
|
||||
<text x="490" y="336" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="600" fill="white">Lib D</text>
|
||||
<text x="490" y="352" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="11" fill="#E8F5E9">shared</text>
|
||||
<text x="490" y="390" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="11" font-weight="500" fill="#9CA3AF">standalone repo</text>
|
||||
|
||||
<!-- App 3 (blue, standalone, bottom-left) -->
|
||||
<circle cx="200" cy="500" r="42" fill="#476088"/>
|
||||
<text x="200" y="496" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="600" fill="white">App 3</text>
|
||||
<text x="200" y="513" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="11" fill="#CBD5E1">mobile</text>
|
||||
<text x="200" y="554" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="11" font-weight="500" fill="#9CA3AF">standalone repo</text>
|
||||
|
||||
<!-- Lib E (green, standalone, bottom-center) -->
|
||||
<circle cx="490" cy="520" r="32" fill="#70AF40"/>
|
||||
<text x="490" y="516" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="600" fill="white">Lib E</text>
|
||||
<text x="490" y="532" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="11" fill="#E8F5E9">design</text>
|
||||
<text x="490" y="564" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="11" font-weight="500" fill="#9CA3AF">standalone repo</text>
|
||||
|
||||
<!-- App 4 (blue, standalone, bottom-right) -->
|
||||
<circle cx="770" cy="500" r="42" fill="#476088"/>
|
||||
<text x="770" y="496" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="600" fill="white">App 4</text>
|
||||
<text x="770" y="513" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="11" fill="#CBD5E1">API</text>
|
||||
<text x="770" y="554" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-size="11" font-weight="500" fill="#9CA3AF">standalone repo</text>
|
||||
|
||||
<!-- ============================================ -->
|
||||
<!-- Cross-repo dependency arrows -->
|
||||
<!-- ============================================ -->
|
||||
|
||||
<!-- App 2 → Lib D (shared): curve down-left -->
|
||||
<path d="M 675 185 C 620 240, 560 280, 525 325" stroke="#476088" stroke-width="2.5" fill="none" marker-end="url(#arrowBlue)"/>
|
||||
|
||||
<!-- Lib A → Lib D: gentle arc down -->
|
||||
<path d="M 300 155 C 370 200, 420 260, 458 322" stroke="#70AF40" stroke-width="2" fill="none" marker-end="url(#arrowGreen)"/>
|
||||
|
||||
<!-- App 3 → Lib B (in Monorepo A): straight up -->
|
||||
<path d="M 210 458 C 220 380, 230 320, 242 252" stroke="#476088" stroke-width="2.5" fill="none" marker-end="url(#arrowBlue)"/>
|
||||
|
||||
<!-- App 3 → Lib E (design): horizontal -->
|
||||
<path d="M 242 500 C 320 490, 400 500, 458 515" stroke="#476088" stroke-width="2.5" fill="none" marker-end="url(#arrowBlue)"/>
|
||||
|
||||
<!-- App 4 → Lib C (in Monorepo B): straight up -->
|
||||
<path d="M 790 458 C 805 380, 820 310, 835 217" stroke="#476088" stroke-width="2.5" fill="none" marker-end="url(#arrowBlue)"/>
|
||||
|
||||
<!-- App 4 → Lib D (shared): arc up-left -->
|
||||
<path d="M 730 490 C 650 460, 570 410, 525 358" stroke="#476088" stroke-width="2.5" fill="none" marker-end="url(#arrowBlue)"/>
|
||||
|
||||
<!-- App 4 → Lib E (design): horizontal left -->
|
||||
<path d="M 728 505 C 660 510, 580 515, 522 518" stroke="#476088" stroke-width="2" fill="none" marker-end="url(#arrowBlue)"/>
|
||||
|
||||
<!-- Lib D → Lib C (cross-repo lib dependency): arc up-right -->
|
||||
<path d="M 525 330 C 620 300, 740 260, 812 200" stroke="#70AF40" stroke-width="2" fill="none" marker-end="url(#arrowGreen)"/>
|
||||
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 155 KiB |
|
After Width: | Height: | Size: 409 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 297 KiB |
@@ -2,70 +2,119 @@
|
||||
import { Icon } from '@astrojs/starlight/components';
|
||||
import { getCollection } from 'astro:content';
|
||||
|
||||
const { pathname } = Astro.url;
|
||||
|
||||
const cleanedPath = pathname.split('?')[0];
|
||||
|
||||
const allDocs = await getCollection('docs');
|
||||
|
||||
const pathSegments = cleanedPath.split('/').filter(Boolean);
|
||||
|
||||
type Crumb = {
|
||||
id: string;
|
||||
name: string;
|
||||
href: string;
|
||||
label: string;
|
||||
href?: string;
|
||||
current: boolean;
|
||||
};
|
||||
|
||||
function slugify(label: string): string {
|
||||
return label
|
||||
.replace(/\.NET/g, 'dotnet')
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9_]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
}
|
||||
|
||||
function findDocByPath (path: string) {
|
||||
path = path.replace(/^docs\//, '');
|
||||
return allDocs.find(d => d.id === `${path}`)
|
||||
?? allDocs.find(d => d.id === `${path}/index`);
|
||||
// --- Primary: Sidebar-based breadcrumbs ---
|
||||
const sidebar = Astro.locals.starlightRoute.sidebar;
|
||||
|
||||
function findBreadcrumbPath(
|
||||
entries: typeof sidebar,
|
||||
path: Crumb[] = [],
|
||||
slugSegments: string[] = []
|
||||
): Crumb[] | null {
|
||||
for (const entry of entries) {
|
||||
if (entry.type === 'link' && entry.isCurrent) {
|
||||
return [...path, { label: entry.label, href: entry.href, current: true }];
|
||||
}
|
||||
if (entry.type === 'group') {
|
||||
const currentSlugs = [...slugSegments, slugify(entry.label)];
|
||||
const groupHref = `/docs/${currentSlugs.join('/')}`;
|
||||
const result = findBreadcrumbPath(
|
||||
entry.entries,
|
||||
[
|
||||
...path,
|
||||
{ label: entry.label, href: groupHref, current: false },
|
||||
],
|
||||
currentSlugs
|
||||
);
|
||||
if (result) return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function createNameFromSegment(segment: string): string {
|
||||
let crumbs: Crumb[] = findBreadcrumbPath(sidebar) ?? [];
|
||||
|
||||
// --- Fallback: URL-path-based breadcrumbs ---
|
||||
if (crumbs.length === 0) {
|
||||
const { pathname } = Astro.url;
|
||||
const cleanedPath = pathname.split('?')[0];
|
||||
const allDocs = await getCollection('docs');
|
||||
const pathSegments = cleanedPath.split('/').filter(Boolean);
|
||||
|
||||
function findDocByPath(path: string) {
|
||||
path = path.replace(/^docs\//, '');
|
||||
return (
|
||||
allDocs.find((d) => d.id === `${path}`) ??
|
||||
allDocs.find((d) => d.id === `${path}/index`)
|
||||
);
|
||||
}
|
||||
|
||||
function createNameFromSegment(segment: string): string {
|
||||
segment = segment.split('#')[0];
|
||||
return segment
|
||||
.split('-')
|
||||
.map(s => s.charAt(0).toUpperCase() + s.slice(1))
|
||||
.join(' ');
|
||||
.split('-')
|
||||
.map((s) => s.charAt(0).toUpperCase() + s.slice(1))
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
crumbs = pathSegments
|
||||
.map((segment, index) => {
|
||||
const docPath = pathSegments.slice(0, index + 1).join('/');
|
||||
const doc = findDocByPath(docPath);
|
||||
const label =
|
||||
doc?.data?.sidebar?.label ||
|
||||
doc?.data?.title ||
|
||||
createNameFromSegment(segment);
|
||||
// If `base` is set in config, don't include it in breadcrumbs
|
||||
if (index === 0 && import.meta.env.BASE_URL) return null;
|
||||
return {
|
||||
label,
|
||||
href: `/${docPath}`,
|
||||
current: index === pathSegments.length - 1,
|
||||
};
|
||||
})
|
||||
.filter(Boolean) as Crumb[];
|
||||
}
|
||||
|
||||
const crumbs: Crumb[] = pathSegments.map((segment, index) => {
|
||||
const docPath = pathSegments.slice(0, index + 1).join('/');
|
||||
const doc = findDocByPath(docPath);
|
||||
const name = doc?.data?.sidebar?.label || doc?.data?.title || createNameFromSegment(segment);
|
||||
// If `base` is set in config, don't include it in breadcrumbs
|
||||
if (index === 0 && import.meta.env.BASE_URL) return null;
|
||||
return {
|
||||
id: segment,
|
||||
name,
|
||||
href: `/${docPath}`,
|
||||
current: index === pathSegments.length - 1,
|
||||
};
|
||||
}).filter(Boolean);
|
||||
---
|
||||
|
||||
|
||||
<nav class="not-content flex mb-4" aria-label="Breadcrumb">
|
||||
<ol role="list" class="flex m-0 p-0 flex-wrap items-center space-x-2 text-sm">
|
||||
{crumbs.map((crumb, index) => (
|
||||
<li class="flex items-center">
|
||||
{index > 0 && (
|
||||
<Icon name="right-caret" class="w-5 h-5 ml-2 mr-2"/>
|
||||
)}
|
||||
<a
|
||||
href={crumb.href}
|
||||
class={`no-underline text-sm${
|
||||
crumb.current
|
||||
? ' text-slate-900 dark:text-slate-100 font-semibold'
|
||||
: ' text-slate-500 dark:text-slate-400 font-medium hover:text-slate-700 dark:hover:text-slate-200'
|
||||
} transition-colors`}
|
||||
aria-current={crumb.current ? 'page' : undefined}
|
||||
>
|
||||
{crumb.name}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
{
|
||||
crumbs.map((crumb, index) => (
|
||||
<li class="flex items-center">
|
||||
{index > 0 && <Icon name="right-caret" class="w-5 h-5 ml-2 mr-2" />}
|
||||
{crumb.href ? (
|
||||
<a
|
||||
href={crumb.href}
|
||||
class={`no-underline text-sm${
|
||||
crumb.current
|
||||
? ' text-slate-900 dark:text-slate-100 font-semibold'
|
||||
: ' text-slate-500 dark:text-slate-400 font-medium hover:text-slate-700 dark:hover:text-slate-200'
|
||||
} transition-colors`}
|
||||
aria-current={crumb.current ? 'page' : undefined}
|
||||
>
|
||||
{crumb.label}
|
||||
</a>
|
||||
) : (
|
||||
<span class="text-sm text-slate-500 dark:text-slate-400 font-medium">
|
||||
{crumb.label}
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
@@ -447,13 +447,6 @@ const currentVersion = versions.find((v) => v.current);
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-px bg-slate-200 mx-1 dark:bg-slate-700"></div>
|
||||
<a
|
||||
id="header-ai-link"
|
||||
href={`${nxDevUrl}/ai`}
|
||||
class="px-3 py-2 text-sm font-medium text-slate-600 hover:text-blue-500 rounded-md transition-colors whitespace-nowrap no-underline dark:text-slate-200 dark:hover:text-sky-500"
|
||||
>
|
||||
AI
|
||||
</a>
|
||||
<a
|
||||
id="header-nx-cloud-link"
|
||||
href={`${nxDevUrl}/nx-cloud`}
|
||||
@@ -516,7 +509,7 @@ const currentVersion = versions.find((v) => v.current);
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import { sendCustomEvent } from '@nx/nx-dev-feature-analytics';
|
||||
import { sendCustomEventViaGtm } from '@nx/nx-dev-feature-analytics';
|
||||
|
||||
function setupAnalyticsTracking() {
|
||||
const docsHomeLink = document.getElementById('header-docs-home-link');
|
||||
@@ -528,7 +521,7 @@ const currentVersion = versions.find((v) => v.current);
|
||||
const tryNxCloudBtn = document.getElementById('header-try-nx-cloud-btn');
|
||||
|
||||
docsHomeLink?.addEventListener('click', () => {
|
||||
sendCustomEvent(
|
||||
sendCustomEventViaGtm(
|
||||
'documentation-click',
|
||||
'header-navigation',
|
||||
'documentation-header'
|
||||
@@ -536,7 +529,7 @@ const currentVersion = versions.find((v) => v.current);
|
||||
});
|
||||
|
||||
aiLink?.addEventListener('click', () => {
|
||||
sendCustomEvent(
|
||||
sendCustomEventViaGtm(
|
||||
'ai-click',
|
||||
'header-navigation',
|
||||
'documentation-header'
|
||||
@@ -544,7 +537,7 @@ const currentVersion = versions.find((v) => v.current);
|
||||
});
|
||||
|
||||
nxCloudLink?.addEventListener('click', () => {
|
||||
sendCustomEvent(
|
||||
sendCustomEventViaGtm(
|
||||
'nx-cloud-click',
|
||||
'header-navigation',
|
||||
'documentation-header'
|
||||
@@ -552,7 +545,7 @@ const currentVersion = versions.find((v) => v.current);
|
||||
});
|
||||
|
||||
pricingLink?.addEventListener('click', () => {
|
||||
sendCustomEvent(
|
||||
sendCustomEventViaGtm(
|
||||
'pricing-click',
|
||||
'header-navigation',
|
||||
'documentation-header'
|
||||
@@ -560,7 +553,7 @@ const currentVersion = versions.find((v) => v.current);
|
||||
});
|
||||
|
||||
enterpriseLink?.addEventListener('click', () => {
|
||||
sendCustomEvent(
|
||||
sendCustomEventViaGtm(
|
||||
'enterprise-click',
|
||||
'header-navigation',
|
||||
'documentation-header'
|
||||
@@ -568,11 +561,19 @@ const currentVersion = versions.find((v) => v.current);
|
||||
});
|
||||
|
||||
contactBtn?.addEventListener('click', () => {
|
||||
sendCustomEvent('contact-click', 'header-cta', 'documentation-header');
|
||||
sendCustomEventViaGtm(
|
||||
'contact-click',
|
||||
'header-cta',
|
||||
'documentation-header'
|
||||
);
|
||||
});
|
||||
|
||||
tryNxCloudBtn?.addEventListener('click', () => {
|
||||
sendCustomEvent('login-click', 'header-cta', 'documentation-header');
|
||||
sendCustomEventViaGtm(
|
||||
'login-click',
|
||||
'header-cta',
|
||||
'documentation-header'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ const bannerId = bannerConfig ? `${bannerConfig.title}-${bannerConfig.activeUnti
|
||||
<GitHubStarWidget starsCount={githubStarsCount} client:load />
|
||||
<div class="flex flex-col mx-2 gap-2">
|
||||
<a
|
||||
href="https://nx.dev/contact"
|
||||
href={`${import.meta.env.SITE || 'https://nx.dev'}/contact`}
|
||||
class="w-full inline-flex items-center justify-center px-4 py-2 text-sm font-medium rounded-md transition no-underline border border-slate-300 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-200 hover:bg-slate-50 dark:hover:bg-slate-700 shadow-sm"
|
||||
title="Contact Us"
|
||||
>
|
||||
@@ -111,7 +111,7 @@ const bannerId = bannerConfig ? `${bannerConfig.title}-${bannerConfig.activeUnti
|
||||
inset-block: var(--sl-nav-height) 0;
|
||||
inset-inline-start: 0;
|
||||
width: 100%;
|
||||
background-color: var(--sl-color-black);
|
||||
background-color: var(--sl-color-bg-sidebar);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,24 +2,81 @@
|
||||
import MobileMenuFooter from '@astrojs/starlight/components/MobileMenuFooter.astro'
|
||||
import SidebarPersister from '@astrojs/starlight/components/SidebarPersister.astro'
|
||||
import SidebarSublist from './SidebarSublist.astro'
|
||||
import { GitHubStarWidget } from '@nx/nx-dev-ui-common/src/lib/github-star-widget';
|
||||
import TabbedSidebar from './sidebar-tabs/TabbedSidebar.astro'
|
||||
import SidebarTab from './sidebar-tabs/SidebarTab.astro'
|
||||
import SidebarTabPanel from './sidebar-tabs/SidebarTabPanel.astro'
|
||||
import type { SidebarEntry } from './SidebarSublist.astro'
|
||||
import { sidebarTabs } from '../../../sidebar.mts'
|
||||
|
||||
const { sidebar } = Astro.locals.starlightRoute
|
||||
const githubStarsCount = Astro.locals.githubStarsCount ?? 0;
|
||||
|
||||
// Check if any entry in a tree has isCurrent
|
||||
function hasCurrentPage(entries: SidebarEntry[]): boolean {
|
||||
return entries.some((entry) => {
|
||||
if (entry.type === 'link') return entry.isCurrent
|
||||
return hasCurrentPage(entry.entries)
|
||||
})
|
||||
}
|
||||
|
||||
// For each tab, resolve its groups from the sidebar using the tab's group labels
|
||||
const tabs = sidebarTabs.map((config) => {
|
||||
const groupLabels = config.groups.map((g) =>
|
||||
typeof g === 'string' ? g : g.label
|
||||
)
|
||||
const groups = sidebar.filter(
|
||||
(entry: SidebarEntry) => entry.type === 'group' && groupLabels.includes(entry.label)
|
||||
)
|
||||
// Single-group tabs: unwrap the top-level group to avoid redundant heading
|
||||
const isSingleGroup = groups.length === 1 && groups[0].type === 'group'
|
||||
const entries: SidebarEntry[] = isSingleGroup ? (groups[0] as any).entries : groups
|
||||
const active = hasCurrentPage(groups)
|
||||
return { ...config, entries, active }
|
||||
})
|
||||
|
||||
// Exactly one tab should be active; if none matched, leave it for the client to decide
|
||||
const anyActive = tabs.some((t) => t.active)
|
||||
---
|
||||
|
||||
<div class="sidebar-wrapper" data-testid="sidebar-wrapper">
|
||||
<SidebarPersister>
|
||||
<SidebarSublist sublist={sidebar}/>
|
||||
<TabbedSidebar>
|
||||
{tabs.map((tab) => (
|
||||
<SidebarTab
|
||||
slot="tabs"
|
||||
id={tab.id}
|
||||
icon={tab.icon}
|
||||
label={tab.label}
|
||||
active={anyActive ? tab.active : false}
|
||||
/>
|
||||
))}
|
||||
{tabs.map((tab) => (
|
||||
<SidebarTabPanel
|
||||
slot="panels"
|
||||
id={`${tab.id}-panel`}
|
||||
tabId={tab.id}
|
||||
active={anyActive ? tab.active : false}
|
||||
>
|
||||
<SidebarSublist sublist={tab.entries} />
|
||||
</SidebarTabPanel>
|
||||
))}
|
||||
</TabbedSidebar>
|
||||
</SidebarPersister>
|
||||
<div class="md:sl-hidden">
|
||||
<MobileMenuFooter/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Enable sidebar expand/collapse animations after first paint so that
|
||||
// groups that are already open on page load don't animate from closed.
|
||||
requestAnimationFrame(() => {
|
||||
document.querySelector('.sidebar-wrapper')?.classList.add('sidebar-animate');
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.sidebar-wrapper :global(a) {
|
||||
color: var(--sl-color-gray-4);
|
||||
color: var(--sl-color-gray-3);
|
||||
}
|
||||
|
||||
.sidebar-wrapper :global(a[aria-current=page]) {
|
||||
@@ -35,11 +92,15 @@ const githubStarsCount = Astro.locals.githubStarsCount ?? 0;
|
||||
}
|
||||
|
||||
.sidebar-wrapper :global(details summary .group-label) {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.sidebar-wrapper :global(ul ul summary) {
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.sidebar-wrapper :global(details[open] summary .group-label) {
|
||||
color: var(--sl-color-gray-2);
|
||||
color: var(--sl-color-text-accent);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,43 +3,43 @@
|
||||
* This is a modified version of the SidebarSublist component from Starlight with support for icons.
|
||||
* https://github.com/withastro/starlight/blob/46524ac/packages/starlight/components/SidebarSublist.astro -->
|
||||
*/
|
||||
import { Badge, Icon } from '@astrojs/starlight/components'
|
||||
import { Badge, Icon } from '@astrojs/starlight/components';
|
||||
|
||||
export interface SidebarLink {
|
||||
type: 'link'
|
||||
label: string
|
||||
href: string
|
||||
isCurrent: boolean
|
||||
badge: any
|
||||
attrs: any
|
||||
type: 'link';
|
||||
label: string;
|
||||
href: string;
|
||||
isCurrent: boolean;
|
||||
badge: any;
|
||||
attrs: any;
|
||||
}
|
||||
|
||||
export interface SidebarGroup {
|
||||
type: 'group'
|
||||
label: string
|
||||
entries: (SidebarLink | SidebarGroup)[]
|
||||
collapsed: boolean
|
||||
badge: any
|
||||
attrs?: any
|
||||
type: 'group';
|
||||
label: string;
|
||||
entries: (SidebarLink | SidebarGroup)[];
|
||||
collapsed: boolean;
|
||||
badge: any;
|
||||
attrs?: any;
|
||||
}
|
||||
|
||||
export type SidebarEntry = SidebarLink | SidebarGroup
|
||||
export type SidebarEntry = SidebarLink | SidebarGroup;
|
||||
|
||||
interface Props {
|
||||
sublist: SidebarEntry[]
|
||||
sublist: SidebarEntry[];
|
||||
|
||||
nested?: boolean
|
||||
nested?: boolean;
|
||||
}
|
||||
|
||||
const { sublist, nested } = Astro.props
|
||||
const { sublist, nested } = Astro.props;
|
||||
|
||||
// Copied from https://github.com/withastro/starlight/blob/46524ac/packages/starlight/utils/navigation.ts#L447
|
||||
function flattenSidebar(entries: SidebarEntry[]): SidebarEntry[] {
|
||||
return entries.reduce<SidebarEntry[]>((acc, entry) => {
|
||||
if (entry.type === 'group') acc.push(...flattenSidebar(entry.entries))
|
||||
else acc.push(entry)
|
||||
return acc
|
||||
}, [])
|
||||
if (entry.type === 'group') acc.push(...flattenSidebar(entry.entries));
|
||||
else acc.push(entry);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
function getIconPath(iconName: string | undefined): string | null {
|
||||
@@ -53,7 +53,7 @@ function getIconPath(iconName: string | undefined): string | null {
|
||||
sublist.map((entry) => {
|
||||
const icon = getIconPath(entry.attrs?.['data-icon']);
|
||||
return (
|
||||
<li class={icon ? 'p-0 border-none' : ''}>
|
||||
<li class={icon ? 'border-none p-0' : ''}>
|
||||
{entry.type === 'link' ? (
|
||||
<a
|
||||
href={entry.href}
|
||||
@@ -66,35 +66,47 @@ function getIconPath(iconName: string | undefined): string | null {
|
||||
aria-hidden="true"
|
||||
src={icon}
|
||||
alt=""
|
||||
class="sidebar-icon w-4 h-4 dark:invert"
|
||||
class="sidebar-icon h-4 w-4 dark:invert"
|
||||
/>
|
||||
)}
|
||||
<span>{entry.label}</span>
|
||||
{entry.badge &&
|
||||
<Badge variant={entry.badge.variant} class={entry.badge.class}
|
||||
text={entry.badge.text}/>}
|
||||
{entry.badge && (
|
||||
<Badge
|
||||
variant={entry.badge.variant}
|
||||
class={entry.badge.class}
|
||||
text={entry.badge.text}
|
||||
/>
|
||||
)}
|
||||
</a>
|
||||
) : (
|
||||
<details
|
||||
open={flattenSidebar(entry.entries).some((i: any) => i.isCurrent) || !entry.collapsed}>
|
||||
<summary class={icon ? 'pl-0 py-2' : ''}>
|
||||
open={
|
||||
flattenSidebar(entry.entries).some((i: any) => i.isCurrent) ||
|
||||
!entry.collapsed
|
||||
}
|
||||
>
|
||||
<summary class={icon ? 'py-2 pl-0' : ''}>
|
||||
<div class="group-label">
|
||||
{icon && (
|
||||
<img
|
||||
aria-hidden="true"
|
||||
src={icon}
|
||||
alt=""
|
||||
class="sidebar-icon w-4 h-4 dark:invert"
|
||||
class="sidebar-icon h-4 w-4 dark:invert"
|
||||
/>
|
||||
)}
|
||||
<span class="large">{entry.label}</span>
|
||||
{entry.badge && (
|
||||
<Badge variant={entry.badge.variant} class={entry.badge.class} text={entry.badge.text}/>
|
||||
<Badge
|
||||
variant={entry.badge.variant}
|
||||
class={entry.badge.class}
|
||||
text={entry.badge.text}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Icon name="right-caret" class="caret" size="1.25rem"/>
|
||||
<Icon name="right-caret" class="caret" size="1.25rem" />
|
||||
</summary>
|
||||
<Astro.self sublist={entry.entries} nested/>
|
||||
<Astro.self sublist={entry.entries} nested />
|
||||
</details>
|
||||
)}
|
||||
</li>
|
||||
@@ -107,7 +119,7 @@ function getIconPath(iconName: string | undefined): string | null {
|
||||
<style>
|
||||
@layer starlight.core {
|
||||
ul {
|
||||
--sl-sidebar-item-padding-inline: 0.5rem;
|
||||
--sl-sidebar-item-padding-inline: 0.375rem;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
@@ -128,8 +140,18 @@ function getIconPath(iconName: string | undefined): string | null {
|
||||
color: var(--sl-color-white);
|
||||
}
|
||||
|
||||
a.large {
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.top-level {
|
||||
padding-top: 0.75rem;
|
||||
}
|
||||
|
||||
.top-level > li + li {
|
||||
margin-top: 0.75rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
summary {
|
||||
@@ -140,6 +162,17 @@ function getIconPath(iconName: string | undefined): string | null {
|
||||
line-height: 1.4;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
font-weight: 600;
|
||||
color: var(--sl-color-white);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
summary:hover {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--sl-color-gray-5) 40%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
summary::marker,
|
||||
@@ -148,8 +181,16 @@ function getIconPath(iconName: string | undefined): string | null {
|
||||
}
|
||||
|
||||
.caret {
|
||||
transition: transform 0.2s ease-in-out;
|
||||
transition:
|
||||
transform 0.2s ease-in-out,
|
||||
color 0.15s ease;
|
||||
flex-shrink: 0;
|
||||
color: var(--sl-color-gray-4);
|
||||
}
|
||||
|
||||
.caret:hover,
|
||||
summary:hover .caret {
|
||||
color: var(--sl-color-gray-2);
|
||||
}
|
||||
|
||||
:global([dir='rtl']) .caret {
|
||||
@@ -158,6 +199,11 @@ function getIconPath(iconName: string | undefined): string | null {
|
||||
|
||||
[open] > summary .caret {
|
||||
transform: rotateZ(90deg);
|
||||
color: var(--sl-color-text-accent);
|
||||
}
|
||||
|
||||
[open] > summary {
|
||||
color: var(--sl-color-text-accent);
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -165,7 +211,7 @@ function getIconPath(iconName: string | undefined): string | null {
|
||||
border-radius: 0.25rem;
|
||||
text-decoration: none;
|
||||
color: var(--sl-color-gray-2);
|
||||
padding: 0.3em var(--sl-sidebar-item-padding-inline);
|
||||
padding: 0.2em var(--sl-sidebar-item-padding-inline);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
@@ -187,9 +233,60 @@ function getIconPath(iconName: string | undefined): string | null {
|
||||
margin-inline-end: 0.25em;
|
||||
}
|
||||
|
||||
/*
|
||||
* Deep nesting adjustments (3+ levels).
|
||||
* Uses :is() to target any li that is at least 3 levels deep,
|
||||
* then tightens spacing progressively via custom properties.
|
||||
*/
|
||||
ul ul ul {
|
||||
--_nest-indent: 0.25rem;
|
||||
--_nest-link-block: 0.15em;
|
||||
}
|
||||
|
||||
ul ul ul ul {
|
||||
--_nest-indent: 0.2rem;
|
||||
}
|
||||
|
||||
ul ul ul li {
|
||||
margin-inline-start: var(--_nest-indent);
|
||||
padding-inline-start: var(--_nest-indent);
|
||||
}
|
||||
|
||||
ul ul ul a {
|
||||
padding-block: var(--_nest-link-block);
|
||||
}
|
||||
|
||||
/* Smooth expand/collapse — progressive enhancement (Chrome 129+, Firefox 131+, Safari 17.5+).
|
||||
Gated behind .sidebar-animate (added by JS after first paint) to prevent
|
||||
already-open groups from animating on page load. */
|
||||
details {
|
||||
interpolate-size: allow-keywords;
|
||||
}
|
||||
|
||||
:global(.sidebar-animate) details > ul {
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
transition:
|
||||
height 0.2s ease,
|
||||
opacity 0.15s ease;
|
||||
}
|
||||
|
||||
:global(.sidebar-animate) details[open] > ul {
|
||||
height: auto;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@starting-style {
|
||||
:global(.sidebar-animate) details[open] > ul {
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 50rem) {
|
||||
.top-level > li + li {
|
||||
margin-top: 0.5rem;
|
||||
margin-top: 0.375rem;
|
||||
}
|
||||
|
||||
.large {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
import { Icon } from '@astrojs/starlight/components'
|
||||
|
||||
interface Props {
|
||||
id: string
|
||||
icon?: string
|
||||
label: string
|
||||
active?: boolean
|
||||
}
|
||||
|
||||
const { id, icon, label, active = false } = Astro.props
|
||||
const panelId = `${id}-panel`
|
||||
---
|
||||
|
||||
<button
|
||||
role="tab"
|
||||
id={id}
|
||||
aria-selected={active ? 'true' : 'false'}
|
||||
aria-controls={panelId}
|
||||
data-active={active ? 'true' : undefined}
|
||||
tabindex={active ? 0 : -1}
|
||||
>
|
||||
{icon && <Icon name={icon} size="1rem" />}
|
||||
{label}
|
||||
</button>
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
interface Props {
|
||||
id: string
|
||||
tabId: string
|
||||
active?: boolean
|
||||
}
|
||||
|
||||
const { id, tabId, active = false } = Astro.props
|
||||
---
|
||||
|
||||
<div
|
||||
role="tabpanel"
|
||||
id={id}
|
||||
aria-labelledby={tabId}
|
||||
hidden={!active}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
@@ -0,0 +1,117 @@
|
||||
---
|
||||
// TabbedSidebar: A custom element that shows/hides tab panels in the sidebar.
|
||||
// All panels are rendered at build time; JS toggles visibility.
|
||||
---
|
||||
|
||||
<nx-tabbed-sidebar>
|
||||
<div class="nx-tab-bar-sticky">
|
||||
<div class="nx-tab-bar" role="tablist" aria-label="Sidebar sections" aria-orientation="vertical">
|
||||
<slot name="tabs" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="nx-tab-panels">
|
||||
<slot name="panels" />
|
||||
</div>
|
||||
</nx-tabbed-sidebar>
|
||||
|
||||
<script>
|
||||
class NxTabbedSidebar extends HTMLElement {
|
||||
private tabs: HTMLButtonElement[] = [];
|
||||
private panels: HTMLElement[] = [];
|
||||
|
||||
connectedCallback() {
|
||||
this.tabs = Array.from(this.querySelectorAll<HTMLButtonElement>('[role="tab"]'));
|
||||
this.panels = Array.from(this.querySelectorAll<HTMLElement>('[role="tabpanel"]'));
|
||||
|
||||
if (this.tabs.length === 0) return;
|
||||
|
||||
// Determine initial active tab
|
||||
const activeIndex = this.getInitialTabIndex();
|
||||
this.switchTab(activeIndex, false);
|
||||
|
||||
// Event listeners
|
||||
this.addEventListener('click', this.handleClick);
|
||||
this.addEventListener('keydown', this.handleKeydown);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.removeEventListener('click', this.handleClick);
|
||||
this.removeEventListener('keydown', this.handleKeydown);
|
||||
}
|
||||
|
||||
private getInitialTabIndex(): number {
|
||||
// 1. Tab with data-active="true" (set server-side from isCurrent check)
|
||||
const serverActive = this.tabs.findIndex(
|
||||
(tab) => tab.getAttribute('data-active') === 'true'
|
||||
);
|
||||
if (serverActive !== -1) return serverActive;
|
||||
|
||||
// 2. sessionStorage fallback
|
||||
const stored = sessionStorage.getItem('nx-sidebar-tab');
|
||||
if (stored !== null) {
|
||||
const storedIndex = this.tabs.findIndex((tab) => tab.id === stored);
|
||||
if (storedIndex !== -1) return storedIndex;
|
||||
}
|
||||
|
||||
// 3. Default to first tab
|
||||
return 0;
|
||||
}
|
||||
|
||||
private switchTab(index: number, store = true) {
|
||||
this.tabs.forEach((tab, i) => {
|
||||
const selected = i === index;
|
||||
tab.setAttribute('aria-selected', String(selected));
|
||||
tab.tabIndex = selected ? 0 : -1;
|
||||
});
|
||||
|
||||
this.panels.forEach((panel, i) => {
|
||||
panel.hidden = i !== index;
|
||||
});
|
||||
|
||||
if (store && this.tabs[index]) {
|
||||
sessionStorage.setItem('nx-sidebar-tab', this.tabs[index].id);
|
||||
}
|
||||
}
|
||||
|
||||
private handleClick = (e: Event) => {
|
||||
const tab = (e.target as HTMLElement).closest<HTMLButtonElement>('[role="tab"]');
|
||||
if (!tab) return;
|
||||
const index = this.tabs.indexOf(tab);
|
||||
if (index !== -1) {
|
||||
this.switchTab(index);
|
||||
tab.focus();
|
||||
}
|
||||
};
|
||||
|
||||
private handleKeydown = (e: KeyboardEvent) => {
|
||||
const tab = (e.target as HTMLElement).closest<HTMLButtonElement>('[role="tab"]');
|
||||
if (!tab) return;
|
||||
|
||||
const currentIndex = this.tabs.indexOf(tab);
|
||||
let nextIndex: number | null = null;
|
||||
|
||||
switch (e.key) {
|
||||
case 'ArrowDown':
|
||||
nextIndex = (currentIndex + 1) % this.tabs.length;
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
nextIndex = (currentIndex - 1 + this.tabs.length) % this.tabs.length;
|
||||
break;
|
||||
case 'Home':
|
||||
nextIndex = 0;
|
||||
break;
|
||||
case 'End':
|
||||
nextIndex = this.tabs.length - 1;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
this.switchTab(nextIndex);
|
||||
this.tabs[nextIndex].focus();
|
||||
};
|
||||
}
|
||||
|
||||
customElements.define('nx-tabbed-sidebar', NxTabbedSidebar);
|
||||
</script>
|
||||
@@ -0,0 +1,108 @@
|
||||
---
|
||||
import { getCollection } from 'astro:content';
|
||||
import Cards from './Cards.astro';
|
||||
import LinkCard from './LinkCard.astro';
|
||||
|
||||
export interface Props {
|
||||
group: string;
|
||||
}
|
||||
|
||||
const { group } = Astro.props;
|
||||
const groupSegments = group.split('/');
|
||||
|
||||
// Use the Starlight resolved sidebar from Astro locals
|
||||
const sidebar = Astro.locals.starlightRoute.sidebar;
|
||||
type SidebarEntry = (typeof sidebar)[number];
|
||||
|
||||
function findGroup(
|
||||
entries: SidebarEntry[],
|
||||
segments: string[],
|
||||
depth: number = 0
|
||||
): SidebarEntry | null {
|
||||
for (const entry of entries) {
|
||||
if (entry.type === 'group' && entry.label === segments[depth]) {
|
||||
if (depth === segments.length - 1) {
|
||||
return entry;
|
||||
}
|
||||
return findGroup(entry.entries, segments, depth + 1);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
interface LinkItem {
|
||||
label: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
function slugify(label: string): string {
|
||||
return label
|
||||
.replace(/\.NET/g, 'dotnet')
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9_]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
}
|
||||
|
||||
// Build base URL path from the group prop segments
|
||||
const baseSlug = groupSegments.map((s) => slugify(s)).join('/');
|
||||
|
||||
function collectTopLevelItems(entries: SidebarEntry[]): LinkItem[] {
|
||||
const items: LinkItem[] = [];
|
||||
for (const entry of entries) {
|
||||
if (entry.type === 'link') {
|
||||
items.push({ label: entry.label, href: entry.href });
|
||||
}
|
||||
if (entry.type === 'group') {
|
||||
const groupSlug = slugify(entry.label);
|
||||
items.push({
|
||||
label: entry.label,
|
||||
href: `/docs/${baseSlug}/${groupSlug}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
const matchedGroup = findGroup(sidebar, groupSegments);
|
||||
const linkItems =
|
||||
matchedGroup && matchedGroup.type === 'group'
|
||||
? collectTopLevelItems(matchedGroup.entries)
|
||||
: [];
|
||||
|
||||
// Look up each page in the docs collection for descriptions
|
||||
const allDocs = await getCollection('docs');
|
||||
|
||||
const processedPages = linkItems.map((item) => {
|
||||
const docPath = item.href.replace(/^\/docs\//, '');
|
||||
const doc = allDocs.find(
|
||||
(d) =>
|
||||
d.id === docPath ||
|
||||
d.id === `${docPath}.mdoc` ||
|
||||
d.id === `${docPath}/index` ||
|
||||
d.id === `${docPath}/index.mdoc`
|
||||
);
|
||||
|
||||
return {
|
||||
title: item.label,
|
||||
description: doc?.data?.description || '',
|
||||
href: item.href,
|
||||
};
|
||||
});
|
||||
---
|
||||
|
||||
{
|
||||
processedPages.length > 0 ? (
|
||||
<Cards>
|
||||
{processedPages.map((page) => (
|
||||
<LinkCard
|
||||
title={page.title}
|
||||
description={page.description}
|
||||
href={page.href}
|
||||
type="documentation"
|
||||
/>
|
||||
))}
|
||||
</Cards>
|
||||
) : (
|
||||
<p>No pages found in this section.</p>
|
||||
)
|
||||
}
|
||||
@@ -548,5 +548,10 @@
|
||||
"name": "@berenddeboer/nx-biome",
|
||||
"description": "A self-inferring Nx plugin for using biome to format and lint projects. Supports --batch",
|
||||
"url": "https://github.com/berenddeboer/nx-plugins/tree/main/packages/nx-biome"
|
||||
},
|
||||
{
|
||||
"name": "@berenddeboer/nx-knip",
|
||||
"description": "A self-inferring Nx plugin for using knip to find and fix unused dependencies, exports and files",
|
||||
"url": "https://github.com/berenddeboer/nx-plugins/tree/main/packages/nx-knip"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,284 +0,0 @@
|
||||
---
|
||||
title: Common Task Configurations
|
||||
description: Learn about standard task naming conventions in Nx projects, including build, serve, test, and lint tasks, for consistent project configuration.
|
||||
keywords: [build, serve, test, lint]
|
||||
sidebar:
|
||||
order: 1
|
||||
label: Common Tasks
|
||||
filter: 'type:Concepts'
|
||||
weight: 5.0
|
||||
---
|
||||
|
||||
The tasks that are [inferred by plugins](/docs/concepts/inferred-tasks) or that you define in your [project configuration](/docs/reference/project-configuration) can have any name that you want, but it is helpful for developers if you keep your task naming convention consistent across the projects in your repository. This way, if a developer moves from one project to another, they already know how to launch tasks for the new project. Here are some common task names that you can define for your projects.
|
||||
|
||||
## `build`
|
||||
|
||||
This task should produce the compiled output of this project. Typically, you'll want to have `build` tasks depend on the `build` tasks of project dependencies across the whole repository. You can set this default in the `nx.json` file like this:
|
||||
|
||||
```json title="nx.json"
|
||||
{
|
||||
"targetDefaults": {
|
||||
"build": {
|
||||
"dependsOn": ["^build"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The task might use the [@nx/vite](/docs/technologies/build-tools/vite/introduction), [@nx/webpack](/docs/technologies/build-tools/webpack/introduction) or [@nx/rspack](/docs/technologies/build-tools/rspack/introduction) plugins. Or you could have the task launch your own custom script.
|
||||
|
||||
{% tabs %}
|
||||
{% tabitem label="Vite" %}
|
||||
|
||||
Set up an [inferred](/docs/concepts/inferred-tasks) `build` task for every project that has a Vite configuration file with this configuration in `nx.json`:
|
||||
|
||||
```json title="nx.json"
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "@nx/vite/plugin",
|
||||
"options": {
|
||||
"buildTargetName": "build"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You can also [override the inferred task configuration](/docs/concepts/inferred-tasks#overriding-inferred-task-configuration) as needed.
|
||||
|
||||
{% /tabitem %}
|
||||
{% tabitem label="Webpack" %}
|
||||
|
||||
Set up an [inferred](/docs/concepts/inferred-tasks) `build` task for every project that has a Webpack configuration file with this configuration in `nx.json`:
|
||||
|
||||
```json title="nx.json"
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "@nx/webpack/plugin",
|
||||
"options": {
|
||||
"buildTargetName": "build"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You can also [override the inferred task configuration](/docs/concepts/inferred-tasks#overriding-inferred-task-configuration) as needed.
|
||||
|
||||
{% /tabitem %}
|
||||
{% tabitem label="rspack" %}
|
||||
|
||||
Set up an [inferred](/docs/concepts/inferred-tasks) `build` task for every project that has an rspack configuration file with this configuration in `nx.json`:
|
||||
|
||||
```json title="nx.json"
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "@nx/rspack/plugin",
|
||||
"options": {
|
||||
"buildTargetName": "build"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You can also [override the inferred task configuration](/docs/concepts/inferred-tasks#overriding-inferred-task-configuration) as needed.
|
||||
|
||||
{% /tabitem %}
|
||||
{% tabitem label="Custom Script" %}
|
||||
|
||||
You can define your own `build` task in your project configuration. Here is an example that uses `ts-node` to run a node script.
|
||||
|
||||
```json title="packages/my-project/package.json"
|
||||
{
|
||||
"scripts": {
|
||||
"build": "ts-node build-script.ts"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tabitem %}
|
||||
{% /tabs %}
|
||||
|
||||
## `serve`
|
||||
|
||||
This task should run your project in a developer preview mode. The task might use the [@nx/vite](/docs/technologies/build-tools/vite/introduction), [@nx/webpack](/docs/technologies/build-tools/webpack/introduction) or [@nx/rspack](/docs/technologies/build-tools/rspack/introduction) plugins. Or you could have the task launch your own custom script.
|
||||
|
||||
{% tabs %}
|
||||
{% tabitem label="Vite" %}
|
||||
|
||||
Set up an [inferred](/docs/concepts/inferred-tasks) `serve` task for every project that has a Vite configuration file with this configuration in `nx.json`:
|
||||
|
||||
```json title="nx.json"
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "@nx/vite/plugin",
|
||||
"options": {
|
||||
"serveTargetName": "serve"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You can also [override the inferred task configuration](/docs/concepts/inferred-tasks#overriding-inferred-task-configuration) as needed.
|
||||
|
||||
{% /tabitem %}
|
||||
{% tabitem label="Webpack" %}
|
||||
|
||||
Set up an [inferred](/docs/concepts/inferred-tasks) `serve` task for every project that has a Webpack configuration file with this configuration in `nx.json`:
|
||||
|
||||
```json title="nx.json"
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "@nx/webpack/plugin",
|
||||
"options": {
|
||||
"serveTargetName": "serve"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You can also [override the inferred task configuration](/docs/concepts/inferred-tasks#overriding-inferred-task-configuration) as needed.
|
||||
|
||||
{% /tabitem %}
|
||||
{% tabitem label="rspack" %}
|
||||
|
||||
Set up an [inferred](/docs/concepts/inferred-tasks) `serve` task for every project that has an rspack configuration file with this configuration in `nx.json`:
|
||||
|
||||
```json title="nx.json"
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "@nx/rspack/plugin",
|
||||
"options": {
|
||||
"serveTargetName": "serve"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You can also [override the inferred task configuration](/docs/concepts/inferred-tasks#overriding-inferred-task-configuration) as needed.
|
||||
|
||||
{% /tabitem %}
|
||||
{% tabitem label="Custom Script" %}
|
||||
|
||||
You can define your own `serve` task in your project configuration. Here is an example that uses `ts-node` to run the entry point of your project.
|
||||
|
||||
```json title="packages/my-project/package.json"
|
||||
{
|
||||
"scripts": {
|
||||
"serve": "ts-node main.ts"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tabitem %}
|
||||
{% /tabs %}
|
||||
|
||||
## `test`
|
||||
|
||||
This task typically runs unit tests for a project. The task might use the [@nx/vite](/docs/technologies/build-tools/vite/introduction) or [@nx/jest](/docs/technologies/test-tools/jest/introduction) plugins. Or you could have the task launch your own custom script.
|
||||
|
||||
{% tabs %}
|
||||
{% tabitem label="Vitest" %}
|
||||
|
||||
Set up an [inferred](/docs/concepts/inferred-tasks) `test` task for every project that has a Vitest configuration file with this configuration in `nx.json`:
|
||||
|
||||
```json title="nx.json"
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "@nx/vite/plugin",
|
||||
"options": {
|
||||
"testTargetName": "test"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You can also [override the inferred task configuration](/docs/concepts/inferred-tasks#overriding-inferred-task-configuration) as needed.
|
||||
|
||||
{% /tabitem %}
|
||||
{% tabitem label="Jest" %}
|
||||
|
||||
Set up an [inferred](/docs/concepts/inferred-tasks) `test` task for every project that has a Jest configuration file with this configuration in `nx.json`:
|
||||
|
||||
```json title="nx.json"
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "@nx/jest/plugin",
|
||||
"options": {
|
||||
"targetName": "test"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You can also [override the inferred task configuration](/docs/concepts/inferred-tasks#overriding-inferred-task-configuration) as needed.
|
||||
|
||||
{% /tabitem %}
|
||||
{% tabitem label="Custom Script" %}
|
||||
|
||||
You can define your own `test` task in your project configuration. Here is an example that runs the `ava` test tool.
|
||||
|
||||
```json title="packages/my-project/package.json"
|
||||
{
|
||||
"scripts": {
|
||||
"test": "ava"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tabitem %}
|
||||
{% /tabs %}
|
||||
|
||||
## `lint`
|
||||
|
||||
This task should run lint rules for a project. The task might use the [@nx/eslint](/docs/technologies/eslint/introduction) plugin or run your own custom script.
|
||||
|
||||
{% tabs %}
|
||||
{% tabitem label="ESLint" %}
|
||||
|
||||
Set up an [inferred](/docs/concepts/inferred-tasks) `lint` task for every project that has an ESLint configuration file with this configuration in `nx.json`:
|
||||
|
||||
```json title="nx.json"
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "@nx/eslint/plugin",
|
||||
"options": {
|
||||
"targetName": "lint"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You can also [override the inferred task configuration](/docs/concepts/inferred-tasks#overriding-inferred-task-configuration) as needed.
|
||||
|
||||
{% /tabitem %}
|
||||
{% tabitem label="Custom Script" %}
|
||||
|
||||
You can define your own `lint` task in your project configuration. Here is an example that runs the `sonarts` lint tool.
|
||||
|
||||
```json title="packages/my-project/package.json"
|
||||
{
|
||||
"scripts": {
|
||||
"lint": "sonarts"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tabitem %}
|
||||
{% /tabs %}
|
||||
@@ -24,7 +24,7 @@ By default, the computation hash for something like `nx test remixapp` includes:
|
||||
|
||||
After Nx computes the hash for a task, it then checks if it ran this exact computation before. First, it checks locally, and then if it is missing, and if a remote cache is configured, it checks remotely. If a matching computation is found, Nx retrieves and replays it. This includes restoring files.
|
||||
|
||||
Nx places the right files in the right folders and prints the terminal output. From the user's point of view, the command ran the same, just a lot faster.
|
||||
Nx places the right files in the right folders and prints the terminal output. From the user's point of view, the command ran the same, only a lot faster.
|
||||
|
||||

|
||||
|
||||
@@ -32,7 +32,7 @@ If Nx doesn't find a corresponding computation hash, Nx runs the task, and after
|
||||
|
||||
## Optimizations
|
||||
|
||||
Although conceptually this is fairly straightforward, Nx optimizes the experience for you. For instance, Nx:
|
||||
Nx optimizes the caching experience in several ways. For instance, Nx:
|
||||
|
||||
- Captures stdout and stderr to make sure the replayed output looks the same, including on Windows.
|
||||
- Minimizes the IO by remembering what files are replayed where.
|
||||
@@ -46,7 +46,7 @@ As your workspace grows, the task graph looks more like this:
|
||||
All of these optimizations are crucial for making Nx usable for any non-trivial workspace. Only the minimum amount of
|
||||
work happens. The rest is either left as is or restored from the cache.
|
||||
|
||||
## Fine-tuning Nx's Cache
|
||||
## Fine-tuning the Nx cache
|
||||
|
||||
Each cacheable task defines a set of inputs and outputs. Inputs are factors Nx considers when calculating the computation hash.
|
||||
Outputs are files that will be cached and restored when the computation hash matches.
|
||||
|
||||
@@ -6,9 +6,7 @@ sidebar:
|
||||
filter: 'type:Concepts'
|
||||
---
|
||||
|
||||
Nx is a VSCode of build tools, with a powerful core, driven by metadata, and extensible through [plugins](/docs/concepts/nx-plugins). Nx works with a
|
||||
few concepts to drive your monorepo efficiently, and effectively. This guide covers the mental model around how Nx works
|
||||
with project graphs, task graphs, affected commands, computation hashing and caching.
|
||||
Nx is a VSCode of build tools, with a powerful core, driven by metadata, and extensible through [plugins](/docs/concepts/nx-plugins). Nx works with a few core concepts to drive your monorepo efficiently: project graphs, task graphs, affected commands, computation hashing, and caching.
|
||||
|
||||
## The project graph
|
||||
|
||||
@@ -267,7 +265,7 @@ With this, running the same test command creates the following task graph:
|
||||
|
||||
This often makes more sense for builds, where to build `app1`, you want to build `lib` first. You can also define
|
||||
similar
|
||||
relationships between targets of the same project, including a test target that depends on the build.
|
||||
relationships between targets of the same project, including a test target that depends on the build. Learn more about configuring task pipelines in [Task Pipeline Configuration](/docs/concepts/task-pipeline-configuration).
|
||||
|
||||
A task graph can contain different targets, and those can run in parallel. For instance, as Nx is building `app2`, it
|
||||
can be testing `app1` at the same time.
|
||||
@@ -287,7 +285,7 @@ and `lib:test`.
|
||||
|
||||
When you run `nx run-many -t test`, you are telling Nx to do this for all the projects.
|
||||
|
||||
As your workspace grows, retesting all projects becomes too slow. To address this Nx implements code change analysis to
|
||||
As your workspace grows, retesting all projects becomes too slow. To address this Nx implements code change analysis via the [`affected` command](/docs/features/ci-features/affected) to
|
||||
get the min set of projects that need to be retested. How does it work?
|
||||
|
||||
When you run `nx affected -t test`, Nx looks at the files you changed in your PR, it will look at the nature of
|
||||
@@ -304,104 +302,19 @@ that `app2` cannot be affected by it, so it only retests `app1`.
|
||||
|
||||
## Computation hashing and caching
|
||||
|
||||
Nx runs the tasks in the task graph in the right order. Before running the task, Nx computes its computation hash. As
|
||||
long as the computation hash is the same, the output of running the task is the same.
|
||||
|
||||
How does Nx do it?
|
||||
|
||||
By default, the computation hash for say `nx test app1` includes:
|
||||
|
||||
- All the source files of `app1` and `lib`
|
||||
- Relevant global configuration
|
||||
- Versions of external dependencies
|
||||
- [Runtime values provisioned by the user](/docs/reference/inputs#runtime-inputs)
|
||||
- CLI Command flags
|
||||
Before running a task, Nx computes a hash based on source files, configuration, dependencies, and other inputs. If the hash matches a previous run, the cached result is replayed — including terminal output and file artifacts. If not, Nx runs the task and stores the result for next time.
|
||||
|
||||

|
||||
|
||||
This behavior is customizable. For instance, lint checks may only depend on the source code of the project and global
|
||||
configs. Builds can depend on the `.d.ts` files of the compiled libs instead of their source.
|
||||
|
||||
After Nx computes the hash for a task, it then checks if it ran this exact computation before. First, it checks locally,
|
||||
and then if it is missing, and if a remote cache is configured, it checks remotely.
|
||||
|
||||
If Nx finds the computation, Nx retrieves it and replays it. Nx places the right files in the right folders and prints
|
||||
the terminal output. So from the user's point of view, the command ran the same, just a lot faster.
|
||||
Nx checks the local cache first, then the [remote cache](/docs/features/ci-features/remote-cache) if configured. From the user's point of view, the command ran the same, only a lot faster.
|
||||
|
||||

|
||||
|
||||
If Nx doesn't find this computation, Nx runs the task, and after it completes, it takes the outputs and the terminal
|
||||
output and stores it locally (and if configured remotely). All of this happens transparently, so you don't have to worry
|
||||
about it.
|
||||
|
||||
Although conceptually this is fairly straightforward, Nx optimizes this to make this experience good for you. For
|
||||
instance, Nx:
|
||||
|
||||
- Captures stdout and stderr to make sure the replayed output looks the same, including on Windows.
|
||||
- Minimizes the IO by remembering what files are replayed where.
|
||||
- Only shows relevant output when processing a large task graph.
|
||||
- Provides affordances for troubleshooting cache misses. And many other optimizations.
|
||||
|
||||
As your workspace grows, the task graph looks more like this:
|
||||
|
||||
{% graph height="200px" type="task"%}
|
||||
|
||||
```json
|
||||
{
|
||||
"projects": [
|
||||
{
|
||||
"name": "lib",
|
||||
"type": "lib",
|
||||
"data": {
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"test": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"taskIds": ["lib:test"],
|
||||
"taskGraph": {
|
||||
"roots": ["lib:test"],
|
||||
"tasks": {
|
||||
"lib:test": {
|
||||
"id": "lib:test",
|
||||
"target": {
|
||||
"project": "lib",
|
||||
"target": "test"
|
||||
},
|
||||
"projectRoot": "libs/lib",
|
||||
"overrides": {}
|
||||
}
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /graph %}
|
||||
|
||||
All of these optimizations are crucial for making Nx usable for any non-trivial workspace. Only the minimum amount of
|
||||
work happens. The rest is either left as is or restored from the cache.
|
||||
See [How Caching Works](/docs/concepts/how-caching-works) for the complete list of hash inputs, cache configuration options, and optimization details.
|
||||
|
||||
## Distributed task execution
|
||||
|
||||
Nx supports running commands across multiple machines. You can either set it up by hand or use Nx Cloud. [Read the comparison of the two approaches.](https://nx.dev/blog/distributing-ci-binning-and-distributed-task-execution)
|
||||
|
||||
When using the distributed task execution, Nx is able to run any task graph on many agents instead of locally.
|
||||
|
||||
For instance, `nx affected --build` won't run the build locally (which can take hours for large workspaces). Instead,
|
||||
it will send the Task Graph to Nx Cloud. Nx Cloud Agents will then pick up the tasks they can run and execute them.
|
||||
|
||||
Note that this happens transparently. If an agent builds `app1`, it will fetch the outputs for `lib` if it doesn't have
|
||||
them
|
||||
already.
|
||||
|
||||
As agents complete tasks, the main job where you invoked `nx affected --build` will start receiving created files and
|
||||
terminal outputs.
|
||||
|
||||
After `nx affected --build` completes, the machine will have the build files and all the terminal outputs as if it ran
|
||||
it locally.
|
||||
For large workspaces, even with caching, running all tasks on a single machine can be slow. [Nx Agents](/docs/features/ci-features/distribute-task-execution) can distribute the task graph across multiple machines, running tasks in parallel while using [remote caching](/docs/features/ci-features/remote-cache) to share artifacts between agents. From your CI's perspective, the results appear as if everything ran on a single machine.
|
||||
|
||||

|
||||
|
||||
@@ -410,5 +323,5 @@ it locally.
|
||||
- Nx is able to analyze your source code to create a Project Graph.
|
||||
- Nx can use the project graph and information about projects' targets to create a Task Graph.
|
||||
- Nx is able to perform code-change analysis to create the smallest task graph for your PR.
|
||||
- Nx supports computation caching to never execute the same computation twice. This computation cache is pluggable and
|
||||
- Nx supports [computation caching](/docs/features/cache-task-results) to never execute the same computation twice. This computation cache is pluggable and
|
||||
can be distributed.
|
||||
|
||||
@@ -49,7 +49,7 @@ To see information about the running Nx Daemon (such as its background process I
|
||||
## Customizing the socket location
|
||||
|
||||
The Nx Daemon uses a unix socket to communicate between the daemon and the Nx processes. By default this socket gets placed in a temp directory. If you are using Nx in a docker-compose environment, however, you may want to run the daemon manually
|
||||
and control its location to enable sharing the daemon among your docker containers. To do so, simply set the NX_DAEMON_SOCKET_DIR environment variable to a shared directory.
|
||||
and control its location to enable sharing the daemon among your docker containers. To do so, set the `NX_DAEMON_SOCKET_DIR` environment variable to a shared directory.
|
||||
|
||||
## Daemon Behavior in Containers
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ For example, plugins can accomplish the following:
|
||||
- [Configure Nx cache settings](/docs/concepts/inferred-tasks) for a tool. The [`@nx/webpack`](/docs/technologies/build-tools/webpack/introduction) plugin can automatically configure the [inputs](/docs/guides/tasks--caching/configure-inputs) and [outputs](/docs/guides/tasks--caching/configure-outputs) for a `build` task based on the settings in the `webpack.config.js` file it uses.
|
||||
- [Update tooling configuration](/docs/features/automate-updating-dependencies) when upgrading the tool version. When Storybook 7 introduced a [new format](https://storybook.js.org/blog/storybook-csf3-is-here) for their configuration files, anyone using the [`@nx/storybook`](/docs/technologies/test-tools/storybook/introduction) plugin could automatically apply those changes to their repository when upgrading.
|
||||
- [Set up a tool](/docs/features/generate-code) for the first time. With the [`@nx/playwright`](/docs/technologies/test-tools/playwright/introduction) plugin installed, you can use the `@nx/playwright:configuration` code generator to set up Playwright tests in an existing project.
|
||||
- [Run a tool in an advanced way](/docs/concepts/executors-and-configurations). The [`@nx/js`](/docs/technologies/typescript/introduction) plugin's [`@nx/js:tsc` executor](/docs/technologies/typescript/executors#tsc) combines Nx's understanding of your repository with Typescript's native batch mode feature to make your builds [even more performant](/docs/technologies/typescript/guides/enable-tsc-batch-mode).
|
||||
- [Run a tool in an advanced way](/docs/concepts/executors-and-configurations). The [`@nx/js`](/docs/technologies/typescript/introduction) plugin's [`@nx/js:tsc` executor](/docs/technologies/typescript/executors#tsc) combines the Nx understanding of your repository with Typescript's native batch mode feature to make your builds [even more performant](/docs/technologies/typescript/guides/enable-tsc-batch-mode).
|
||||
|
||||
## Plugin Features
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
title: Synthetic Monorepos
|
||||
description: Learn how synthetic monorepos connect separate repositories into a unified dependency graph, giving you monorepo intelligence without moving code.
|
||||
---
|
||||
|
||||
Most organizations don't have a single giant monorepo. They have a handful of monorepos per team or domain, plus dozens of standalone repos. Consolidating everything into one repository is not just a technical challenge. The organizational side (bringing teams along, changing workflows, ensuring adoption) is often harder than the code migration itself.
|
||||
|
||||
Synthetic monorepos let you get monorepo benefits without that consolidation.
|
||||
|
||||
## What is a synthetic monorepo?
|
||||
|
||||
A synthetic monorepo connects separate repositories into a unified dependency graph without moving any code. Which repo depends on which, what a change affects downstream, how projects relate across teams: all of that becomes visible automatically.
|
||||
|
||||

|
||||
|
||||
Unlike a traditional monorepo where all code lives in one repository, a synthetic monorepo leaves each repository where it is. Instead, it builds a cross-repo graph that tooling can reason about, just as if the code were in one place.
|
||||
|
||||
## What synthetic monorepos enable
|
||||
|
||||
A synthetic monorepo addresses several downsides of a polyrepo setup:
|
||||
|
||||
**Visibility** — An automatic cross-repo dependency graph shows which repo depends on which and what a change affects downstream. Always up to date, discovered from actual code — not a manually maintained spreadsheet or catalog. Nx implements this through the [Workspace Graph](/docs/enterprise/polygraph).
|
||||
|
||||
**Coordination** — Cross-repo changes no longer require manually sequencing PRs, managing compatibility, and coordinating release order. Tooling on top of the graph enables impact analysis, coordinated changes, and conformance checking across repo boundaries.
|
||||
|
||||
**Governance** — Organizational standards apply across every connected repo through [conformance rules](/docs/enterprise/conformance). Scheduled [custom workflows](/docs/enterprise/custom-workflows) check repos continuously — even ones nobody has touched in months. Detection and enforcement happen automatically, not through tickets and follow-ups.
|
||||
|
||||
**CI intelligence** — [Affected detection](/docs/concepts/mental-model#affected-commands), [remote caching](/docs/concepts/how-caching-works), and [distributed task execution](/docs/concepts/ci-concepts/parallelization-distribution) work across the full graph, not just within a single repo.
|
||||
|
||||
**AI agents** — AI coding agents are [dramatically less effective in polyrepos](https://youtu.be/alIto5fqrfk) — they can only see one repo at a time, so cross-repo features require you to manually shuttle context between sessions. A synthetic monorepo gives agents cross-repo visibility, enabling coordinated changes, parallel execution, and automatic PR creation across boundaries. [Self-healing CI](/docs/features/ci-features/self-healing-ci) catches failures automatically.
|
||||
|
||||
## When to use a synthetic monorepo vs. a real monorepo
|
||||
|
||||
A real monorepo is the best option when you can consolidate. It gives you atomic commits, a single toolchain, and the simplest mental model.
|
||||
|
||||
A synthetic monorepo is the better starting point when:
|
||||
|
||||
- **Consolidation isn't feasible yet:** team autonomy concerns, divergent CI setups, or hundreds of repos make migration impractical.
|
||||
- **You need cross-repo visibility now:** you can't wait months for a migration to see how projects relate across teams.
|
||||
- **Teams need to stay autonomous:** each team keeps their repo, workflow, and release cadence while still participating in a unified graph.
|
||||
|
||||
The two aren't mutually exclusive. Start synthetic for org-wide visibility, then consolidate tightly coupled teams into real monorepos where it makes sense.
|
||||
|
||||
## Synthetic monorepos with Nx Polygraph
|
||||
|
||||
Nx implements synthetic monorepos through [Nx Polygraph](/docs/enterprise/polygraph). Polygraph connects existing repositories into a unified, intelligent graph that powers the visibility, coordination, and CI features described above. It works with any repo, even those that don't use Nx, and requires zero changes to target repos.
|
||||
|
||||
Learn more about [getting started with Nx Polygraph](/docs/enterprise/polygraph).
|
||||
@@ -65,7 +65,7 @@ This becomes even more evident when you run tasks in parallel. You cannot just n
|
||||
|
||||

|
||||
|
||||
Nx allows you to define task dependencies in the form of "rules", which are then followed when running tasks. There's a [detailed recipe](/docs/guides/tasks--caching/defining-task-pipeline) but here's the high-level overview:
|
||||
Define task dependencies in the form of "rules", which are then followed when running tasks. There's a [detailed recipe](/docs/guides/tasks--caching/defining-task-pipeline) but here's the high-level overview:
|
||||
|
||||
```jsonc title="nx.json"
|
||||
{
|
||||
|
||||
@@ -55,7 +55,7 @@ The configuration for package manager workspaces varies based on which package m
|
||||
|
||||
Defining the `workspaces` property in the root `package.json` file lets npm know to look for other `package.json` files in the specified folders. With this configuration in place, all the dependencies for the individual projects will be installed in the root `node_modules` folder when `npm install` is run in the root folder. Also, the projects themselves will be linked in the root `node_modules` folder to be accessed as if they were npm packages.
|
||||
|
||||
If you want to reference a local library project with its own `build` task, you should include the library in the `devDependencies` of the application's `package.json` with `*` specified as the library's version. `*` tells npm to use whatever version of the project is available.
|
||||
If you want to reference a local library project with its own `build` task, you should include the library in the `devDependencies` of the application/library's `package.json` with `*` specified as the library's version. `*` tells npm to use whatever version of the project is available.
|
||||
|
||||
```json title="/apps/my-app/package.json"
|
||||
{
|
||||
@@ -76,7 +76,7 @@ If you want to reference a local library project with its own `build` task, you
|
||||
|
||||
Defining the `workspaces` property in the root `package.json` file lets yarn know to look for other `package.json` files in the specified folders. With this configuration in place, all the dependencies for the individual projects will be installed in the root `node_modules` folder when `yarn` is run in the root folder. Also, the projects themselves will be linked in the root `node_modules` folder to be accessed as if they were npm packages.
|
||||
|
||||
If you want to reference a local library project with its own `build` task, you should include the library in the `devDependencies` of the application's `package.json` with `workspace:*` specified as the library's version. [`workspace:*` tells yarn that the project is in the same repository](https://yarnpkg.com/features/workspaces) and not an npm package. You want to specify local projects as `devDependencies` instead of `dependencies` so that the library is not included twice in the production bundle of the application.
|
||||
If you want to reference a local library project with its own `build` task, you should include the library in the `devDependencies` of the application/library's `package.json` with `workspace:*` specified as the library's version. [`workspace:*` tells yarn that the project is in the same repository](https://yarnpkg.com/features/workspaces) and not an npm package. You want to specify local projects as `devDependencies` instead of `dependencies` so that the library is not included twice in the production bundle of the application.
|
||||
|
||||
```json title="/apps/my-app/package.json"
|
||||
{
|
||||
@@ -97,7 +97,7 @@ If you want to reference a local library project with its own `build` task, you
|
||||
|
||||
Defining the `workspaces` property in the root `package.json` file lets bun know to look for other `package.json` files in the specified folders. With this configuration in place, all the dependencies for the individual projects will be installed in the root `node_modules` folder when `bun install` is run in the root folder. Also, the projects themselves will be linked in the root `node_modules` folder to be accessed as if they were npm packages.
|
||||
|
||||
If you want to reference a local library project with its own `build` task, you should include the library in the `devDependencies` of the application's `package.json` with `workspace:*` specified as the library's version. [`workspace:*` tells bun that the project is in the same repository](https://bun.sh/docs/install/workspaces) and not an npm package. You want to specify local projects as `devDependencies` instead of `dependencies` so that the library is not included twice in the production bundle of the application.
|
||||
If you want to reference a local library project with its own `build` task, you should include the library in the `devDependencies` of the application/library's `package.json` with `workspace:*` specified as the library's version. [`workspace:*` tells bun that the project is in the same repository](https://bun.sh/docs/install/workspaces) and not an npm package. You want to specify local projects as `devDependencies` instead of `dependencies` so that the library is not included twice in the production bundle of the application.
|
||||
|
||||
```json title="/apps/my-app/package.json"
|
||||
{
|
||||
@@ -187,7 +187,7 @@ Each project's `tsconfig.json` file should extend the `tsconfig.base.json` file
|
||||
}
|
||||
```
|
||||
|
||||
Each project's `tsconfig.lib.json` file extends the project's `tsconfig.json` file and adds `references` to the `tsconfig.lib.json` files of project dependencies.
|
||||
Each project's `tsconfig.lib.json` file extends the `tsconfig.base.json` file and adds `references` to the `tsconfig.lib.json` files of project dependencies.
|
||||
|
||||
```jsonc title="packages/cart/tsconfig.lib.json"
|
||||
{
|
||||
|
||||
@@ -68,18 +68,80 @@ The following permissions are required for Nx Cloud to work:
|
||||
|
||||
Repository permissions:
|
||||
|
||||
- `Administration: Read & Write`
|
||||
- `Checks: Read & Write`
|
||||
- `Contents: Read & Write`
|
||||
- `Pull requests: Read & Write`
|
||||
- `Checks: Read Only`
|
||||
- `Commit Statuses: Read & Write`
|
||||
- `Commit Statuses: Read`
|
||||
- `Issues: Read & Write`
|
||||
- `Metadata: Read Only`
|
||||
- `Metadata: Read`
|
||||
- `Pull requests: Read & Write`
|
||||
- `Workflows: Read & Write`
|
||||
|
||||
Organization permissions:
|
||||
|
||||
- `Administration: Read Only`
|
||||
- `Members: Read Only`
|
||||
|
||||
### Administration (write)
|
||||
|
||||
**Used for:** Creating new repositories with a pre-configured Nx workspace during initial onboarding.
|
||||
|
||||
**When it's used:** Only when you explicitly choose to create a new workspace through Nx Cloud's setup flow. [Single tenant instances](/docs/enterprise/single-tenant/overview) can safely forego this scope and will only lose the ability to create new workspaces through the app.
|
||||
|
||||
### Checks (write)
|
||||
|
||||
**Used for:** Updating CI run statuses so you can see the progress and results of your Nx Cloud pipeline executions directly in GitHub. Also used for Self-Healing CI status check runs in PRs.
|
||||
|
||||
**When it's used:** Automatically during CI runs to provide real-time status updates.
|
||||
|
||||
### Contents (read & write)
|
||||
|
||||
**Used for:**
|
||||
|
||||
- **Read:** Detecting your workspace's current Nx version to ensure compatibility. Reading files for Self-Healing CI.
|
||||
- **Write:** Adding Nx Cloud configuration (`nxCloudId` or access token) to your repository during setup. Creating commits and pushing fixes for Self-Healing CI.
|
||||
|
||||
**When it's used:** During initial setup and configuration, and regularly if Self-Healing CI is enabled.
|
||||
|
||||
### Commit statuses (read)
|
||||
|
||||
**Used for:** Reading commit status information to coordinate with other CI tools and provide accurate pipeline context.
|
||||
|
||||
**When it's used:** During CI pipeline executions to gather context about your commits.
|
||||
|
||||
### Issues (read & write)
|
||||
|
||||
**Used for:** PR comments (GitHub uses the Issues API for PR comments — see "Pull requests" below for more detail).
|
||||
|
||||
**When it's used:** During CI runs and when posting status comments.
|
||||
|
||||
### Metadata (read)
|
||||
|
||||
**Used for:** Accessing basic repository information (name, description, visibility). This is a required baseline permission for most GitHub App functionality.
|
||||
|
||||
### Pull requests (read & write)
|
||||
|
||||
**Used for:**
|
||||
|
||||
- **Read:** Gathering branch information, SHAs, and metadata necessary for CI pipeline execution and distributed task coordination.
|
||||
- **Write:** Posting comments on PRs with CI pipeline status, command results, and Self-Healing CI fixes. Creating PRs during initial Nx Cloud setup. Creating demo PRs for optional features like Self-Healing CI (only when you opt in).
|
||||
|
||||
**When it's used:** Read operations occur during CI runs. Write operations occur during setup and when posting status comments.
|
||||
|
||||
### Workflows (write)
|
||||
|
||||
**Used for:** Automatically configuring GitHub Actions workflow files when you opt in to features like Self-Healing CI and distributed task execution.
|
||||
|
||||
**When it's used:** Only when you explicitly enable these features through the Nx Cloud interface.
|
||||
|
||||
## Your Data and Security
|
||||
|
||||
Most information accessed through these permissions is used transiently during operations and is not stored. Limited version control metadata (such as branch names, SHAs, and commit information) may be stored as part of your CI pipeline execution records for analytics and debugging purposes.
|
||||
|
||||
[Nx Cloud is SOC2 Type II certified](https://security.nx.app). We implement industry-standard security practices including encryption at rest and in transit, access logging, and regular security audits.
|
||||
|
||||
You can revoke access to the Nx Cloud GitHub app at any time through your GitHub settings. Write operations (creating repos, posting comments, modifying workflows) only occur when explicitly triggered by your actions or when you opt in to specific features.
|
||||
|
||||
## Connect Your Nx Cloud Installation
|
||||
|
||||
Provide the following values to your developer productivity engineer so they can help connect Nx Cloud to your custom GitHub app:
|
||||
|
||||
@@ -4,7 +4,7 @@ description: Create and publish custom Nx Conformance rules to your Nx Cloud org
|
||||
filter: 'type:Guides'
|
||||
---
|
||||
|
||||
[Nx Cloud Enterprise](https://nx.dev/enterprise) allows you to publish your organization's [Nx Conformance](/docs/enterprise/conformance) rules to your Nx Cloud Organization, and consume them in any of your other Nx Workspaces without having to deal with the complexity and friction of dealing with a private NPM registry or similar. Authentication is handled automatically through your Nx Cloud connection and rules are downloaded and applied based on your preferences configured in the Nx Cloud UI.
|
||||
[Nx Cloud Enterprise](https://nx.dev/enterprise) lets you publish custom [Nx Conformance](/docs/enterprise/conformance) rules to your Nx Cloud Organization and consume them across workspaces — no private NPM registry needed. See [Configure Conformance Rules in Nx Cloud](/docs/enterprise/configure-conformance-rules-in-nx-cloud) for how to manage published rules in the UI.
|
||||
|
||||
Let's create a custom rule which we can then publish to Nx Cloud. We will first create a new library project to contain our rule (and any others we might create in the future):
|
||||
|
||||
|
||||
@@ -128,6 +128,184 @@ Nx uses the paths from `tsconfig.base.json` when running plugins locally, but us
|
||||
|
||||

|
||||
|
||||
## Generator Schema Properties
|
||||
|
||||
Beyond the standard [JSON Schema](https://json-schema.org/) properties like `type`, `description`, `enum`, and `default`, Nx recognizes several custom properties in your `schema.json` that control CLI prompting behavior and how [Nx Console](/docs/getting-started/editor-setup) renders the generator form.
|
||||
|
||||
### `$default`
|
||||
|
||||
Provides a dynamic default value from a runtime source. Used to map positional CLI arguments and other context to schema properties.
|
||||
|
||||
```json
|
||||
// schema.json
|
||||
{
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Source | Description |
|
||||
| ----------------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| `{ "$source": "argv", "index": 0 }` | Uses the positional CLI argument at the given index |
|
||||
| `{ "$source": "projectName" }` | Uses the current project name. Also triggers project autocomplete in the CLI and Nx Console |
|
||||
| `{ "$source": "workingDirectory" }` | Uses the current working directory relative to the workspace root |
|
||||
| `{ "$source": "unparsed" }` | Collects any extra arguments not matched by other schema properties |
|
||||
|
||||
### `x-prompt`
|
||||
|
||||
Defines an interactive prompt shown when the option is not provided on the command line. Can be a simple string or a structured object for more control.
|
||||
|
||||
```json
|
||||
// schema.json
|
||||
{
|
||||
"properties": {
|
||||
"style": {
|
||||
"type": "string",
|
||||
"description": "The file extension to be used for style files.",
|
||||
"x-prompt": {
|
||||
"message": "Which stylesheet format would you like to use?",
|
||||
"type": "list",
|
||||
"items": [
|
||||
{ "value": "css", "label": "CSS" },
|
||||
{ "value": "scss", "label": "SASS (.scss)" },
|
||||
{ "value": "less", "label": "LESS" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Short form:** `"x-prompt": "What name would you like to use?"` — displays a simple text prompt.
|
||||
|
||||
**Long form object properties:**
|
||||
|
||||
| Property | Type | Description |
|
||||
| ------------- | ------------------------------------------------ | ----------------------------------------------------- |
|
||||
| `message` | `string` | The prompt text displayed to the user |
|
||||
| `type` | `string` | Prompt type: `"input"`, `"list"`, or `"confirmation"` |
|
||||
| `multiselect` | `boolean` | Allow selecting multiple items (for `"list"` type) |
|
||||
| `items` | `(string \| { label: string, value: string })[]` | Choices for `"list"` type prompts |
|
||||
|
||||
In Nx Console, the `message` is shown as a tooltip on the field and `items` labels are shown as option descriptions.
|
||||
|
||||
### `x-priority`
|
||||
|
||||
Controls the visibility and ordering of an option in the Nx Console Generate form.
|
||||
|
||||
```json
|
||||
// schema.json
|
||||
{
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"skipFormat": {
|
||||
"type": "boolean",
|
||||
"x-priority": "internal"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Value | Effect |
|
||||
| ------------- | ------------------------------------------------------------- |
|
||||
| `"important"` | Field appears near the top of the form, after required fields |
|
||||
| `"internal"` | Field is hidden from the form by default |
|
||||
|
||||
Options in the Nx Console form are sorted: **required** > **important** > **regular** > **deprecated** > **internal**.
|
||||
|
||||
### `x-deprecated`
|
||||
|
||||
Marks an option as deprecated. Deprecated options are sorted to the bottom of the form in Nx Console and display a warning.
|
||||
|
||||
```json
|
||||
// schema.json
|
||||
{
|
||||
"properties": {
|
||||
"oldOption": {
|
||||
"type": "string",
|
||||
"x-deprecated": "Use 'newOption' instead."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The value can be `true` (boolean) or a string with the deprecation reason/migration guidance.
|
||||
|
||||
### `x-dropdown`
|
||||
|
||||
Tells both the CLI and Nx Console to present a dropdown populated with workspace data.
|
||||
|
||||
```json
|
||||
// schema.json
|
||||
{
|
||||
"properties": {
|
||||
"projectName": {
|
||||
"type": "string",
|
||||
"x-dropdown": "projects"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Currently only `"projects"` is supported, which shows all projects in the workspace.
|
||||
|
||||
{% aside type="note" title="Automatic project autocomplete" %}
|
||||
The CLI and Nx Console also automatically provide project autocomplete for any property named `project` or `projectName`, or that has `$default` set to `{ "$source": "projectName" }` — even without `x-dropdown`.
|
||||
{% /aside %}
|
||||
|
||||
### `x-hint`
|
||||
|
||||
Displays a hint popover next to the field label in the Nx Console Generate form. Use this for brief contextual guidance that doesn't belong in the main `description`.
|
||||
|
||||
```json
|
||||
// schema.json
|
||||
{
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"x-hint": "You can provide a nested path like my-dir/my-lib"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `x-completion-type` and `x-completion-glob`
|
||||
|
||||
These properties are used by Nx Console's language server to provide autocomplete suggestions when editing configuration files like `project.json` or `nx.json`.
|
||||
|
||||
```json
|
||||
// schema.json
|
||||
{
|
||||
"properties": {
|
||||
"tsConfig": {
|
||||
"type": "string",
|
||||
"x-completion-type": "file",
|
||||
"x-completion-glob": "tsconfig*.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| `x-completion-type` value | Description |
|
||||
| ------------------------- | ------------------------------------------------------------------------- |
|
||||
| `"file"` | Autocomplete with file paths (optionally filtered by `x-completion-glob`) |
|
||||
| `"directory"` | Autocomplete with directory paths |
|
||||
| `"projects"` | Autocomplete with workspace project names |
|
||||
| `"targets"` | Autocomplete with available target names |
|
||||
| `"targetsWithDeps"` | Autocomplete targets, including `^target` syntax for dependencies |
|
||||
| `"tags"` | Autocomplete with project tags |
|
||||
| `"projectTarget"` | Autocomplete with `project:target` format |
|
||||
|
||||
## Generator Utilities
|
||||
|
||||
The [`@nx/devkit` package](/docs/reference/devkit) provides many utility functions that can be used in generators to help with modifying files, reading and updating configuration files, and working with an Abstract Syntax Tree (AST).
|
||||
|
||||
@@ -11,12 +11,10 @@ src="https://youtu.be/NF1__N_snog"
|
||||
title="Remote Caching with Nx Replay"
|
||||
/%}
|
||||
|
||||
Repeatedly rebuilding and retesting the same code is costly — not just in terms of wasted resources, but also in terms of developer time. To solve this, Nx includes a sophisticated computation caching system that ensures **code is never rebuilt twice**, saving you both time and resources.
|
||||
Nx [caches task results locally](/docs/features/cache-task-results) to avoid rebuilding the same code twice. Remote caching extends this by **sharing the cache across your team and CI**.
|
||||
|
||||

|
||||
|
||||
By default, Nx [caches task computations locally](/docs/features/cache-task-results), but the biggest benefit comes from **sharing this cache across your team and in CI**.
|
||||
|
||||
- **Zero config** and **secure** by default
|
||||
- Drastically **speeds up task execution times** during local development, and more critically in CI
|
||||
- **Saves money on CI/CD costs** by reducing the number of tasks that need to be executed (we observed 30-70% faster CI & half the cost)
|
||||
|
||||
@@ -16,7 +16,7 @@ Nx Cloud Self-Healing CI is an **AI-powered system that automatically detects, a
|
||||
|
||||
- **Improves Time to Green (TTG):** Automatically proposes fixes when tasks fail, significantly reducing the time to get your PR merge-ready. No more babysitting PRs.
|
||||
- **Keeps You in the Flow:** Get notified about failed PRs and proposed fixes via PR/MR comments or directly in your editor with Nx Console (VS Code, Cursor, or WebStorm). Review, approve, and keep working while AI handles the rest.
|
||||
- **Leverages Deep Context:** AI agents understand your workspace structure, project relationships, and build configurations through Nx's project graph and metadata.
|
||||
- **Leverages Deep Context:** AI agents understand your workspace structure, project relationships, and build configurations through the Nx [project graph](/docs/features/explore-graph) and metadata.
|
||||
- **Non-Invasive Integration:** Works with your existing CI provider without overhauling your current setup.
|
||||
|
||||
## Enable Self-Healing CI
|
||||
@@ -122,6 +122,33 @@ steps:
|
||||
|
||||
{% /tabitem %}
|
||||
|
||||
{% tabitem label="Bitbucket Pipelines" %}
|
||||
|
||||
```yaml
|
||||
# bitbucket-pipelines.yml
|
||||
image: node:22
|
||||
|
||||
pipelines:
|
||||
pull-requests:
|
||||
'**':
|
||||
- step:
|
||||
name: CI
|
||||
script:
|
||||
# Your existing steps which start-ci-run, install
|
||||
# dependencies, etc.
|
||||
# These are just illustrative examples...
|
||||
- npx nx-cloud start-ci-run
|
||||
- npm ci
|
||||
- npx nx affected -t lint test build
|
||||
after-script:
|
||||
# NEW: Add this section at the end of your step
|
||||
# IMPORTANT: after-script runs regardless of step success/failure
|
||||
# so it's like if: always() on GitHub
|
||||
- npx nx fix-ci
|
||||
```
|
||||
|
||||
{% /tabitem %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
> NOTE: If all tasks succeed then the `fix-ci` command becomes a no-op automatically, so that is why "always" is recommended.
|
||||
@@ -182,31 +209,62 @@ Tasks matching these patterns will also have high-confidence, verified code chan
|
||||
|
||||
Tasks matching these patterns will **never** have code changes auto-applied, even if they match the include patterns or presets specified above. For example: `*e2e*`.
|
||||
|
||||
## Customization with CLAUDE.md
|
||||
## Configuration with SELF_HEALING.md
|
||||
|
||||
Create a `CLAUDE.md` file in your repository root to provide additional context to the AI agent:
|
||||
Create a `.nx/SELF_HEALING.md` file in your repository to provide project-specific instructions to the Self-Healing CI agent. This file contains freeform markdown that the AI agent reads and interprets naturally.
|
||||
|
||||
### Failure Classification Rules
|
||||
{% aside title="Why a dedicated file?" type="note" %}
|
||||
Using `.nx/SELF_HEALING.md` instead of `AGENTS.md` (or equivalent) separates CI-specific instructions from local development context. The file lives in the `.nx` directory alongside other Nx Cloud configuration.
|
||||
{% /aside %}
|
||||
|
||||
Override how the AI categorizes failures:
|
||||
### Example SELF_HEALING.md
|
||||
|
||||
```markdown
|
||||
## Failure Classification
|
||||
# Self-Healing Configuration
|
||||
|
||||
- Failures in `**/migrations/**` should be classified as `environment_state`
|
||||
- Test timeouts in e2e tests are usually `flaky_task`
|
||||
## Confidence Rules
|
||||
|
||||
- Fixes involving "test" targets should require high confidence
|
||||
- Formatting fixes can be applied with medium confidence
|
||||
|
||||
## Off-Limits Areas
|
||||
|
||||
- `/src/generated/` - auto-generated, do not modify
|
||||
- `/legacy/` - requires manual review
|
||||
|
||||
## Fix Preferences
|
||||
|
||||
- Prefer updating ESLint rules over adding disable comments
|
||||
- For type errors, prefer explicit types over `any`
|
||||
|
||||
## Context
|
||||
|
||||
See ARCHITECTURE.md for module boundaries.
|
||||
```
|
||||
|
||||
### Predefined Fixes
|
||||
### What to Include
|
||||
|
||||
Specify deterministic solutions for common failures:
|
||||
| Section | Purpose | Example |
|
||||
| -------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
|
||||
| **Confidence Rules** | Override how the AI categorizes failure severity | "Failures in `**/migrations/**` should be classified as `environment_state`" |
|
||||
| **Off-Limits Areas** | Directories or files the agent should never modify | "`/src/generated/` - auto-generated code" |
|
||||
| **Fix Preferences** | Guide the agent's approach to common issues | "Prefer updating ESLint rules over adding disable comments" |
|
||||
| **Predefined Fixes** | Specify deterministic solutions for known failures | "For lint failures, always try running `nx lint --fix` first" |
|
||||
| **Context** | Reference other documentation the agent should read | "See ARCHITECTURE.md for module boundaries" |
|
||||
|
||||
```markdown
|
||||
## Predefined Fixes
|
||||
### Using CLAUDE.md
|
||||
|
||||
- For lint failures, always try running `nx lint --fix` first
|
||||
- Format failures should use `nx format:write`
|
||||
```
|
||||
If your repository already has a `CLAUDE.md` file at the root, the Self-Healing CI agent will read it for additional context. When both files exist:
|
||||
|
||||
- **SELF_HEALING.md takes precedence** for any conflicting instructions
|
||||
- Both files are read, so general context in `CLAUDE.md` is still available
|
||||
- CI-specific instructions should go in `SELF_HEALING.md`
|
||||
|
||||
This allows teams to maintain `CLAUDE.md` for local development workflows while using `SELF_HEALING.md` for CI-specific behavior.
|
||||
|
||||
### Viewing Configuration Status
|
||||
|
||||
After a CI run, navigate to the pipeline execution in Nx Cloud and check the **Configurations** tab to see whether `SELF_HEALING.md` was detected and applied.
|
||||
|
||||
## Receiving Fix Notifications
|
||||
|
||||
|
||||
@@ -1,180 +1,82 @@
|
||||
---
|
||||
title: 'Enhance Your LLM'
|
||||
description: 'Learn how Nx enhances your AI assistant by providing rich workspace metadata, architectural insights, and project relationships to make your LLM smarter and more context-aware.'
|
||||
title: 'Enhance Your AI Coding Agent'
|
||||
description: 'Learn how Nx enhances your AI assistant by providing rich workspace metadata, architectural insights, and CI integration for autonomous workflows.'
|
||||
sidebar:
|
||||
order: 3
|
||||
filter: 'type:Features'
|
||||
---
|
||||
|
||||
{% youtube src="https://youtu.be/dRQq_B1HSLA" title="We Just Shipped the Monorepo MCP for Copilot" /%}
|
||||
AI agents are moving beyond autocomplete. They can now operate independently across projects. But most setups hit a wall: agents lack workspace context (seeing files, not architecture), generate inconsistent code, and have a hard time to interact with CI.
|
||||
|
||||
Monorepos [provide an ideal foundation for AI-powered development](https://nx.dev/blog/nx-and-ai-why-they-work-together), enabling cross-project reasoning and code generation. However, without proper context, **LLMs struggle to understand your workspace architecture**, seeing only individual files rather than the complete picture.
|
||||
Nx monorepos solve this by enabling cross-project reasoning and by providing the structured metadata and CI integration that agents need to work autonomously:
|
||||
|
||||
Nx transforms your AI assistant by providing rich workspace metadata that enables it to:
|
||||
- Deep **workspace architecture** understanding and project relationships
|
||||
- **Code generators** for fast, predictable scaffolding
|
||||
- **CI pipeline integration** to fix failures autonomously
|
||||
- The ability to **iterate until CI is green** without human intervention
|
||||
|
||||
- Understand your **workspace architecture** and project relationships
|
||||
- Identify **project owners** and team responsibilities
|
||||
- Access **Nx documentation** for accurate guidance
|
||||
- Leverage **code generators** for consistent scaffolding
|
||||
- Connect to your **CI pipeline** to help fix failures
|
||||
## Setup
|
||||
|
||||
The goal is to transform your AI assistant from a generic code helper into an architecturally-aware collaborator that understands your specific workspace structure and can make intelligent, context-aware decisions.
|
||||
|
||||
## How Nx MCP Enhances Your LLM
|
||||
|
||||
The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) is an open standard that enables AI models to interact with your development environment through a standardized interface. Nx implements an MCP server via the [Nx Console](/docs/getting-started/editor-setup) that exposes workspace metadata to compatible AI assistants like GitHub Copilot, Claude, and others.
|
||||
|
||||
With the Nx MCP server, your AI assistant gains a "map" of your entire system being able to go from just reasoning at the file level to seeing the higher-level picture. This allows the LLM to move between different abstraction levels - from high-level architecture down to specific implementation details:
|
||||
|
||||

|
||||
|
||||
The Nx MCP server exposes tools for workspace analysis, code generation, documentation lookup, and CI/CD analytics. For a complete list of available tools and their descriptions, see the [Nx MCP Server Reference](/docs/reference/nx-mcp#available-tools).
|
||||
|
||||
## Setting Up Nx MCP
|
||||
|
||||
To configure Nx for AI agents and AI-Assistants, run the following command:
|
||||
To configure your Nx workspace for AI agents, run:
|
||||
|
||||
```shell
|
||||
npx nx configure-ai-agents
|
||||
```
|
||||
|
||||
This configures Nx Console which automatically configures and serves the Nx MCP server for you if you're using VSCode or Cursor. It also sets up the corresponding AI agent configuration files (e.g. `CLAUDE.md`, `AGENTS.md`,...).
|
||||
This sets up:
|
||||
|
||||
### IDE Setup
|
||||
- **Agent configuration files**: `CLAUDE.md`, `AGENTS.md` with workspace-specific guidelines
|
||||
- **Agent skills**: Domain-specific knowledge for monorepo workflows — workspace exploration, code generation, task execution, CI monitoring, and package linking. Skills teach agents _how_ to work with Nx rather than dumping data into context.
|
||||
- **Nx MCP server**: Provides connectivity to Nx Cloud CI pipelines, self-healing fixes, running processes, and Nx documentation — things agents can't easily reach on their own
|
||||
|
||||
For VS Code, Cursor, and JetBrains IDE users:
|
||||
## What This Enables
|
||||
|
||||
1. Install [Nx Console](/docs/getting-started/editor-setup) from the marketplace
|
||||
2. You'll receive a notification to "Improve Copilot/AI agent with Nx-specific context"
|
||||
3. Click "Yes" to configure the MCP server
|
||||
### Self-Healing CI Integration
|
||||
|
||||

|
||||
Nx Cloud provides AI-powered [Self-Healing CI](/docs/features/ci-features/self-healing-ci) that analyzes failed runs and proposes verified fixes. With `configure-ai-agents`, your local agent connects to this CI counterpart via skills and the Nx MCP, gaining full context about run information, failures, and suggested fixes.
|
||||
|
||||
If you miss the notification, run the `nx.configureMcpServer` (`Nx: Setup MCP Server` in JetBrains) command from the command palette (Cursor: `Ctrl/Cmd + Shift + P`, JetBrains IDEs: `Ctrl/Cmd + Shift + A`).
|
||||
|
||||
### Other MCP-Compatible Clients
|
||||
|
||||
For other MCP-compatible clients like Claude Desktop, Claude Code, or Warp, you can configure the Nx MCP server manually. See the [Nx MCP Server Reference](/docs/reference/nx-mcp#client-specific-setup) for detailed setup instructions for each client.
|
||||
|
||||
Quick example for Claude Code:
|
||||
|
||||
```shell
|
||||
claude mcp add nx-mcp npx nx-mcp@latest
|
||||
```
|
||||
|
||||
## Powerful Use Cases
|
||||
|
||||
### Understanding Your Workspace Architecture
|
||||
|
||||
{% youtube src="https://youtu.be/RNilYmJJzdk" title="Nx Just Made Your LLM Way Smarter" /%}
|
||||
|
||||
Ask your AI assistant about your workspace structure and get detailed, accurate responses about projects, their types, and relationships:
|
||||
Your agent can autonomously iterate until CI passes:
|
||||
|
||||
```text
|
||||
What is the structure of this workspace?
|
||||
How are the projects organized?
|
||||
Commit this work, create a PR, and monitor CI until it's green.
|
||||
```
|
||||
|
||||
With Nx MCP, your AI assistant can:
|
||||
The workflow:
|
||||
|
||||
- Identify applications and libraries in your workspace
|
||||
- Understand project categorization through tags
|
||||
- Recognize technology types (feature, UI, data-access)
|
||||
- Determine project ownership and team responsibilities
|
||||
1. Agent pushes changes and creates PR
|
||||
2. Monitors CI pipeline
|
||||
3. Receives failure context from Nx Cloud and Self-Healing CI
|
||||
4. Accepts proposed fix or pulls context locally and manually applies it
|
||||
5. Repeats until CI is green
|
||||
|
||||

|
||||
This reduces context-switching—you review the final PR rather than intervening at each failure.
|
||||
|
||||
You can also get informed suggestions about where to implement new functionality:
|
||||
### Workspace Architecture Understanding
|
||||
|
||||
```text
|
||||
Where should I implement a feature for adding products to cart?
|
||||
```
|
||||
Nx exposes the project graph and relevant metadata to AI agents. This helps them move faster and more precisely:
|
||||
|
||||

|
||||
- Identify all applications and libraries in the workspace
|
||||
- Understand project relationships and dependencies
|
||||
- Recognize project types and ownership via tags
|
||||
- Determine which projects are affected by changes
|
||||
- Suggest where to implement new functionality based on existing structure
|
||||
|
||||
Learn more about workspace architecture understanding in our blog post [Nx Just Made Your LLM Way Smarter](https://nx.dev/blog/nx-just-made-your-llm-smarter).
|
||||
This architectural awareness is critical for agents operating in large monorepos where understanding project relationships determines the quality of generated code.
|
||||
|
||||
### Instant CI Failure Resolution
|
||||
### Predictable, Fast Code Generation
|
||||
|
||||
{% youtube src="https://youtu.be/fPqPh4h8RJg" title="Connect Your Editor, CI and LLMs" /%}
|
||||
AI-generated code is token-intensive, slow, and not guaranteed to align with patterns in other projects. Nx generators solve this by providing predictable scaffolding that agents can invoke and then adapt.
|
||||
|
||||
When a CI build fails, Nx Console can notify you directly in your editor:
|
||||
Your AI agent can:
|
||||
|
||||

|
||||
1. Find generators from [Nx plugins](/docs/plugin-registry) or custom [local workspace generators](/docs/extending-nx/local-generators)
|
||||
2. Run the generator with correct options
|
||||
3. Make small adjustments based on the specific situation
|
||||
|
||||
Your AI assistant can then:
|
||||
This approach is faster, produces consistent code across projects, and reduces hallucinations.
|
||||
|
||||
1. Access detailed information from Nx Cloud about the failed build
|
||||
2. Analyze your git history to understand what changed in your PR
|
||||
3. Understand the error context and affected files
|
||||
4. Help implement the fix right in your editor
|
||||
## Learn More
|
||||
|
||||
This integration dramatically improves the development velocity because you get immediately notified when an error occurs, you don't even have to leave your editor to understand what broke, and the LLM can help you implement or suggest a possible fix.
|
||||
|
||||
Learn more about CI integration in our blog post [Save Time: Connecting Your Editor, CI and LLMs](https://nx.dev/blog/nx-editor-ci-llm-integration).
|
||||
|
||||
### Smart Code Generation with AI-Enhanced Generators
|
||||
|
||||
{% youtube src="https://youtu.be/PXNjedYhZDs" title="Enhancing Nx Generators with AI" /%}
|
||||
|
||||
Nx generators provide predictable code scaffolding, while AI adds intelligence and contextual understanding. Instead of having the AI generate everything from scratch, you get the best of both worlds:
|
||||
|
||||
```text
|
||||
Create a new React library into the packages/orders/feat-cancel-orders folder
|
||||
and call the library with the same name of the folder structure. Afterwards,
|
||||
also connect it to the main shop application.
|
||||
```
|
||||
|
||||
Your AI assistant will:
|
||||
|
||||
1. Identify the appropriate generator and its parameters
|
||||
2. Open the Nx Console Generate UI with preset values
|
||||
3. Let you review and customize the options
|
||||
4. Execute the generator and help integrate the new code with your existing projects
|
||||
|
||||

|
||||
|
||||
This approach ensures consistent code that follows your organization's best practices while still being tailored to your specific needs. Learn more about AI-enhanced generators in our blog post [Enhancing Nx Generators with AI](https://nx.dev/blog/nx-generators-ai-integration).
|
||||
|
||||
### Documentation-Aware Configuration
|
||||
|
||||
{% youtube src="https://youtu.be/V2W94Sq_v6A?si=aBA-eppEw0fHrh5O&t=388" title="Making Cursor Smarter with an MCP Server" /%}
|
||||
|
||||
Get accurate guidance on Nx configuration without worrying about hallucinations or outdated information:
|
||||
|
||||
```text
|
||||
Can you configure Nx release for the packages of this workspace?
|
||||
Update nx.json with the necessary configuration using conventional commits
|
||||
as the versioning strategy.
|
||||
```
|
||||
|
||||
The AI assistant will:
|
||||
|
||||
1. Query the Nx docs for the latest information on release configuration
|
||||
2. Understand your workspace structure to identify packages
|
||||
3. Generate the correct configuration based on your specific needs
|
||||
4. Apply the changes to your nx.json file
|
||||
|
||||
Learn more about documentation-aware configuration in our blog post [Making Cursor Smarter with an MCP Server For Nx Monorepos](https://nx.dev/blog/nx-made-cursor-smarter).
|
||||
|
||||
### Cross-Project Dependency Analysis
|
||||
|
||||
{% youtube src="https://youtu.be/dRQq_B1HSLA?si=lhHsjRvwgijC1IL8&t=186" title="Nx MCP Now Available for VS Code Copilot" /%}
|
||||
|
||||
Understand the impact of changes across your monorepo with questions like:
|
||||
|
||||
```text
|
||||
If I change the public API of feat-product-detail, which other projects
|
||||
might be affected by that change?
|
||||
```
|
||||
|
||||
Your AI assistant can:
|
||||
|
||||
- Analyze the project graph to identify direct and indirect dependencies
|
||||
- Visualize affected projects using the `nx_visualize_graph` tool
|
||||
- Suggest strategies for refactoring that minimize impact
|
||||
- Identify which teams would need to be consulted for major changes
|
||||
|
||||
This architectural awareness is particularly powerful in larger monorepos where understanding project relationships is crucial for making informed development decisions.
|
||||
|
||||
Learn more about dependency analysis in our blog post [Nx MCP Now Available for VS Code Copilot](https://nx.dev/blog/nx-mcp-vscode-copilot).
|
||||
- [Autonomous AI Agents at Scale](https://nx.dev/blog/ai-agents-and-continuity): Infrastructure requirements for AI agent workflows
|
||||
- [Why Nx and AI Work So Well Together](https://nx.dev/blog/nx-and-ai-why-they-work-together): The foundation for AI-powered development
|
||||
- [Nx MCP Server Reference](/docs/reference/nx-mcp): Complete tool reference and setup instructions
|
||||
|
||||
@@ -83,12 +83,10 @@ If your repository is using package manager workspaces, Nx will use those settin
|
||||
|
||||
### Inferred Tasks with Tooling Plugins
|
||||
|
||||
Nx provides [plugins](/docs/concepts/nx-plugins) for tools that run tasks, like Vite, TypeScript, Playwright or Jest. These plugins can automatically [infer the Nx-specific task configuration](/docs/concepts/inferred-tasks) based on the tooling configuration files that already exist.
|
||||
Nx [plugins](/docs/concepts/nx-plugins) for tools like Vite, TypeScript, Playwright, and Jest automatically [infer task configuration](/docs/concepts/inferred-tasks) from your existing tooling config files — keeping them as the single source of truth.
|
||||
|
||||
In the example below, because the `/apps/cart/vite.config.ts` file exists, Nx knows that the `cart` project can run a `build` task using Vite. If you expand the `build` task, you can also see that Nx configured the output directory for the [cache](/docs/features/cache-task-results) to match the `build.outDir` provided in the Vite configuration file.
|
||||
|
||||
With inferred tasks, you can keep your tooling configuration file as the one source of truth for that tool's configuration, instead of adding an extra layer of configuration on top.
|
||||
|
||||
```ts
|
||||
// /apps/cart/vite.config.ts
|
||||
/// <reference types='vitest' />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: 'Building and Testing Angular Apps in Nx'
|
||||
description: In this tutorial you'll create a frontend-focused workspace with Nx.
|
||||
description: In this tutorial you'll create a frontend-focused monorepo with Nx.
|
||||
sidebar:
|
||||
label: 'Angular Monorepo'
|
||||
filter: 'type:Guides'
|
||||
|
||||
@@ -106,7 +106,7 @@ Root project 'gradle-tutorial'
|
||||
|
||||
## Add Nx
|
||||
|
||||
Nx is a build system with built in tooling and advanced CI capabilities. It helps you maintain and scale monorepos,
|
||||
Nx is a monorepo platform with built in tooling and advanced CI capabilities. It helps you maintain and scale monorepos,
|
||||
both locally and on CI. We will explore the features of Nx in this tutorial by adding it to the Gradle workspace above.
|
||||
|
||||
To add Nx, run
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: 'Building and Testing React Apps in Nx'
|
||||
sidebar:
|
||||
label: 'React Monorepo'
|
||||
description: In this tutorial you'll create a frontend-focused workspace with Nx.
|
||||
description: In this tutorial you'll create a frontend-focused monorepo with Nx.
|
||||
filter: 'type:Guides'
|
||||
---
|
||||
|
||||
|
||||