Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c86b654022 | |||
| 25dd46eb70 | |||
| 123b908211 | |||
| f7adc5f29a | |||
| f6644b4462 | |||
| 872d197b3c | |||
| f3870a019f | |||
| cee72ad540 | |||
| 540321dad9 | |||
| b9ec5592e5 | |||
| f067b0d826 | |||
| 0ab10ae59e | |||
| 95c92db114 | |||
| d1767de440 | |||
| a1d99dbacf | |||
| 377bc2f8d0 | |||
| deac1bd335 | |||
| 77e97fb3a9 | |||
| f36b010cec | |||
| 28e9999044 | |||
| b29172fb08 |
+1
-4
@@ -1,8 +1,5 @@
|
||||
[env]
|
||||
JEMALLOC_SYS_WITH_MALLOC_CONF = "dirty_decay_ms:1000,muzzy_decay_ms:0"
|
||||
|
||||
[build]
|
||||
target-dir = 'dist/target'
|
||||
target-dir = 'build/target'
|
||||
|
||||
[target.x86_64-unknown-linux-musl]
|
||||
rustflags = [
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
# Commit Command
|
||||
|
||||
## Description
|
||||
|
||||
Create a git commit following Nx repository standards and validation requirements.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
/commit [message]
|
||||
```
|
||||
|
||||
## What this command does:
|
||||
|
||||
1. **Pre-commit validation**: Runs the full validation suite (`pnpm nx prepush`) to ensure code quality
|
||||
2. **Formatting**: Automatically formats changed files with Prettier
|
||||
3. **Testing**: Runs tests on affected projects to validate changes
|
||||
4. **Commit creation**: Creates a well-formed commit with proper message formatting (without co-author attribution)
|
||||
5. **Status reporting**: Provides clear feedback on the commit process
|
||||
|
||||
## Workflow:
|
||||
|
||||
1. Format any modified files with Prettier
|
||||
2. Run the prepush validation suite
|
||||
3. If validation passes, stage relevant changes
|
||||
4. Create commit with descriptive message
|
||||
5. Provide summary of what was committed
|
||||
|
||||
## Commit Message Format:
|
||||
|
||||
- Use conventional commit format when appropriate
|
||||
- Include scope (e.g., `feat(core):`, `fix(angular):`, `docs(nx):`)
|
||||
- Keep first line under 72 characters
|
||||
- Include detailed description if needed
|
||||
|
||||
## Examples:
|
||||
|
||||
- `/commit "feat(core): add new project graph visualization"`
|
||||
- `/commit "fix(react): resolve build issues with webpack config"`
|
||||
- `/commit "docs(nx): update getting started guide"`
|
||||
|
||||
## Validation Requirements:
|
||||
|
||||
- All tests must pass
|
||||
- Code must be properly formatted
|
||||
- No linting errors
|
||||
- E2E tests for affected areas should pass
|
||||
@@ -1,155 +0,0 @@
|
||||
# GitHub Issue Planning and Resolution
|
||||
|
||||
This command provides guidance for both automated and manual GitHub issue workflows.
|
||||
|
||||
## Automated Workflow (GitHub Actions)
|
||||
|
||||
The automated workflow consists of two phases:
|
||||
|
||||
### Phase 1: Planning (`@claude plan` or `claude:plan` label)
|
||||
|
||||
- Claude analyzes the issue and creates a detailed implementation plan
|
||||
- Plan is posted as a comment on the issue
|
||||
- Issue is labeled with `claude:planned`
|
||||
|
||||
### Phase 2: Implementation (`@claude implement` or `claude:implement` label)
|
||||
|
||||
- Claude implements the solution based on the plan
|
||||
- Runs validation tests and creates a feature branch
|
||||
- Suggests opening a PR with proper formatting
|
||||
|
||||
## Planning Phase Template
|
||||
|
||||
When creating a plan (either automated or manual), include these sections:
|
||||
|
||||
### Problem Analysis
|
||||
|
||||
- Root cause identification
|
||||
- Impact assessment
|
||||
- Related components or systems affected
|
||||
|
||||
### Proposed Solution
|
||||
|
||||
- High-level approach
|
||||
- Alternative solutions considered
|
||||
- Trade-offs and rationale
|
||||
|
||||
### Implementation Details
|
||||
|
||||
- Files that need to be modified
|
||||
- Key changes required
|
||||
- Dependencies or prerequisites
|
||||
|
||||
### Testing Strategy
|
||||
|
||||
- Unit tests to add/modify
|
||||
- Integration tests needed
|
||||
- E2E test considerations
|
||||
|
||||
### Validation Steps
|
||||
|
||||
```bash
|
||||
# Test specific affected projects
|
||||
nx run-many -t test,build,lint -p PROJECT_NAME
|
||||
|
||||
# Test all affected projects
|
||||
nx affected -t build,test,lint
|
||||
|
||||
# Run affected e2e tests
|
||||
nx affected -t e2e-local
|
||||
|
||||
# Format code
|
||||
npx nx prettier -- FILES
|
||||
|
||||
# Final validation
|
||||
pnpm nx prepush
|
||||
```
|
||||
|
||||
### Risks and Considerations
|
||||
|
||||
- Breaking changes
|
||||
- Performance implications
|
||||
- Migration requirements
|
||||
|
||||
## Manual Workflow
|
||||
|
||||
When working on a GitHub issue manually, follow this systematic approach:
|
||||
|
||||
## 1. Get Issue Details
|
||||
|
||||
```bash
|
||||
# Get issue details using GitHub CLI (replace ISSUE_NUMBER with actual number)
|
||||
gh issue view ISSUE_NUMBER
|
||||
```
|
||||
|
||||
When cloning reproduction repos, please clone within `./tmp/claude/repro-ISSUE_NUMBER`
|
||||
|
||||
## 2. Analyze the Plan
|
||||
|
||||
- Look for a plan or implementation details in the issue description
|
||||
- Check comments for additional context or clarification
|
||||
- Identify affected projects and components
|
||||
|
||||
## 3. Implement the Solution
|
||||
|
||||
- Follow the plan outlined in the issue
|
||||
- Make focused changes that address the specific problem
|
||||
- Ensure code follows existing patterns and conventions
|
||||
|
||||
## 4. Run Full Validation
|
||||
|
||||
```bash
|
||||
# Test specific affected projects first
|
||||
nx run-many -t test,build,lint -p PROJECT_NAME
|
||||
|
||||
# Test all affected projects
|
||||
nx affected -t build,test,lint
|
||||
|
||||
# Run affected e2e tests
|
||||
nx affected -t e2e-local
|
||||
|
||||
# Final pre-push validation
|
||||
pnpm nx prepush
|
||||
```
|
||||
|
||||
## 5. Submit Pull Request
|
||||
|
||||
- Create a descriptive PR title that references the issue
|
||||
- Include "Fixes #ISSUE_NUMBER" in the PR description
|
||||
- Provide a clear summary of changes made
|
||||
- Request appropriate reviewers
|
||||
|
||||
## Pull Request Template
|
||||
|
||||
When creating a pull request, follow the template found in `.github/PULL_REQUEST_TEMPLATE.md`. The template includes:
|
||||
|
||||
### Required Sections
|
||||
|
||||
1. **Current Behavior**: Describe the behavior we have today
|
||||
2. **Expected Behavior**: Describe the behavior we should expect with the changes in this PR
|
||||
3. **Related Issue(s)**: Link the issue being fixed so it gets closed when the PR is merged
|
||||
|
||||
### Template Format
|
||||
|
||||
```markdown
|
||||
## Current Behavior
|
||||
|
||||
<!-- This is the behavior we have today -->
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
<!-- This is the behavior we should expect with the changes in this PR -->
|
||||
|
||||
## Related Issue(s)
|
||||
|
||||
<!-- Please link the issue being fixed so it gets closed when this is merged. -->
|
||||
|
||||
Fixes #ISSUE_NUMBER
|
||||
```
|
||||
|
||||
### Guidelines
|
||||
|
||||
- Ensure your commit message follows the conventional commit format (use `pnpm commit`)
|
||||
- Read the submission guidelines in CONTRIBUTING.md before posting
|
||||
- For complex changes, you can request a dedicated Nx release by mentioning the Nx team
|
||||
- Always link the related issue using "Fixes #ISSUE_NUMBER" to automatically close it when merged
|
||||
@@ -1,30 +0,0 @@
|
||||
# Claude Issue Workflow Usage Guide
|
||||
|
||||
## Quick Start
|
||||
|
||||
## Expected Outputs
|
||||
|
||||
### Planning Phase
|
||||
|
||||
- Detailed analysis comment posted to issue
|
||||
- Implementation plan with steps and file changes
|
||||
- Testing strategy and validation steps
|
||||
- Risk assessment
|
||||
|
||||
### Implementation Phase
|
||||
|
||||
- Code changes made according to plan
|
||||
- Tests run and validated
|
||||
- Feature branch created: `fix/issue-{number}`
|
||||
- PR suggestion with proper title format
|
||||
|
||||
## Manual Override
|
||||
|
||||
If you need to work on an issue manually, use the `/gh-issue-plan` command for structured guidance following the same workflow patterns.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Ensure you're on the authorized users list
|
||||
- Check that the issue has sufficient detail for analysis
|
||||
- For implementation, ensure a plan comment exists from the planning phase
|
||||
- If workflows fail, check the Actions tab for detailed logs
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(find:*)",
|
||||
"Bash(ls:*)",
|
||||
"Bash(mkdir:*)",
|
||||
"WebFetch(domain:github.com)",
|
||||
"WebFetch(domain:www.typescriptlang.org)",
|
||||
"Bash(git log:*)",
|
||||
"Bash(gh issue list:*)",
|
||||
"Bash(gh issue view:*)",
|
||||
"Bash(npx prettier:*)",
|
||||
"Bash(nx prepush:*)",
|
||||
"Bash(pnpm commit:*)",
|
||||
"Bash(rg:*)",
|
||||
"mcp__nx__nx_docs",
|
||||
"mcp__nx__nx_workspace",
|
||||
"mcp__nx__nx_project_details",
|
||||
"Bash(nx show projects:*)",
|
||||
"Bash(nx run-many:*)",
|
||||
"Bash(nx run:*)",
|
||||
"Bash(nx affected:*)",
|
||||
"Bash(nx lint:*)",
|
||||
"Bash(nx test:*)",
|
||||
"Bash(nx build:*)",
|
||||
"Bash(nx documentation:*)"
|
||||
],
|
||||
"deny": []
|
||||
},
|
||||
"enableAllProjectMcpServers": true,
|
||||
"env": {
|
||||
"BASH_MAX_TIMEOUT_MS": "1800000"
|
||||
},
|
||||
"extraKnownMarketplaces": {
|
||||
"nx-claude-plugins": {
|
||||
"source": {
|
||||
"source": "github",
|
||||
"repo": "nrwl/nx-ai-agents-config",
|
||||
"ref": "experimental"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enabledPlugins": {
|
||||
"nx@nx-claude-plugins": true,
|
||||
"polygraph@nx-claude-plugins": true
|
||||
}
|
||||
}
|
||||
@@ -1,301 +0,0 @@
|
||||
---
|
||||
name: diagnose-sandbox-report
|
||||
description: >
|
||||
Diagnose Nx sandbox violations from a sandbox report. Use when asked to
|
||||
"diagnose sandbox", "analyze sandbox report", "investigate sandbox violations",
|
||||
"check violations", when given a sandbox report JSON file or URL to investigate,
|
||||
or when the user pastes a staging.nx.app sandbox-report URL. Also trigger when
|
||||
discussing unexpected reads/writes in Nx task execution. Guides structured
|
||||
investigation of why tasks read/write undeclared files, determines root causes,
|
||||
and recommends fixes.
|
||||
argument-hint: '<sandbox-report.json or URL> [--filter <file|pattern|list>]'
|
||||
allowed-tools: Bash, Read, Grep, Glob
|
||||
---
|
||||
|
||||
# Diagnose Sandbox Report
|
||||
|
||||
## Overview
|
||||
|
||||
Sandbox violations occur when an Nx task reads files not declared as inputs or writes files not declared as outputs.
|
||||
|
||||
**Unexpected reads** are one of:
|
||||
|
||||
1. **Missing input** (most likely) — the process legitimately needs this file. Understand what the process does and why the access makes sense, then declare it as an input.
|
||||
2. **Potential sandboxing gap** (last resort) — the access is irrelevant to correctness and should be filtered/ignored by the sandbox. Only conclude this after exhausting every possibility for it being a missing input.
|
||||
|
||||
**Unexpected writes** follow the same logic:
|
||||
|
||||
1. **Missing output** (most likely) — the process legitimately produces this file.
|
||||
2. **Potential sandboxing gap** (last resort) — same as above.
|
||||
|
||||
The default assumption is that an unexpected access IS a missing declaration. The investigation's job is to understand WHY the process accesses the file — not to find reasons it shouldn't.
|
||||
|
||||
## Critical Rules
|
||||
|
||||
1. **NEVER read the sandbox report JSON directly** — these files are too large for the Read tool (50K+ tokens). Do NOT use `Read`, `cat`, `head`, `python3`, or `jq` on the raw report. All report parsing is handled by the script.
|
||||
2. **ALWAYS run the context-gathering script as the very first step** — no manual parsing, no ad-hoc python/jq on the report file. The script does everything deterministically.
|
||||
3. If the script fails, **report the error and stop**. Do not attempt manual parsing as a fallback.
|
||||
4. **Identify the inferring plugin BEFORE proposing any fix** — check `inference.plugin` in the script output or run `jq '.targets.<target>.metadata' <detail-file>`. Fixing the wrong plugin wastes entire investigation rounds.
|
||||
5. **Verify hypotheses empirically before committing to them** — see Principle 4 and the Phase 2 instrumentation guidance.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Phase 0: Input
|
||||
|
||||
User provides one of:
|
||||
|
||||
- Path to a sandbox report JSON file
|
||||
- A URL to a sandbox report — pass it directly to the script, it handles downloading
|
||||
- A task ID + CIPE URL (fetch report via MCP if available)
|
||||
- Inline violation data
|
||||
|
||||
If a task ID is provided but no report, ask the user for the report file.
|
||||
|
||||
**Filtering**: Most invocations will focus on specific files, not the entire report. The user may specify:
|
||||
|
||||
- A single file: `e2e.log`
|
||||
- A comma-separated list: `apps/nx-cloud/e2e.log,apps/nx-cloud/build/client/assets/main.js`
|
||||
- A glob pattern: `*.tsbuildinfo`, `apps/nx-cloud/build/**`
|
||||
- A directory prefix: `apps/nx-cloud/build/client/assets`
|
||||
|
||||
When the user specifies files to focus on, pass them via `--filter` to the script. When they don't specify a filter and the report has many violations, summarize the groupings (by directory, extension) and ask which group(s) to investigate first rather than trying to investigate everything at once.
|
||||
|
||||
### Phase 1: Deterministic Pre-Processing
|
||||
|
||||
Run the context-gathering script **immediately** — this is the first tool call after reading the user's input.
|
||||
|
||||
Call it exactly as shown — do NOT append `2>&1` or `2>/dev/null` (the script manages its own stderr internally). Run in the **foreground** (no `run_in_background`) with a **3-minute timeout** — reports can be large and the script runs the task + multiple nx commands:
|
||||
|
||||
```bash
|
||||
npx tsx ${CLAUDE_SKILL_DIR}/scripts/gather-sandbox-context.ts <report.json or URL> [--filter <pattern>] [--workspace <path>]
|
||||
```
|
||||
|
||||
Pass `--filter` when the user wants to focus on specific files or patterns. The script filters violations before all downstream processing (grouping, validation, classification), so the output only contains relevant data.
|
||||
|
||||
The script produces two outputs:
|
||||
|
||||
**stdout** (~3-5KB compact brief) — everything needed to start investigating:
|
||||
|
||||
- `summary`: violation counts (total, filtered, confirmed vs undeclared)
|
||||
- `undeclaredFiles`: the actual file paths that are true violations
|
||||
- `grouping`: violations grouped by directory and extension
|
||||
- `commands`: processes with violations (pid, cmd, executable, arguments, counts) — no full file lists
|
||||
- `classificationSummary`: counts per category (cross-project, build artifacts, config files, etc.)
|
||||
- `crossProjectDependencyCheck`: whether cross-project file owners are in the task's dependency chain
|
||||
- `staleDeclarations`: grouped analysis of expectedInputsNotRead / expectedOutputsNotWritten
|
||||
- `dependentTasksOutputFiles`: extracted from target inputs config and named inputs — shows what dep output globs are declared (critical for cross-project violations)
|
||||
- `executorInfo`: executor name and resolved source path in `node_modules` — read this file to understand how the tool is invoked
|
||||
- `checkSample`: results of `--check` on up to 5 undeclared files (catches false positives early)
|
||||
- `inference` + `pluginRegistration`: plugin metadata
|
||||
- `verificationCommands`: pre-built `--check` commands with the correct task ref
|
||||
- `detailFile`: path to the full detail JSON
|
||||
|
||||
**detail file** (`/tmp/sandbox-diagnosis-detail-<project>-<target>.json`) — full data for drill-down. Structure:
|
||||
|
||||
- `processTree.processTree`: array of `{pid, cmd, parentPid}` entries
|
||||
- `processTree.processPidToCmd`: `{ "pid": "command string" }` map
|
||||
- `processTree.readsByPid`: `{ "pid": ["file1", "file2"] }` — violated reads grouped by PID
|
||||
- `processTree.writesByPid`: `{ "pid": ["file1", "file2"] }` — violated writes grouped by PID
|
||||
- `targetConfig`: full target configuration (executor, options, inputs, outputs, dependsOn)
|
||||
- `projectConfig`: full project configuration
|
||||
- `resolvedInputs`: `{ files: [...], depOutputs: [...], runtime: [...], environment: [...] }`
|
||||
- `resolvedOutputs`: `{ outputPaths: [...], expandedOutputs: [...] }`
|
||||
- `validation`: `{ reads: { confirmed: [...], undeclared: [...] }, writes: { ... } }`
|
||||
- `classification`: `{ reads: { crossProject, buildArtifacts, configFiles, ... }, writes: { ... } }`
|
||||
|
||||
Read the brief output — it has everything to start. Use `jq` on the detail file only when you need to drill into specific sections. When querying the detail file, use the structure above — do not guess the schema. Do NOT use Python, ad-hoc scripts, or the Read tool on the detail file — only `jq`.
|
||||
|
||||
For reports with many violations, use `--filter` to narrow scope. When investigating without a filter, use the `grouping` data to identify patterns and prioritize — don't try to trace every file individually.
|
||||
|
||||
If `summary.undeclaredReads` and `summary.undeclaredWrites` are both 0, all violations were resolved by the script's validation against resolved inputs/outputs. Report this to the user — no further investigation needed.
|
||||
|
||||
The `commands` array pre-parses each process — use `executable` and `arguments` to identify the tool without re-parsing `cmd`. When many files share the same root cause, group them under one finding using a glob pattern or count (e.g., "88 `.d.ts` files matching `packages/nx/dist/**/*.d.ts`").
|
||||
|
||||
### Phase 2: Command Analysis — the core investigation
|
||||
|
||||
**This is the most important phase.** The goal is to determine with 100% certainty why each process reads or writes each violated file. Do not classify violations from file names or paths alone — trace the actual causal chain from command → config → file access.
|
||||
|
||||
#### Step 1: Understand the command
|
||||
|
||||
The brief's `commands` array pre-parses each process. Use the `executable` and `arguments` fields directly — don't re-parse `cmd`. Identify:
|
||||
|
||||
- The tool (from `executable`)
|
||||
- The arguments (target files/dirs, config flags, extensions — from `arguments`)
|
||||
- The working directory (from executor options or project root)
|
||||
|
||||
#### Step 2: Trace why the command accesses each violated file
|
||||
|
||||
For each violated file, establish the **exact causal chain** that leads the command to read or write it. The approach is the same regardless of tool:
|
||||
|
||||
1. Identify the tool's config file (usually in the project root or workspace root)
|
||||
2. Read the config and trace file references: `includes`, `extends`, `presets`, entry points, plugins
|
||||
3. Follow the reference chain until you can explain exactly why the violated file is accessed
|
||||
|
||||
Common causal patterns:
|
||||
|
||||
- **Config chain walk-up**: tool reads config, config extends another, chain reaches the violated file (e.g., tsconfig `extends`, eslint config chain, jest preset chain)
|
||||
- **Directory traversal**: tool scans a directory for matching files and reads everything, including files it won't process (e.g., jest-haste-map scanning `.next/`, eslint reading `.d.ts` alongside `.ts`)
|
||||
- **Dependency resolution**: tool resolves imports/requires and follows the dependency graph to files outside the project (e.g., esbuild/vite/webpack resolving workspace packages to their dist outputs)
|
||||
- **Plugin/transformer loading**: tool loads plugins or transformers that read additional files (e.g., ts-jest loading tsconfig for TypeScript compilation)
|
||||
|
||||
For any tool, read its source code in `node_modules` to understand its file discovery behavior. Don't assume — trace the actual code.
|
||||
|
||||
**You must be able to explain the full path:** e.g., "eslint loads `.eslintrc.json` → configures `@typescript-eslint/parser` → parser resolves `parserOptions.project` → walks up to find `tsconfig.json` → reads it." If you can't trace the full path, keep investigating — do not guess.
|
||||
|
||||
**When theoretical analysis is inconclusive, verify empirically.** For difficult cases, instrument `node_modules` with interceptors to capture real stack traces. For example, patch `fs.readFileSync` in the tool's entry point to log stack traces when the violated file is accessed. A confirmed stack trace is worth more than multiple rounds of code reading.
|
||||
|
||||
#### Step 3: Confirm the violation with `--check`
|
||||
|
||||
**This step is mandatory — do not skip it.** The script already runs `--check` on a sample of up to 5 undeclared files (see `checkSample` in the brief). Review those results first — if the sample files are confirmed as inputs/outputs, the corresponding violations are false positives.
|
||||
|
||||
For files not in the sample, use the pre-generated commands from `verificationCommands` in the brief:
|
||||
|
||||
```bash
|
||||
npx nx show target inputs <project>:<target> --check <violated-read-files>
|
||||
npx nx show target outputs <project>:<target> --check <violated-write-files>
|
||||
```
|
||||
|
||||
If the commands fail because output files don't exist (e.g., the script's task run timed out), run the task first with `verificationCommands.runTask`.
|
||||
|
||||
If `--check` shows the file IS already an input/output, the violation is a false positive from the script's static analysis. If it confirms the file is NOT an input/output, proceed to classification.
|
||||
|
||||
#### Step 4: Classify
|
||||
|
||||
With the causal chain established and the violation confirmed, classify into one of these categories:
|
||||
|
||||
1. **Missing input/output** (most common) — the process legitimately needs this file. Understand why:
|
||||
- **Direct dependency** — the tool needs this file to do its job (e.g., tsc reads referenced tsconfigs, eslint loads config chain)
|
||||
- **Transitive dependency** — a config file references another file that references this one (e.g., jest preset → resolver → module). Trace the full chain.
|
||||
- **Directory traversal side effect** — the tool reads all files in a directory even if it only processes some (e.g., eslint reads `.d.ts` files while linting `.ts`). Still a legitimate access from the tool's perspective.
|
||||
|
||||
2. **Bad tool configuration** — the tool accesses a file it shouldn't because its scope is too broad. The fix is fixing the tool's config, NOT adding an input. Investigate:
|
||||
- Is the command targeting too broad a directory? (e.g., `eslint .` instead of `eslint src/`)
|
||||
- Is a config file missing ignore/exclude rules? (e.g., eslint processing a file type it should skip)
|
||||
- Is a plugin inferring a target for a project that doesn't match? (e.g., eslint target on a non-JS project)
|
||||
- Is an env var causing the tool to behave differently?
|
||||
|
||||
3. **Potential sandboxing gap** (last resort) — the access is genuinely irrelevant to correctness (PID files, temp sockets, dev server logs that no task consumes). Only conclude this after exhausting categories 1 and 2.
|
||||
|
||||
### Phase 3: Deep Investigation
|
||||
|
||||
For violations that aren't immediately obvious, investigate further:
|
||||
|
||||
#### If the target is inferred by a plugin
|
||||
|
||||
1. Identify which plugin from `inference.plugin` in the brief output, or `nx show project --json` metadata
|
||||
2. Read the plugin's `createNodesV2` implementation to understand inference logic
|
||||
3. Determine if this project should have this target at all
|
||||
4. Check if the plugin has `include`/`exclude` patterns in `nx.json` that should filter this project
|
||||
5. **Check for input override layers** — `project.json`, `package.json`, or `nx.json` `targetDefaults` may override plugin-inferred inputs, rendering plugin-level fixes invisible. Check all three before concluding a plugin fix is sufficient.
|
||||
|
||||
#### If violations come from a subprocess
|
||||
|
||||
1. Trace the process tree: which parent spawned the subprocess?
|
||||
2. Why does the subprocess exist? (dev server for e2e, worker thread, build tool subprocess)
|
||||
3. What environment does the subprocess inherit? (env vars, cwd)
|
||||
4. Does the subprocess access files in a different project's directory?
|
||||
|
||||
#### If violations involve config file reference chains
|
||||
|
||||
1. Read the config file (jest.config, tsconfig, .eslintrc)
|
||||
2. Trace all file references: `preset`, `extends`, `references`, `setupFiles`, `resolver`, `moduleNameMapper`, `transform`, etc.
|
||||
3. Recursively resolve references (preset → preset → files)
|
||||
4. Determine which referenced files are not declared as task inputs
|
||||
|
||||
#### If violations involve dependency task outputs
|
||||
|
||||
1. Check `dependsOn` to understand task dependency chain
|
||||
2. Check `dependentTasksOutputFiles` glob pattern — is it too narrow?
|
||||
3. Compare the glob against actual file types the tool reads from dependencies (e.g., `**/*.d.ts` missing `.tsbuildinfo`)
|
||||
|
||||
#### Generalizability analysis
|
||||
|
||||
After diagnosing the root cause, determine scope:
|
||||
|
||||
1. Is this violation specific to this project, or does it affect all projects using this tool/plugin?
|
||||
2. What conditions trigger it? (specific config, specific tool version, specific project structure)
|
||||
3. Should the fix be per-project (declarative input) or systemic (plugin improvement)?
|
||||
4. If the plugin can be made smarter to infer the correct inputs, that's preferable to manual declarations.
|
||||
|
||||
### Phase 4: Output
|
||||
|
||||
**You MUST present findings using the structured format below before proceeding to any implementation discussion.** Do not use free-form narrative — the structure ensures completeness and makes findings reviewable.
|
||||
|
||||
Present findings grouped by category:
|
||||
|
||||
```
|
||||
=== Sandbox Violation Diagnosis: {project}:{target} ===
|
||||
|
||||
## Summary
|
||||
Unexpected reads: N total → M validated as declared → K true violations
|
||||
Unexpected writes: N total → M validated as declared → K true violations
|
||||
|
||||
## Findings
|
||||
|
||||
### [MISSING INPUT] {short description}
|
||||
Files: {file list or pattern}
|
||||
Process: PID {pid} — {command}
|
||||
Why: {why the process legitimately needs this file}
|
||||
Scope: {project-specific or affects all projects using this tool/plugin}
|
||||
Fix: {where/how to add the input declaration — consider both declarative (add input) and systemic (improve plugin inference) options}
|
||||
|
||||
### [MISSING OUTPUT] {short description}
|
||||
Files: {file list or pattern}
|
||||
Process: PID {pid} — {command}
|
||||
Why: {why the process produces this file}
|
||||
Scope: {project-specific or affects all projects using this tool/plugin}
|
||||
Fix: {where/how to add the output declaration}
|
||||
|
||||
### [BAD TOOL CONFIG] {short description}
|
||||
Files: {file list or pattern}
|
||||
Process: PID {pid} — {command}
|
||||
Why: {why the tool accesses files it shouldn't — config too broad, missing ignore, etc.}
|
||||
Fix: {specific tool config change}
|
||||
|
||||
### [POTENTIAL SANDBOXING GAP] {short description}
|
||||
Files: {file list or pattern}
|
||||
Process: PID {pid} — {command}
|
||||
Why: {why this access is irrelevant to correctness}
|
||||
Evidence: {proof that categories 1-2 were exhausted}
|
||||
|
||||
### [INVESTIGATE] {short description}
|
||||
Files: {file list or pattern}
|
||||
Notes: {what's known, what needs more info}
|
||||
Question: {what to ask the user or team}
|
||||
|
||||
## Stale Declarations
|
||||
expectedInputsNotRead: {count and details if relevant}
|
||||
expectedOutputsNotWritten: {count and details if relevant}
|
||||
|
||||
## Verification Plan
|
||||
For each fix, provide the exact commands to verify:
|
||||
1. Run the task so output files exist on disk: `npx nx <target> <project> --skip-nx-cache`
|
||||
2. Check each violation file is now an input: `npx nx show target <project>:<target> inputs --check <space-separated files>`
|
||||
3. For plugin-level fixes: build the plugin, patch node_modules, then verify with steps 1-2
|
||||
```
|
||||
|
||||
## Principles
|
||||
|
||||
1. **Missing declaration is the default.** Most unexpected accesses are legitimate — the process needs the file, it just wasn't declared. Start from this assumption and investigate to understand WHY the access happens.
|
||||
2. **The command is the unit of analysis.** Don't classify files in isolation. Understand what the command does and whether each file access makes sense given that command's purpose.
|
||||
3. **Trace the full chain.** Plugin inference → target config → executor → command → file access. The root cause is often several layers removed from the symptom.
|
||||
4. **Empirical over theoretical.** When code analysis produces a hypothesis, verify it before acting. Instrument `node_modules`, capture stack traces, run with debug flags. Wrong theories waste entire investigation rounds.
|
||||
5. **Be thorough.** Read plugin source code, config files, executor implementations. Don't guess based on file names alone.
|
||||
6. **Potential sandboxing gaps are last resort.** Only conclude this after exhausting missing declaration and bad tool config. The access must be genuinely irrelevant to correctness.
|
||||
7. **Verify claims about Nx behavior in source code.** Any assertion about how Nx works must be traced to the actual implementation. Do not reason from theory or assumptions.
|
||||
8. **Prefer systemic fixes over per-project declarations.** If a plugin can be improved to infer correct inputs for all projects, that's better than adding manual input declarations to each project.
|
||||
|
||||
## Delegating to Subagents
|
||||
|
||||
When the investigation is complex and requires parallel research, you can delegate to subagents. Follow this pattern:
|
||||
|
||||
1. **Run the context-gathering script yourself first.** The brief output (~3-5KB) is the shared context all subagents need.
|
||||
2. **Include the brief output in each subagent prompt** along with the specific question to investigate. Subagents should NOT run the script again or try to parse the raw report.
|
||||
3. **Give subagents the detail file path** so they can `jq` specific sections (process tree, resolved inputs, etc.) without re-running the script.
|
||||
4. **Each subagent should answer one focused question**, e.g., "Why does PID 12345 (eslint) read `tsconfig.base.json`? Trace the full causal chain from the eslint config."
|
||||
5. **Subagents must still follow the skill principles** — trace full causal chains, verify empirically, use `--check`, don't guess from file names. Include these instructions in the subagent prompt.
|
||||
6. **Synthesize subagent results yourself** using the structured Phase 4 output format. Do not delegate the final classification.
|
||||
|
||||
## Reference
|
||||
|
||||
For the sandbox report data model and field definitions, see `references/data-model.md`.
|
||||
@@ -1,92 +0,0 @@
|
||||
# Sandbox Report Data Model
|
||||
|
||||
## Raw Report Structure (JSON)
|
||||
|
||||
```typescript
|
||||
interface SandboxReport {
|
||||
taskId: string; // "project:target" or "project:target:configuration"
|
||||
sandboxReportId: string;
|
||||
inputs: string[]; // declared input patterns (globs or paths)
|
||||
outputs: string[]; // declared output patterns
|
||||
filesRead: FileAccessEntry[]; // all files actually read
|
||||
filesWritten: FileAccessEntry[]; // all files actually written
|
||||
unexpectedReads?: FileAccessEntry[]; // reads not matching any input pattern
|
||||
unexpectedWrites?: FileAccessEntry[]; // writes not matching any output pattern
|
||||
expectedInputsNotRead?: string[]; // declared inputs never accessed
|
||||
expectedOutputsNotWritten?: string[]; // declared outputs never written
|
||||
processTree?: ProcessTreeEntry[]; // process hierarchy with commands
|
||||
}
|
||||
|
||||
interface FileAccessEntry {
|
||||
path: string; // workspace-relative file path
|
||||
pid: number; // process ID that accessed the file
|
||||
}
|
||||
|
||||
interface ProcessTreeEntry {
|
||||
pid: number;
|
||||
cmd: string; // full command string
|
||||
parentPid?: number; // parent process (absent for root)
|
||||
}
|
||||
```
|
||||
|
||||
## Violation Computation
|
||||
|
||||
Violations are computed by `findUnexpectedFiles()` using `minimatch`:
|
||||
|
||||
- A file is "unexpected" if it does NOT match any declared pattern
|
||||
- Patterns without wildcards also match as directory prefixes (`pattern + '/'`)
|
||||
- If `unexpectedReads`/`unexpectedWrites` are pre-computed in the report, those are used directly
|
||||
|
||||
## Nx CLI Commands for Context
|
||||
|
||||
### `nx show target <project:target> --json`
|
||||
|
||||
Returns: executor, command, options (merged with configuration), inputs (configured, not resolved), outputs, dependsOn, cache, parallelism, configurations, metadata.
|
||||
|
||||
### `nx show target inputs <project:target> --json`
|
||||
|
||||
Returns resolved input files (requires files to exist on disk — task must have run):
|
||||
|
||||
```json
|
||||
{
|
||||
"files": ["workspace-relative paths..."],
|
||||
"runtime": ["node version checks..."],
|
||||
"environment": ["ENV_VAR_NAMES..."],
|
||||
"depOutputs": ["dependency output paths..."],
|
||||
"external": ["external package names..."]
|
||||
}
|
||||
```
|
||||
|
||||
### `nx show target inputs <project:target> --check <files...>`
|
||||
|
||||
Validates specific files against declared inputs. Exit code 0 = match, 1 = no match.
|
||||
Categories: `files`, `environment`, `runtime`, `external`, `depOutputs`.
|
||||
Also detects directory matches (directory containing N input files).
|
||||
|
||||
### `nx show target outputs <project:target> --json`
|
||||
|
||||
Returns:
|
||||
|
||||
```json
|
||||
{
|
||||
"outputPaths": ["configured output paths..."],
|
||||
"expandedOutputs": ["glob-expanded actual paths..."],
|
||||
"unresolvedOutputs": ["{options.key} patterns that couldn't resolve..."]
|
||||
}
|
||||
```
|
||||
|
||||
### `nx show target outputs <project:target> --check <files...>`
|
||||
|
||||
Validates specific files against declared outputs. Same exit code behavior as inputs.
|
||||
|
||||
### `nx show project <project> --json`
|
||||
|
||||
Returns full project config. Key fields for sandbox analysis:
|
||||
|
||||
- `targets[name].metadata.plugin` — which plugin inferred the target
|
||||
- `targets[name].metadata.technologies` — what tech the target uses
|
||||
- `root` — project root directory
|
||||
|
||||
### `nx graph --view=tasks --targets=<target> --focus=<project> --print --file=stdout`
|
||||
|
||||
Returns task dependency graph with task IDs, dependencies, and roots.
|
||||
@@ -1,846 +0,0 @@
|
||||
#!/usr/bin/env npx tsx
|
||||
/**
|
||||
* gather-sandbox-context: Parse sandbox report + gather Nx task context
|
||||
* Produces structured JSON for the diagnose-sandbox-report skill
|
||||
*
|
||||
* Usage: npx tsx gather-sandbox-context.ts <report.json or URL> [--filter <pattern>] [--workspace <path>]
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
||||
import { resolve, basename, extname, dirname } from 'path';
|
||||
import { execSync, execFileSync } from 'child_process';
|
||||
import { minimatch } from 'minimatch';
|
||||
|
||||
// --- CLI argument parsing ---
|
||||
|
||||
interface Args {
|
||||
reportFile: string;
|
||||
filter: string | null;
|
||||
workspaceRoot: string;
|
||||
}
|
||||
|
||||
function parseArgs(): Args {
|
||||
const args = process.argv.slice(2);
|
||||
let reportFile = '';
|
||||
let filter: string | null = null;
|
||||
let workspaceRoot = process.cwd();
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
switch (args[i]) {
|
||||
case '--filter':
|
||||
filter = args[++i];
|
||||
break;
|
||||
case '--workspace':
|
||||
workspaceRoot = args[++i];
|
||||
break;
|
||||
case '--help':
|
||||
case '-h':
|
||||
console.error(
|
||||
'Usage: gather-sandbox-context <report.json or URL> [--filter <pattern>] [--workspace <path>]'
|
||||
);
|
||||
process.exit(1);
|
||||
default:
|
||||
if (args[i].startsWith('-')) {
|
||||
console.error(`Unknown option: ${args[i]}`);
|
||||
process.exit(1);
|
||||
}
|
||||
reportFile = args[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!reportFile) {
|
||||
console.error(
|
||||
'Usage: gather-sandbox-context <report.json or URL> [--filter <pattern>] [--workspace <path>]'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return { reportFile, filter, workspaceRoot };
|
||||
}
|
||||
|
||||
// --- Types ---
|
||||
|
||||
interface FileAccessEntry {
|
||||
path: string;
|
||||
pid: number;
|
||||
}
|
||||
|
||||
interface ProcessTreeEntry {
|
||||
pid: number;
|
||||
cmd: string;
|
||||
parentPid?: number;
|
||||
}
|
||||
|
||||
interface SandboxReport {
|
||||
taskId: string;
|
||||
unexpectedReads?: FileAccessEntry[];
|
||||
unexpectedWrites?: FileAccessEntry[];
|
||||
expectedInputsNotRead?: string[];
|
||||
expectedOutputsNotWritten?: string[];
|
||||
filesRead?: FileAccessEntry[];
|
||||
filesWritten?: FileAccessEntry[];
|
||||
processTree?: ProcessTreeEntry[];
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
function downloadUrl(url: string): string {
|
||||
const tmpPath = `/tmp/sandbox-report-${Date.now()}.json`;
|
||||
try {
|
||||
execFileSync('curl', ['-sL', '-o', tmpPath, url], { stdio: 'pipe' });
|
||||
} catch {
|
||||
console.error(`Error: Failed to download report from URL: ${url}`);
|
||||
process.exit(1);
|
||||
}
|
||||
return tmpPath;
|
||||
}
|
||||
|
||||
function runNxCommand(
|
||||
args: string[],
|
||||
workspaceRoot: string,
|
||||
timeoutMs = 30000
|
||||
): string | null {
|
||||
try {
|
||||
return execFileSync('npx', ['nx', ...args], {
|
||||
cwd: workspaceRoot,
|
||||
timeout: timeoutMs,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function safeJsonParse<T>(str: string | null, fallback: T): T {
|
||||
if (!str) return fallback;
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function filterEntries(
|
||||
entries: FileAccessEntry[],
|
||||
filterStr: string | null
|
||||
): FileAccessEntry[] {
|
||||
if (!filterStr) return entries;
|
||||
|
||||
const patterns = filterStr.split(',').map((p) => p.trim());
|
||||
return entries.filter((entry) =>
|
||||
patterns.some((pattern) => {
|
||||
if (
|
||||
pattern.includes('*') ||
|
||||
pattern.includes('?') ||
|
||||
pattern.includes('[')
|
||||
) {
|
||||
// Glob pattern — if no slashes, match against basename
|
||||
if (!pattern.includes('/')) {
|
||||
return minimatch(basename(entry.path), pattern);
|
||||
}
|
||||
return minimatch(entry.path, pattern);
|
||||
}
|
||||
// Literal: exact match or directory prefix
|
||||
return entry.path === pattern || entry.path.startsWith(pattern + '/');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function groupByDirPrefix(
|
||||
paths: string[],
|
||||
depth = 3
|
||||
): { prefix: string; count: number }[] {
|
||||
const groups: Record<string, number> = {};
|
||||
for (const p of paths) {
|
||||
const prefix = p.split('/').slice(0, depth).join('/');
|
||||
groups[prefix] = (groups[prefix] || 0) + 1;
|
||||
}
|
||||
return Object.entries(groups)
|
||||
.map(([prefix, count]) => ({ prefix, count }))
|
||||
.sort((a, b) => b.count - a.count);
|
||||
}
|
||||
|
||||
function groupByExtension(paths: string[]): { ext: string; count: number }[] {
|
||||
const groups: Record<string, number> = {};
|
||||
for (const p of paths) {
|
||||
const ext = extname(p) || '(no ext)';
|
||||
groups[ext] = (groups[ext] || 0) + 1;
|
||||
}
|
||||
return Object.entries(groups)
|
||||
.map(([ext, count]) => ({ ext, count }))
|
||||
.sort((a, b) => b.count - a.count);
|
||||
}
|
||||
|
||||
function classifyFiles(
|
||||
undeclared: string[],
|
||||
projectRoot: string,
|
||||
projectRoots: Record<string, string>
|
||||
) {
|
||||
const projects = Object.entries(projectRoots).map(([project, root]) => ({
|
||||
project,
|
||||
root,
|
||||
}));
|
||||
|
||||
const isBuildArtifact = (f: string) =>
|
||||
f.startsWith('dist/') ||
|
||||
f.startsWith('build/') ||
|
||||
f.startsWith('out-tsc/') ||
|
||||
f.startsWith('.next/') ||
|
||||
f.includes('/node_modules/.cache/') ||
|
||||
f.endsWith('.tsbuildinfo') ||
|
||||
f.includes('/dist/') ||
|
||||
f.includes('/build/output/');
|
||||
|
||||
const configBasenames = new Set(['nx.json', 'project.json', 'package.json']);
|
||||
const configPrefixes = [
|
||||
'tsconfig',
|
||||
'jest.config',
|
||||
'jest.preset',
|
||||
'.eslintrc',
|
||||
'eslint.config',
|
||||
'playwright.config',
|
||||
'webpack.config',
|
||||
'vite.config',
|
||||
'babel.config',
|
||||
'.babelrc',
|
||||
'rollup.config',
|
||||
];
|
||||
const isConfigFile = (f: string) => {
|
||||
const b = basename(f);
|
||||
return (
|
||||
configBasenames.has(b) ||
|
||||
configPrefixes.some((prefix) => b.startsWith(prefix))
|
||||
);
|
||||
};
|
||||
|
||||
const isEnvFile = (f: string) => {
|
||||
const b = basename(f);
|
||||
return b === '.env' || b.startsWith('.env.');
|
||||
};
|
||||
|
||||
const classified = undeclared.map((f) => {
|
||||
const inProjectRoot = projectRoot !== '' && f.startsWith(projectRoot + '/');
|
||||
const owner = projects.find((p) => f.startsWith(p.root + '/'));
|
||||
return {
|
||||
path: f,
|
||||
inProjectRoot,
|
||||
ownerProject: owner?.project ?? null,
|
||||
isBuildArtifact: isBuildArtifact(f),
|
||||
isConfigFile: isConfigFile(f),
|
||||
isEnvFile: isEnvFile(f),
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
crossProject: classified
|
||||
.filter((c) => !c.inProjectRoot)
|
||||
.map((c) => ({ path: c.path, owner: c.ownerProject })),
|
||||
buildArtifacts: classified
|
||||
.filter((c) => c.isBuildArtifact)
|
||||
.map((c) => c.path),
|
||||
configFiles: classified.filter((c) => c.isConfigFile).map((c) => c.path),
|
||||
envFiles: classified.filter((c) => c.isEnvFile).map((c) => c.path),
|
||||
inProjectRoot: classified.filter((c) => c.inProjectRoot).map((c) => c.path),
|
||||
outsideProjectRoot: classified
|
||||
.filter((c) => !c.inProjectRoot)
|
||||
.map((c) => c.path),
|
||||
total: undeclared.length,
|
||||
};
|
||||
}
|
||||
|
||||
function validateViolations(
|
||||
violations: string[],
|
||||
resolvedFiles: Set<string>
|
||||
): { confirmed: string[]; undeclared: string[] } {
|
||||
const confirmed: string[] = [];
|
||||
const undeclared: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const f of violations) {
|
||||
if (seen.has(f)) continue;
|
||||
seen.add(f);
|
||||
if (resolvedFiles.has(f)) {
|
||||
confirmed.push(f);
|
||||
} else {
|
||||
undeclared.push(f);
|
||||
}
|
||||
}
|
||||
return { confirmed, undeclared };
|
||||
}
|
||||
|
||||
function validateOutputViolations(
|
||||
violations: string[],
|
||||
resolvedOutputs: string[]
|
||||
): { confirmed: string[]; undeclared: string[] } {
|
||||
const outputSet = new Set(resolvedOutputs);
|
||||
const outputDirs = resolvedOutputs.map((o) => o + '/');
|
||||
const confirmed: string[] = [];
|
||||
const undeclared: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const f of violations) {
|
||||
if (seen.has(f)) continue;
|
||||
seen.add(f);
|
||||
if (outputSet.has(f) || outputDirs.some((d) => f.startsWith(d))) {
|
||||
confirmed.push(f);
|
||||
} else {
|
||||
undeclared.push(f);
|
||||
}
|
||||
}
|
||||
return { confirmed, undeclared };
|
||||
}
|
||||
|
||||
function extractCommands(
|
||||
processTree: ProcessTreeEntry[],
|
||||
readsByPid: Record<string, string[]>,
|
||||
writesByPid: Record<string, string[]>
|
||||
) {
|
||||
const pidToCmd: Record<string, string> = {};
|
||||
for (const entry of processTree) {
|
||||
pidToCmd[String(entry.pid)] = entry.cmd;
|
||||
}
|
||||
|
||||
return processTree
|
||||
.filter(
|
||||
(entry) =>
|
||||
(readsByPid[String(entry.pid)]?.length ?? 0) > 0 ||
|
||||
(writesByPid[String(entry.pid)]?.length ?? 0) > 0
|
||||
)
|
||||
.map((entry) => {
|
||||
const parts = entry.cmd.split(' ');
|
||||
const exe = parts[0].split('/').pop() ?? parts[0];
|
||||
return {
|
||||
pid: entry.pid,
|
||||
cmd: entry.cmd,
|
||||
parentPid: entry.parentPid ?? null,
|
||||
parentCmd: entry.parentPid
|
||||
? (pidToCmd[String(entry.parentPid)] ?? null)
|
||||
: null,
|
||||
unexpectedReadCount: readsByPid[String(entry.pid)]?.length ?? 0,
|
||||
unexpectedWriteCount: writesByPid[String(entry.pid)]?.length ?? 0,
|
||||
unexpectedReads: readsByPid[String(entry.pid)] ?? [],
|
||||
unexpectedWrites: writesByPid[String(entry.pid)] ?? [],
|
||||
executable: exe,
|
||||
arguments: parts.slice(1).join(' '),
|
||||
};
|
||||
})
|
||||
.sort(
|
||||
(a, b) =>
|
||||
b.unexpectedReadCount +
|
||||
b.unexpectedWriteCount -
|
||||
(a.unexpectedReadCount + a.unexpectedWriteCount)
|
||||
);
|
||||
}
|
||||
|
||||
function resolveExecutorSource(
|
||||
executor: string | undefined,
|
||||
workspaceRoot: string
|
||||
): { executor: string; sourcePath: string } {
|
||||
if (
|
||||
!executor ||
|
||||
executor === 'null' ||
|
||||
executor.includes('nx:run-commands')
|
||||
) {
|
||||
return { executor: executor ?? '', sourcePath: '' };
|
||||
}
|
||||
|
||||
const lastColon = executor.lastIndexOf(':');
|
||||
const pkg = executor.substring(0, lastColon);
|
||||
const name = executor.substring(lastColon + 1);
|
||||
|
||||
try {
|
||||
const result = execFileSync(
|
||||
'node',
|
||||
[
|
||||
'-e',
|
||||
`
|
||||
try {
|
||||
const pkg = require('${pkg}/package.json');
|
||||
const executors = pkg.executors || pkg.builders;
|
||||
if (executors) {
|
||||
const p = require.resolve('${pkg}/' + executors);
|
||||
const dir = require('path').dirname(p);
|
||||
const json = require(p);
|
||||
const impl = json.executors?.['${name}']?.implementation ||
|
||||
json.builders?.['${name}']?.implementation;
|
||||
if (impl) console.log(require.resolve(dir + '/' + impl));
|
||||
}
|
||||
} catch(e) {}
|
||||
`,
|
||||
],
|
||||
{
|
||||
cwd: workspaceRoot,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
timeout: 10000,
|
||||
}
|
||||
).trim();
|
||||
return { executor, sourcePath: result };
|
||||
} catch {
|
||||
return { executor, sourcePath: '' };
|
||||
}
|
||||
}
|
||||
|
||||
function extractDepTaskOutputFiles(
|
||||
targetConfig: any,
|
||||
workspaceRoot: string
|
||||
): { dependentTasksOutputFiles: any[]; namedInputs: string[] } {
|
||||
const inputs: any[] = targetConfig?.inputs ?? [];
|
||||
const depOutputs: any[] = [];
|
||||
const namedInputs: string[] = [];
|
||||
|
||||
for (const input of inputs) {
|
||||
if (
|
||||
typeof input === 'object' &&
|
||||
input !== null &&
|
||||
'dependentTasksOutputFiles' in input
|
||||
) {
|
||||
depOutputs.push({
|
||||
glob: input.dependentTasksOutputFiles,
|
||||
transitive: input.transitive ?? false,
|
||||
});
|
||||
} else if (
|
||||
typeof input === 'string' &&
|
||||
!input.startsWith('{') &&
|
||||
!input.startsWith('^') &&
|
||||
!input.includes('/') &&
|
||||
!input.includes('.')
|
||||
) {
|
||||
namedInputs.push(input);
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve named inputs from nx.json
|
||||
const nxJsonPath = resolve(workspaceRoot, 'nx.json');
|
||||
if (existsSync(nxJsonPath) && namedInputs.length > 0) {
|
||||
try {
|
||||
const nxJson = JSON.parse(readFileSync(nxJsonPath, 'utf-8'));
|
||||
for (const name of namedInputs) {
|
||||
const namedDef = nxJson.namedInputs?.[name] ?? [];
|
||||
for (const entry of namedDef) {
|
||||
if (
|
||||
typeof entry === 'object' &&
|
||||
entry !== null &&
|
||||
'dependentTasksOutputFiles' in entry
|
||||
) {
|
||||
depOutputs.push({
|
||||
glob: entry.dependentTasksOutputFiles,
|
||||
transitive: entry.transitive ?? false,
|
||||
fromNamedInput: name,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore nx.json parse errors
|
||||
}
|
||||
}
|
||||
|
||||
return { dependentTasksOutputFiles: depOutputs, namedInputs };
|
||||
}
|
||||
|
||||
function analyzeStaleDeclarations(
|
||||
expectedInputsNotRead: string[],
|
||||
expectedOutputsNotWritten: string[]
|
||||
) {
|
||||
const classifyPattern = (value: string) => {
|
||||
if (/[*{]/.test(value)) return 'glob';
|
||||
if (value.startsWith('^')) return 'depOutput';
|
||||
return 'file';
|
||||
};
|
||||
|
||||
const groupByType = (items: string[]) => {
|
||||
const groups: Record<string, string[]> = {};
|
||||
for (const item of items) {
|
||||
const type = classifyPattern(item);
|
||||
(groups[type] ??= []).push(item);
|
||||
}
|
||||
return Object.entries(groups).map(([type, values]) => ({
|
||||
type,
|
||||
count: values.length,
|
||||
samples: values.slice(0, 3),
|
||||
}));
|
||||
};
|
||||
|
||||
return {
|
||||
expectedInputsNotRead: expectedInputsNotRead.length,
|
||||
expectedOutputsNotWritten: expectedOutputsNotWritten.length,
|
||||
staleInputsByType: groupByType(expectedInputsNotRead),
|
||||
staleOutputsByType: groupByType(expectedOutputsNotWritten),
|
||||
};
|
||||
}
|
||||
|
||||
// --- Main ---
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs();
|
||||
let reportPath = args.reportFile;
|
||||
|
||||
// Handle URL inputs
|
||||
if (reportPath.startsWith('http')) {
|
||||
reportPath = downloadUrl(reportPath);
|
||||
}
|
||||
|
||||
if (!existsSync(reportPath)) {
|
||||
console.error(`Error: Report file not found: ${reportPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
reportPath = resolve(reportPath);
|
||||
process.chdir(args.workspaceRoot);
|
||||
|
||||
// Phase 1: Parse report (single read)
|
||||
let report: SandboxReport;
|
||||
try {
|
||||
report = JSON.parse(readFileSync(reportPath, 'utf-8'));
|
||||
} catch {
|
||||
console.error(`Error: Report file is not valid JSON: ${reportPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!report.taskId) {
|
||||
console.error('Error: Report file has no .taskId field');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const [project, target, config] = report.taskId.split(':');
|
||||
const taskRef = config
|
||||
? `${project}:${target}:${config}`
|
||||
: `${project}:${target}`;
|
||||
|
||||
const unexpectedReads = report.unexpectedReads ?? [];
|
||||
const unexpectedWrites = report.unexpectedWrites ?? [];
|
||||
|
||||
// Apply filter
|
||||
const filteredReads = filterEntries(unexpectedReads, args.filter);
|
||||
const filteredWrites = filterEntries(unexpectedWrites, args.filter);
|
||||
|
||||
const readPaths = filteredReads.map((e) => e.path);
|
||||
const writePaths = filteredWrites.map((e) => e.path);
|
||||
|
||||
// Build pid → files maps
|
||||
const readsByPid: Record<string, string[]> = {};
|
||||
const writesByPid: Record<string, string[]> = {};
|
||||
for (const entry of filteredReads) {
|
||||
(readsByPid[String(entry.pid)] ??= []).push(entry.path);
|
||||
}
|
||||
for (const entry of filteredWrites) {
|
||||
(writesByPid[String(entry.pid)] ??= []).push(entry.path);
|
||||
}
|
||||
|
||||
// Phase 2: Gather Nx task context (run task + parallel nx commands)
|
||||
runNxCommand(['run', taskRef], args.workspaceRoot, 120000);
|
||||
|
||||
const [
|
||||
targetConfigStr,
|
||||
projectConfigStr,
|
||||
resolvedInputsStr,
|
||||
resolvedOutputsStr,
|
||||
graphResult,
|
||||
] = await Promise.all([
|
||||
runNxCommand(['show', 'target', taskRef, '--json'], args.workspaceRoot),
|
||||
runNxCommand(['show', 'project', project, '--json'], args.workspaceRoot),
|
||||
runNxCommand(
|
||||
['show', 'target', 'inputs', taskRef, '--json'],
|
||||
args.workspaceRoot
|
||||
),
|
||||
runNxCommand(
|
||||
['show', 'target', 'outputs', taskRef, '--json'],
|
||||
args.workspaceRoot
|
||||
),
|
||||
(() => {
|
||||
const graphPath = `/tmp/sandbox-project-graph-${Date.now()}.json`;
|
||||
runNxCommand(['graph', '--file', graphPath], args.workspaceRoot);
|
||||
try {
|
||||
return readFileSync(graphPath, 'utf-8');
|
||||
} catch {
|
||||
return '{"graph":{"nodes":{}}}';
|
||||
}
|
||||
})(),
|
||||
]);
|
||||
|
||||
const targetConfig = safeJsonParse(targetConfigStr, {} as any);
|
||||
const projectConfig = safeJsonParse(projectConfigStr, {} as any);
|
||||
const resolvedInputs = safeJsonParse(resolvedInputsStr, {} as any);
|
||||
const resolvedOutputs = safeJsonParse(resolvedOutputsStr, {} as any);
|
||||
const projectGraph = safeJsonParse(graphResult, {
|
||||
graph: { nodes: {} },
|
||||
} as any);
|
||||
|
||||
// Phase 3: Validate violations
|
||||
const resolvedInputFiles = new Set([
|
||||
...(resolvedInputs.files ?? []),
|
||||
...(resolvedInputs.depOutputs ?? []),
|
||||
]);
|
||||
const resolvedOutputFiles = [
|
||||
...(resolvedOutputs.outputPaths ?? []),
|
||||
...(resolvedOutputs.expandedOutputs ?? []),
|
||||
];
|
||||
|
||||
const checkInputs = validateViolations(readPaths, resolvedInputFiles);
|
||||
const checkOutputs = validateOutputViolations(
|
||||
writePaths,
|
||||
resolvedOutputFiles
|
||||
);
|
||||
|
||||
// Phase 3.5: Sample --check verification
|
||||
let checkSampleInputs: any = {};
|
||||
let checkSampleOutputs: any = {};
|
||||
const sampleReadFiles = checkInputs.undeclared.slice(0, 5);
|
||||
if (sampleReadFiles.length > 0) {
|
||||
const result = runNxCommand(
|
||||
[
|
||||
'show',
|
||||
'target',
|
||||
'inputs',
|
||||
taskRef,
|
||||
'--check',
|
||||
...sampleReadFiles,
|
||||
'--json',
|
||||
],
|
||||
args.workspaceRoot
|
||||
);
|
||||
checkSampleInputs = safeJsonParse(result, {});
|
||||
}
|
||||
const sampleWriteFiles = checkOutputs.undeclared.slice(0, 5);
|
||||
if (sampleWriteFiles.length > 0) {
|
||||
const result = runNxCommand(
|
||||
[
|
||||
'show',
|
||||
'target',
|
||||
'outputs',
|
||||
taskRef,
|
||||
'--check',
|
||||
...sampleWriteFiles,
|
||||
'--json',
|
||||
],
|
||||
args.workspaceRoot
|
||||
);
|
||||
checkSampleOutputs = safeJsonParse(result, {});
|
||||
}
|
||||
|
||||
// Phase 4: File classification
|
||||
const projectRoots: Record<string, string> = {};
|
||||
for (const [name, node] of Object.entries(projectGraph.graph?.nodes ?? {})) {
|
||||
projectRoots[name] = (node as any).data?.root ?? name;
|
||||
}
|
||||
const taskProjectRoot = projectRoots[project] ?? '';
|
||||
|
||||
const readClassification = classifyFiles(
|
||||
checkInputs.undeclared,
|
||||
taskProjectRoot,
|
||||
projectRoots
|
||||
);
|
||||
const writeClassification = classifyFiles(
|
||||
checkOutputs.undeclared,
|
||||
taskProjectRoot,
|
||||
projectRoots
|
||||
);
|
||||
|
||||
// Phase 5: Command extraction
|
||||
const processTree = report.processTree ?? [];
|
||||
const commands = extractCommands(processTree, readsByPid, writesByPid);
|
||||
|
||||
// Phase 6: Inference detection
|
||||
const targetMeta = projectConfig.targets?.[target]?.metadata ?? {};
|
||||
const inference = {
|
||||
isInferred: 'plugin' in targetMeta || 'technologies' in targetMeta,
|
||||
plugin: targetMeta.plugin ?? null,
|
||||
technologies: targetMeta.technologies ?? null,
|
||||
description: targetMeta.description ?? null,
|
||||
};
|
||||
|
||||
let pluginRegistration: any = {};
|
||||
const nxJsonPath = resolve(args.workspaceRoot, 'nx.json');
|
||||
if (inference.plugin && existsSync(nxJsonPath)) {
|
||||
try {
|
||||
const nxJson = JSON.parse(readFileSync(nxJsonPath, 'utf-8'));
|
||||
const plugins = (nxJson.plugins ?? []).map((p: any) =>
|
||||
typeof p === 'string' ? { plugin: p, options: {} } : p
|
||||
);
|
||||
pluginRegistration =
|
||||
plugins.find((p: any) => p.plugin === inference.plugin) ?? {};
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 6.5: dependentTasksOutputFiles + executor resolution
|
||||
const depTaskOutputs = extractDepTaskOutputFiles(
|
||||
targetConfig,
|
||||
args.workspaceRoot
|
||||
);
|
||||
const executorInfo = resolveExecutorSource(
|
||||
targetConfig.executor ?? targetConfig.command,
|
||||
args.workspaceRoot
|
||||
);
|
||||
|
||||
// Phase 7: Cross-project dependency check
|
||||
const dependsOn = (targetConfig.dependsOn ?? []).map((d: any) =>
|
||||
typeof d === 'string' ? d : (d.target ?? '')
|
||||
);
|
||||
const checkCrossProject = (classification: typeof readClassification) => {
|
||||
const owners = [
|
||||
...new Set(
|
||||
classification.crossProject
|
||||
.map((c) => c.owner)
|
||||
.filter((o): o is string => o !== null)
|
||||
),
|
||||
];
|
||||
return owners.map((owner) => ({
|
||||
project: owner,
|
||||
isDependency: dependsOn.some(
|
||||
(d: string) =>
|
||||
d === owner ||
|
||||
d === `${owner}:build` ||
|
||||
d === `^${owner}:build` ||
|
||||
d.includes(`^${owner}`)
|
||||
),
|
||||
files: classification.crossProject
|
||||
.filter((c) => c.owner === owner)
|
||||
.map((c) => c.path),
|
||||
}));
|
||||
};
|
||||
|
||||
const crossProjectDeps = {
|
||||
reads: checkCrossProject(readClassification),
|
||||
writes: checkCrossProject(writeClassification),
|
||||
};
|
||||
|
||||
// Phase 8: Stale declarations
|
||||
const staleDeclarations = analyzeStaleDeclarations(
|
||||
report.expectedInputsNotRead ?? [],
|
||||
report.expectedOutputsNotWritten ?? []
|
||||
);
|
||||
|
||||
// Assemble outputs
|
||||
const detailFile = `/tmp/sandbox-diagnosis-detail-${taskRef.replace(/[/:@]/g, '-')}.json`;
|
||||
|
||||
const detail = {
|
||||
processTree: {
|
||||
processTree,
|
||||
processPidToCmd: Object.fromEntries(
|
||||
processTree.map((e) => [String(e.pid), e.cmd])
|
||||
),
|
||||
readsByPid,
|
||||
writesByPid,
|
||||
},
|
||||
targetConfig,
|
||||
projectConfig,
|
||||
resolvedInputs,
|
||||
resolvedOutputs,
|
||||
validation: { reads: checkInputs, writes: checkOutputs },
|
||||
classification: { reads: readClassification, writes: writeClassification },
|
||||
report: {
|
||||
taskId: report.taskId,
|
||||
totalFilesRead: report.filesRead?.length ?? 0,
|
||||
totalFilesWritten: report.filesWritten?.length ?? 0,
|
||||
totalUnexpectedReads: unexpectedReads.length,
|
||||
totalUnexpectedWrites: unexpectedWrites.length,
|
||||
expectedInputsNotRead: report.expectedInputsNotRead ?? [],
|
||||
expectedOutputsNotWritten: report.expectedOutputsNotWritten ?? [],
|
||||
},
|
||||
commands,
|
||||
crossProjectDependencyCheck: crossProjectDeps,
|
||||
staleDeclarations,
|
||||
inference,
|
||||
pluginRegistration,
|
||||
dependentTasksOutputFiles: depTaskOutputs,
|
||||
executorInfo,
|
||||
};
|
||||
writeFileSync(detailFile, JSON.stringify(detail, null, 2));
|
||||
|
||||
// Brief to stdout
|
||||
const brief = {
|
||||
task: {
|
||||
ref: taskRef,
|
||||
project,
|
||||
target,
|
||||
configuration: config ?? null,
|
||||
projectRoot: taskProjectRoot,
|
||||
},
|
||||
summary: {
|
||||
unexpectedReads: unexpectedReads.length,
|
||||
unexpectedWrites: unexpectedWrites.length,
|
||||
filteredReads: filteredReads.length,
|
||||
filteredWrites: filteredWrites.length,
|
||||
filterApplied: args.filter !== null,
|
||||
filterPattern: args.filter,
|
||||
confirmedReads: checkInputs.confirmed.length,
|
||||
undeclaredReads: checkInputs.undeclared.length,
|
||||
confirmedWrites: checkOutputs.confirmed.length,
|
||||
undeclaredWrites: checkOutputs.undeclared.length,
|
||||
},
|
||||
undeclaredFiles: {
|
||||
reads: checkInputs.undeclared,
|
||||
writes: checkOutputs.undeclared,
|
||||
},
|
||||
grouping: {
|
||||
readsByDirectory: groupByDirPrefix(readPaths),
|
||||
writesByDirectory: groupByDirPrefix(writePaths),
|
||||
byExtension: {
|
||||
readsByExt: groupByExtension(readPaths),
|
||||
writesByExt: groupByExtension(writePaths),
|
||||
},
|
||||
},
|
||||
commands: commands.map(
|
||||
({
|
||||
pid,
|
||||
cmd,
|
||||
parentCmd,
|
||||
executable,
|
||||
arguments: args,
|
||||
unexpectedReadCount,
|
||||
unexpectedWriteCount,
|
||||
}) => ({
|
||||
pid,
|
||||
cmd,
|
||||
parentCmd,
|
||||
executable,
|
||||
arguments: args,
|
||||
unexpectedReadCount,
|
||||
unexpectedWriteCount,
|
||||
})
|
||||
),
|
||||
checkSample: {
|
||||
inputs: checkSampleInputs,
|
||||
outputs: checkSampleOutputs,
|
||||
},
|
||||
classificationSummary: {
|
||||
reads: {
|
||||
crossProject: readClassification.crossProject.length,
|
||||
buildArtifacts: readClassification.buildArtifacts.length,
|
||||
configFiles: readClassification.configFiles.length,
|
||||
envFiles: readClassification.envFiles.length,
|
||||
inProjectRoot: readClassification.inProjectRoot.length,
|
||||
outsideProjectRoot: readClassification.outsideProjectRoot.length,
|
||||
},
|
||||
writes: {
|
||||
crossProject: writeClassification.crossProject.length,
|
||||
buildArtifacts: writeClassification.buildArtifacts.length,
|
||||
configFiles: writeClassification.configFiles.length,
|
||||
envFiles: writeClassification.envFiles.length,
|
||||
inProjectRoot: writeClassification.inProjectRoot.length,
|
||||
outsideProjectRoot: writeClassification.outsideProjectRoot.length,
|
||||
},
|
||||
},
|
||||
crossProjectDependencyCheck: crossProjectDeps,
|
||||
staleDeclarations,
|
||||
dependentTasksOutputFiles: depTaskOutputs.dependentTasksOutputFiles,
|
||||
executorInfo,
|
||||
inference,
|
||||
pluginRegistration,
|
||||
verificationCommands: {
|
||||
checkInputs: `npx nx show target inputs ${taskRef} --check <files...>`,
|
||||
checkOutputs: `npx nx show target outputs ${taskRef} --check <files...>`,
|
||||
runTask: `npx nx run ${taskRef} --skip-nx-cache`,
|
||||
},
|
||||
detailFile,
|
||||
};
|
||||
|
||||
console.log(JSON.stringify(brief, null, 2));
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(`Script failed: ${err.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,100 +0,0 @@
|
||||
---
|
||||
name: nx-docs-style-check
|
||||
description: Check modified Nx documentation pages against the astro-docs style guide. Auto-trigger after writing or editing docs content in the nx repo. Also trigger on "check style", "style guide", "docs review", "validate docs". Should run as a final step whenever docs files are modified. IMPORTANT: anytime astro-docs/**/*.mdoc files are modified, this should always run automatically without being asked.
|
||||
allowed-tools: Read, Glob, Grep
|
||||
---
|
||||
|
||||
# Nx docs style check
|
||||
|
||||
You are a documentation editor for Nx. Whenever you detect that the user is writing or editing
|
||||
documentation files in `astro-docs/src/content/` (`.mdoc`, `.mdx`, `.md`), automatically run this
|
||||
check and fix any issues. Do not wait to be asked.
|
||||
|
||||
## Phase 1: Information architecture audit
|
||||
|
||||
Read `astro-docs/STYLE_GUIDE.md` (the "Information architecture" section) and
|
||||
`astro-docs/sidebar.mts` to understand where the page lives in the sidebar hierarchy.
|
||||
|
||||
For every new or moved page, evaluate against ALL FIVE principles. These are non-negotiable:
|
||||
|
||||
### 1. Progressive disclosure ("journey" rule)
|
||||
|
||||
- Is this for the first 30 minutes (Getting Started), first 30 days (Features), or forever (Reference)?
|
||||
- Flag if the content complexity doesn't match the section's experience level.
|
||||
|
||||
### 2. Category homogeneity ("scan" rule)
|
||||
|
||||
- Look at sibling pages in the same sidebar section.
|
||||
- Do they all share the same content type (concepts, tasks, or products)?
|
||||
- Flag if this page mixes types that siblings don't.
|
||||
|
||||
### 3. Type-based navigation ("intent" rule)
|
||||
|
||||
- Is this a learning page (narrative/guide) or a lookup page (reference/API)?
|
||||
- Flag if it's in the wrong category (e.g., a reference page in a guides section).
|
||||
|
||||
### 4. Pen and paper test ("theory" rule)
|
||||
|
||||
- Can the page be explained using only pen and paper (no terminal needed)?
|
||||
- YES = belongs in "How Nx Works" (architecture/concepts)
|
||||
- NO (needs terminal/code examples) = belongs in "Platform Features" or "Technologies"
|
||||
- Flag if a concept page has terminal output, CLI commands, or code-heavy examples.
|
||||
|
||||
### 5. Universal vs. specific ("placement" rule)
|
||||
|
||||
- Does this feature apply to every Nx user?
|
||||
- YES = "Platform Features"
|
||||
- NO (only React/Angular/etc. users) = "Technologies"
|
||||
- Flag if a technology-specific page is in Platform Features or vice versa.
|
||||
|
||||
## Phase 2: Style validation
|
||||
|
||||
### Step 1: Run Vale and fix errors
|
||||
|
||||
Run `nx run astro-docs:vale` to check the modified files.
|
||||
|
||||
- **errors** — fix these automatically. Edit the file to resolve the violation.
|
||||
- **warnings** — fix these automatically when the fix is unambiguous (e.g., sentence case headings).
|
||||
For ambiguous cases, suggest the fix and ask.
|
||||
- **suggestions** — mention them to the user but do not auto-fix.
|
||||
|
||||
### Step 2: Fix issues Vale doesn't catch
|
||||
|
||||
Read `astro-docs/STYLE_GUIDE.md` and check for that things that Vale may have missed.
|
||||
|
||||
### Handling false positives
|
||||
|
||||
Use inline Vale comments to suppress legitimate exceptions:
|
||||
|
||||
```markdown
|
||||
<!-- vale Nx.Headings = NO -->
|
||||
|
||||
## extractLicenses
|
||||
|
||||
<!-- vale Nx.Headings = YES -->
|
||||
```
|
||||
|
||||
Common cases where suppression is appropriate:
|
||||
|
||||
- **CLI option headings** (e.g., `## extractLicenses`) — camelCase by design.
|
||||
Prefer wrapping in backticks first (`## \`extractLicenses\``).
|
||||
- **Product possessives in historical/migration context** (e.g., "Angular's original schematic system")
|
||||
- **Terminology in migration docs** (e.g., explaining what "schematics" were before being renamed)
|
||||
|
||||
Do NOT suppress rules just to avoid fixing real violations.
|
||||
|
||||
## Output summary
|
||||
|
||||
After fixing, report what you did:
|
||||
|
||||
```
|
||||
## Style check results
|
||||
|
||||
### Information architecture: [PASS/FAIL]
|
||||
[List any violations or confirm all five principles pass]
|
||||
|
||||
### Vale: [X errors fixed, Y warnings fixed, Z suggestions noted]
|
||||
[Summary of changes made]
|
||||
|
||||
### Manual fixes: [list of additional fixes applied]
|
||||
```
|
||||
@@ -1,151 +0,0 @@
|
||||
---
|
||||
name: nx-gradle-plugin-version-bump
|
||||
description: Bump the dev.nx.gradle.project-graph plugin version. Use when updating the Gradle project graph plugin version across the codebase, creating the migration files, and updating migrations.json.
|
||||
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
|
||||
---
|
||||
|
||||
# Gradle Plugin Version Bump
|
||||
|
||||
Bumps the `dev.nx.gradle.project-graph` plugin to a new version. This is a recurring task that touches 5 files in an identical pattern every time.
|
||||
|
||||
## Required Inputs
|
||||
|
||||
Collect these values from the master branch before starting:
|
||||
|
||||
1. `NEW_VERSION` - the version we want to bump to
|
||||
Example: OLD_VERSION: 0.1.15 => NEW_VERSION: 0.1.16
|
||||
You can find this value by looking at the `OLD_VERSION` specified in `packages/gradle/project-graph/build.gradle.kts` in the `version` field.
|
||||
The NEW_VERSION will be the `OLD_VERSION` + 1.
|
||||
|
||||
2. `NX_MIGRATION_VERSION` - the version of Nx that will trigger our version bump migration
|
||||
Example: OLD_VERSION: 22.7.0-beta.0 => NEW_VERSION: 22.7.0-beta.1
|
||||
You can find this value by looking at the `nx` version in `package.json` under `devDependencies`. The NEW_VERSION will be the `OLD_VERSION` + 1.
|
||||
|
||||
3. `MIGRATION_FOLDER` - the folder name under `packages/gradle/src/migrations/` that will contain our migration files
|
||||
Example: NEW_VERSION: 22.7.0-beta.1 => MIGRATION_FOLDER: 22-7-0
|
||||
Take the version and replace all the dots with hyphens and remove the `beta` or `rc` suffix.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Update the version constant
|
||||
|
||||
**File:** `packages/gradle/src/utils/versions.ts`
|
||||
|
||||
Change `gradleProjectGraphVersion` to the new version:
|
||||
|
||||
```ts
|
||||
export const gradleProjectGraphVersion = 'NEW_VERSION';
|
||||
```
|
||||
|
||||
### 2. Update build.gradle.kts
|
||||
|
||||
**File:** `packages/gradle/project-graph/build.gradle.kts`
|
||||
|
||||
Update the `version` on line 13:
|
||||
|
||||
```kotlin
|
||||
version = "NEW_VERSION"
|
||||
```
|
||||
|
||||
### 3. Create migration TypeScript file
|
||||
|
||||
**File:** `packages/gradle/src/migrations/MIGRATION_FOLDER/change-plugin-version-NEW_VERSION.ts`
|
||||
|
||||
Determine the previous version by reading the current `gradleProjectGraphVersion` from `packages/gradle/src/utils/versions.ts` before modifying it.
|
||||
|
||||
Template:
|
||||
|
||||
```ts
|
||||
import { Tree, readNxJson } from '@nx/devkit';
|
||||
import { hasGradlePlugin } from '../../utils/has-gradle-plugin';
|
||||
import { addNxProjectGraphPlugin } from '../../generators/init/gradle-project-graph-plugin-utils';
|
||||
import { updateNxPluginVersionInCatalogsAst } from '../../utils/version-catalog-ast-utils';
|
||||
|
||||
/* Change the plugin version to NEW_VERSION
|
||||
*/
|
||||
export default async function update(tree: Tree) {
|
||||
const nxJson = readNxJson(tree);
|
||||
if (!nxJson) {
|
||||
return;
|
||||
}
|
||||
if (!hasGradlePlugin(tree)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const gradlePluginVersionToUpdate = 'NEW_VERSION';
|
||||
|
||||
// Update version in version catalogs using AST-based approach to preserve formatting
|
||||
await updateNxPluginVersionInCatalogsAst(tree, gradlePluginVersionToUpdate);
|
||||
|
||||
// Then update in build.gradle(.kts) files
|
||||
await addNxProjectGraphPlugin(tree, gradlePluginVersionToUpdate);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Create migration documentation file
|
||||
|
||||
**File:** `packages/gradle/src/migrations/MIGRATION_FOLDER/change-plugin-version-NEW_VERSION.md`
|
||||
|
||||
Replace `PREV_VERSION` with the version that was current before this bump.
|
||||
|
||||
Template:
|
||||
|
||||
````md
|
||||
#### Change dev.nx.gradle.project-graph to version NEW_VERSION
|
||||
|
||||
Change dev.nx.gradle.project-graph to version NEW_VERSION in build file
|
||||
|
||||
#### Sample Code Changes
|
||||
|
||||
##### Before
|
||||
|
||||
\```text title="build.gradle"
|
||||
plugins {
|
||||
id "dev.nx.gradle.project-graph" version "PREV_VERSION"
|
||||
}
|
||||
\```
|
||||
|
||||
##### After
|
||||
|
||||
\```text title="build.gradle"
|
||||
plugins {
|
||||
id "dev.nx.gradle.project-graph" version "NEW_VERSION"
|
||||
}
|
||||
\```
|
||||
````
|
||||
|
||||
### 5. Add migration entry to migrations.json
|
||||
|
||||
**File:** `packages/gradle/migrations.json`
|
||||
|
||||
Add a new entry at the end of the `generators` object (before the closing `}`), following the existing pattern:
|
||||
|
||||
```json
|
||||
"change-plugin-version-NEW_VERSION": {
|
||||
"version": "NX_MIGRATION_VERSION",
|
||||
"cli": "nx",
|
||||
"description": "Change dev.nx.gradle.project-graph to version NEW_VERSION in build file",
|
||||
"factory": "./src/migrations/MIGRATION_FOLDER/change-plugin-version-NEW_VERSION"
|
||||
}
|
||||
```
|
||||
|
||||
The migration key uses the version with hyphens replacing dots (e.g., `0-1-16`).
|
||||
|
||||
## Verification
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
nx run-many -t test,build,lint -p gradle
|
||||
```
|
||||
|
||||
## Commit Convention
|
||||
|
||||
```
|
||||
chore(gradle): bump gradle project graph plugin version to NEW_VERSION
|
||||
```
|
||||
|
||||
## Final Verification
|
||||
|
||||
Take a look at the most recent Gradle version bump PR and compare your changes to that. You should not be touching more or less files than
|
||||
the most recent version bump PR. If you do, ask for more information and stop all changes.
|
||||
@@ -1,87 +0,0 @@
|
||||
---
|
||||
name: run-nx-generator
|
||||
description: Run Nx generators with prioritization for workspace-plugin generators. Use this when generating code, scaffolding new features, or automating repetitive tasks in the monorepo.
|
||||
allowed-tools: Bash, Read, Glob, Grep, mcp__nx-mcp__nx_generators, mcp__nx-mcp__nx_generator_schema
|
||||
---
|
||||
|
||||
# Run Nx Generator
|
||||
|
||||
This skill helps you execute Nx generators efficiently, with special focus on workspace-plugin generators from your internal tooling.
|
||||
|
||||
## Generator Priority List
|
||||
|
||||
Use the `mcp__nx-mcp__nx_generator_schema` tool to get more information about how to use the generator
|
||||
|
||||
Choose which generators to run in this priority order:
|
||||
|
||||
### 🔥 Workspace-Plugin Generators (High Priority)
|
||||
|
||||
These are your custom internal tools in `tools/workspace-plugin/`
|
||||
|
||||
### 📦 Core Nx Generators (Standard)
|
||||
|
||||
Only use these if workspace-plugin generators don't fit:
|
||||
|
||||
- `nx generate @nx/devkit:...` - DevKit utilities
|
||||
- `nx generate @nx/node:...` - Node.js libraries
|
||||
- `nx generate @nx/react:...` - React components and apps
|
||||
- Framework-specific generators
|
||||
|
||||
## How to Run Generators
|
||||
|
||||
1. **List available generators**:
|
||||
|
||||
2. **Get generator schema** (to see available options):
|
||||
Use the `mcp__nx-mcp__nx_generator_schema` tool to get more information about how to use the generator
|
||||
|
||||
3. **Run the generator**:
|
||||
|
||||
```bash
|
||||
nx generate [generator-path] [options]
|
||||
```
|
||||
|
||||
4. **Verify the changes**:
|
||||
- Review generated files
|
||||
- Run tests: `nx affected -t test`
|
||||
- Format code: `npx prettier --write [files]`
|
||||
|
||||
## Best Practices
|
||||
|
||||
- ✅ Always check workspace-plugin first - it has your custom solutions
|
||||
- ✅ Use `--dry-run` flag to preview changes before applying
|
||||
- ✅ Format generated code immediately with Prettier
|
||||
- ✅ Test affected projects after generation
|
||||
- ✅ Commit generator changes separately from manual edits
|
||||
|
||||
## Examples
|
||||
|
||||
### Bumping Maven Version
|
||||
|
||||
When updating the Maven plugin version, use the workspace-plugin generator:
|
||||
|
||||
```bash
|
||||
nx generate @nx/workspace-plugin:bump-maven-version \
|
||||
--newVersion 0.0.10 \
|
||||
--nxVersion 22.1.0-beta.7
|
||||
```
|
||||
|
||||
This automates all the version bumping instead of manual file edits.
|
||||
|
||||
### Creating a New Plugin
|
||||
|
||||
For creating a new create-nodes plugin:
|
||||
|
||||
```bash
|
||||
nx generate @nx/workspace-plugin:create-nodes-plugin \
|
||||
--name my-custom-plugin
|
||||
```
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when you need to:
|
||||
|
||||
- Generate new code or projects
|
||||
- Scaffold new features or libraries
|
||||
- Automate repetitive setup tasks
|
||||
- Update internal tools and configurations
|
||||
- Create migrations or version updates
|
||||
@@ -1,480 +0,0 @@
|
||||
---
|
||||
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
|
||||
@@ -1,428 +0,0 @@
|
||||
# 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
|
||||
```
|
||||
@@ -1,437 +0,0 @@
|
||||
---
|
||||
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
|
||||
```
|
||||
@@ -1,228 +0,0 @@
|
||||
---
|
||||
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
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
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`.
|
||||
@@ -1,58 +0,0 @@
|
||||
---
|
||||
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`)
|
||||
@@ -1,186 +0,0 @@
|
||||
---
|
||||
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'
|
||||
```
|
||||
@@ -8,11 +8,11 @@
|
||||
// Try a more recent distribution, if your are having build issues related to GLIBC version
|
||||
// Here we use 'bookworm', which is based on `Debian-12`, which comes with `GLIBC v2.36`
|
||||
// (Nx tools currenlty requires `GLIBC v2.33` or higher)
|
||||
// Note: Using base debian image instead of typescript-node since mise will manage all tools
|
||||
"image": "mcr.microsoft.com/devcontainers/base:bookworm",
|
||||
"image": "mcr.microsoft.com/devcontainers/typescript-node:20-bookworm",
|
||||
|
||||
// All tools (Node, Java, Rust, Dotnet) are managed by mise via mise.toml
|
||||
"features": {},
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/rust:1": {}
|
||||
},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// 4211 = nx graph port
|
||||
|
||||
@@ -1,30 +1,12 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
|
||||
# Update the underlying (Debian) OS, to make sure we have the latest security patches and libraries like 'GLIBC'
|
||||
# Update the underlying (Debian) OS, to make sure we have the latest security patches and libraries like 'GLIBC'
|
||||
echo "⚙️ Updating the underlying OS..."
|
||||
sudo apt-get update && sudo apt-get -y upgrade
|
||||
|
||||
# Install mise for managing development tools (Node, Java, Rust, Dotnet)
|
||||
echo "⚙️ Installing mise..."
|
||||
curl https://mise.run | sh
|
||||
|
||||
# Add mise to PATH
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
|
||||
# Trust the mise.toml configuration file
|
||||
echo "⚙️ Trusting mise.toml configuration..."
|
||||
mise trust
|
||||
|
||||
# Install all tools from mise.toml (node, java, rust, dotnet)
|
||||
echo "⚙️ Installing tools via mise (node, java, rust, dotnet)..."
|
||||
mise install
|
||||
|
||||
# Activate mise to make tools available in current shell
|
||||
eval "$(mise activate bash)"
|
||||
|
||||
# Add mise activation to bashrc for future shell sessions
|
||||
echo "⚙️ Configuring mise activation in shell..."
|
||||
echo 'eval "$(~/.local/bin/mise activate bash)"' >> ~/.bashrc
|
||||
# Uninstall globally installed PNPM (required version will be reinstalled through corepack)
|
||||
echo "❌ Uninstalling globally installed PNPM..."
|
||||
npm uninstall -g pnpm
|
||||
|
||||
# Prevent corepack from prompting user before downloading PNPM
|
||||
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# 4 space indentation
|
||||
[*.{kts,kt,js,ts,jsx,tsx}]
|
||||
[*.{js,ts,jsx,tsx}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
|
||||
+2
-19
@@ -4,7 +4,7 @@
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"ignorePatterns": ["**/*.ts", "**/test-output", "**/dist"],
|
||||
"ignorePatterns": ["**/*.ts"],
|
||||
"plugins": ["@typescript-eslint", "@nx"],
|
||||
"extends": ["plugin:storybook/recommended"],
|
||||
"rules": {
|
||||
@@ -76,8 +76,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"@nx/workspace/valid-command-object": "error",
|
||||
"@nx/workspace/require-windows-hide": "error"
|
||||
"@nx/workspace/valid-command-object": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -97,22 +96,6 @@
|
||||
"rules": {
|
||||
"@angular-eslint/prefer-standalone": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.spec.ts",
|
||||
"*.spec.tsx",
|
||||
"*.spec.js",
|
||||
"*.spec.jsx",
|
||||
"*.test.ts",
|
||||
"*.test.tsx",
|
||||
"*.test.js",
|
||||
"*.test.jsx"
|
||||
],
|
||||
"plugins": ["jest"],
|
||||
"rules": {
|
||||
"jest/no-disabled-tests": "warn"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,438 +0,0 @@
|
||||
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
|
||||
```"""
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"nx-mcp": {
|
||||
"type": "stdio",
|
||||
"command": "npx",
|
||||
"args": ["nx", "mcp"]
|
||||
}
|
||||
},
|
||||
"contextFileName": "AGENTS.md"
|
||||
}
|
||||
@@ -1,437 +0,0 @@
|
||||
---
|
||||
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
|
||||
```
|
||||
@@ -1,228 +0,0 @@
|
||||
---
|
||||
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
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
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`.
|
||||
@@ -1,58 +0,0 @@
|
||||
---
|
||||
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`)
|
||||
@@ -1,186 +0,0 @@
|
||||
---
|
||||
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'
|
||||
```
|
||||
@@ -1,10 +0,0 @@
|
||||
#
|
||||
# https://help.github.com/articles/dealing-with-line-endings/
|
||||
#
|
||||
# Linux start script should use lf
|
||||
/gradlew text eol=lf
|
||||
# These are Windows script files and should use crlf
|
||||
*.bat text eol=crlf
|
||||
# Exclude files from Graphite reviews
|
||||
docs/generated/* linguist-generated=true
|
||||
*.pdf,*.gif,*.mp4,*.webp,*.avif,*.png,*.jpeg,*.jpg,*.tiff filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -28,12 +28,12 @@ Note: We reserve the right to remove unmaintained plugins from the registry. If
|
||||
|
||||
## Steps to Submit Your Plugin
|
||||
- Use the following commit message template: `chore(core): nx plugin submission [PLUGIN_NAME]`
|
||||
- Update the `astro-docs/src/content/approved-community-plugins.json` file with a new entry for your plugin that includes `name`, `url`, `description`:
|
||||
- Update the `community/approved-plugins.json` file with a new entry for your plugin that includes `name`, `url`, `description`:
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
// astro-docs/src/content/approved-community-plugins.json
|
||||
// community/approved-plugins.json
|
||||
|
||||
[{
|
||||
"name": "@community/plugin",
|
||||
@@ -42,7 +42,7 @@ Example:
|
||||
}]
|
||||
```
|
||||
|
||||
Once merged, your plugin will be available when running the `nx list` command, and will also be available in the Plugin Registry on [nx.dev](https://nx.dev/docs/plugin-registry)
|
||||
Once merged, your plugin will be available when running the `nx list` command, and will also be available in the Plugin Registry on [nx.dev](https://nx.dev/plugin-registry)
|
||||
-->
|
||||
|
||||
# Community Plugin Submission
|
||||
|
||||
@@ -1,478 +0,0 @@
|
||||
---
|
||||
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
|
||||
@@ -1,18 +0,0 @@
|
||||
# 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
|
||||
@@ -1,437 +0,0 @@
|
||||
---
|
||||
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
|
||||
```
|
||||
@@ -1,437 +0,0 @@
|
||||
---
|
||||
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
|
||||
```
|
||||
@@ -1,228 +0,0 @@
|
||||
---
|
||||
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
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
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`.
|
||||
@@ -1,58 +0,0 @@
|
||||
---
|
||||
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`)
|
||||
@@ -1,186 +0,0 @@
|
||||
---
|
||||
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'
|
||||
```
|
||||
@@ -1,92 +0,0 @@
|
||||
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@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.0.3
|
||||
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@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.0.3
|
||||
with:
|
||||
path: .banner-hash
|
||||
key: banner-content-hash-${{ github.run_id }}
|
||||
+47
-210
@@ -4,21 +4,17 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- '[0-9]+.[0-9]+.x'
|
||||
pull_request:
|
||||
branches:
|
||||
- "**"
|
||||
|
||||
env:
|
||||
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
|
||||
NX_CLOUD_ENABLE_METRICS_COLLECTION: 'true'
|
||||
PNPM_HOME: ~/.pnpm
|
||||
|
||||
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'
|
||||
@@ -27,28 +23,20 @@ jobs:
|
||||
NX_E2E_RUN_E2E: 'true'
|
||||
NX_CI_EXECUTION_ENV: 'linux'
|
||||
NX_CLOUD_NO_TIMEOUTS: 'true'
|
||||
NX_ALLOW_NON_CACHEABLE_DTE: 'true'
|
||||
NX_CLOUD_EXPERIMENTAL_POLLING: 'true'
|
||||
NX_CLOUD_CONTINUOUS_ASSIGNMENT: 'false'
|
||||
NX_CLOUD_VERBOSE_LOGGING: 'true'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
filter: tree:0
|
||||
|
||||
- name: Set verbose logging from debug mode
|
||||
if: runner.debug == '1'
|
||||
run: echo "NX_VERBOSE_LOGGING=true" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Fetch Master
|
||||
run: git fetch origin master:master
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
- name: Set SHAs
|
||||
uses: nrwl/nx-set-shas@310288c04d90696f9f1bc27c5e3caea6642b53d4 # v5.0.0
|
||||
uses: nrwl/nx-set-shas@v4
|
||||
with:
|
||||
main-branch-name: 'master'
|
||||
|
||||
@@ -61,266 +49,115 @@ jobs:
|
||||
sudo apt-get install -y ca-certificates lsof libvips-dev libglib2.0-dev libgirepository1.0-dev
|
||||
|
||||
- name: Install Chrome
|
||||
uses: browser-actions/setup-chrome@2dbff04819ebbfd5c974947148805a825b8a07fd # v2.1.0
|
||||
uses: browser-actions/setup-chrome@v1
|
||||
|
||||
- name: Setup dev tools with mise
|
||||
uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3
|
||||
|
||||
- name: Enable corepack and install pnpm
|
||||
run: |
|
||||
corepack enable
|
||||
corepack prepare --activate
|
||||
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@48b5f213c81028ace310571dc5ec0fbbca0b2947 # v4.4.3
|
||||
version: 9.8.0
|
||||
run_install: false
|
||||
|
||||
- name: Install project dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm playwright install --with-deps
|
||||
|
||||
- name: Install Playwright
|
||||
run: pnpm playwright install --with-deps
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Nx Report
|
||||
run:
|
||||
pnpm nx report
|
||||
- name: Check Documentation
|
||||
run: pnpm nx documentation
|
||||
timeout-minutes: 20
|
||||
|
||||
- name: Run Checks/Lint/Test/Build
|
||||
run: |
|
||||
pids=()
|
||||
|
||||
pnpm nx record -- nx format:check &
|
||||
pnpm nx-cloud record -- nx format:check &
|
||||
pids+=($!)
|
||||
|
||||
pnpm nx record -- nx sync:check
|
||||
pnpm nx-cloud record -- nx sync:check
|
||||
pids+=($!)
|
||||
|
||||
pnpm nx build workspace-plugin && pnpm nx record -- pnpm nx-cloud conformance:check
|
||||
pnpm nx-cloud record -- nx-cloud conformance:check
|
||||
pids+=($!)
|
||||
|
||||
pnpm nx run-many -t check-imports check-lock-files check-codeowners --parallel=1 --no-dte &
|
||||
pnpm nx run-many -t check-imports check-commit check-lock-files check-codeowners --parallel=1 --no-dte &
|
||||
pids+=($!)
|
||||
|
||||
pnpm nx affected --targets=lint,test,build,e2e,e2e-ci,format-native,lint-native,gradle:build-ci,vale,run &
|
||||
pnpm nx affected --targets=lint,test,build,e2e,e2e-ci &
|
||||
pids+=($!)
|
||||
|
||||
for pid in "${pids[@]}"; do
|
||||
wait "$pid"
|
||||
done
|
||||
timeout-minutes: 100
|
||||
- name: Fix CI
|
||||
run: pnpm nx fix-ci
|
||||
if: failure()
|
||||
|
||||
main-macos:
|
||||
runs-on: macos-latest
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}${{ contains(github.event_name, 'push') && format('-{0}', github.sha) || '' }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NX_E2E_CI_CACHE_KEY: e2e-github-macos
|
||||
NX_PERF_LOGGING: 'false'
|
||||
NX_CI_EXECUTION_ENV: 'macos'
|
||||
SELECTED_PM: 'npm'
|
||||
steps:
|
||||
|
||||
- name: Log concurrency info
|
||||
run: |
|
||||
echo "Concurrency group: ${{ github.workflow }}-${{ github.ref }}${{ contains(github.event_name, 'push') && format('-{0}', github.sha) || '' }}"
|
||||
echo "Concurrency cancel-in-progress: ${{ !contains(github.event_name, 'push') }}"
|
||||
echo "Concurrency cancel-event-name: ${{ github.event_name }}"
|
||||
if: always()
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
filter: tree:0
|
||||
|
||||
- name: Set verbose logging from debug mode
|
||||
if: runner.debug == '1'
|
||||
run: echo "NX_VERBOSE_LOGGING=true" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Fetch Master
|
||||
run: git fetch origin master:master
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
- name: Setup dev tools with mise
|
||||
uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3
|
||||
|
||||
- name: Enable corepack and install pnpm
|
||||
run: |
|
||||
corepack enable
|
||||
corepack prepare --activate
|
||||
|
||||
- name: Set SHAs
|
||||
uses: nrwl/nx-set-shas@310288c04d90696f9f1bc27c5e3caea6642b53d4 # v5.0.0
|
||||
with:
|
||||
main-branch-name: 'master'
|
||||
|
||||
- name: Check for React Native changes
|
||||
id: check-changes
|
||||
run: |
|
||||
HAS_CHANGED=$(node ./scripts/check-react-native-changes.js $NX_BASE $NX_HEAD);
|
||||
if $HAS_CHANGED; then
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
echo "React Native projects are affected, will run macOS tests"
|
||||
else
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
echo "No React Native projects affected, skipping macOS tests"
|
||||
fi
|
||||
|
||||
- name: Restore Homebrew packages
|
||||
if: steps.check-changes.outputs.has_changes == 'true'
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
/opt/homebrew
|
||||
/usr/local/Homebrew
|
||||
~/Library/Caches/Homebrew
|
||||
key: nrwl-nx-homebrew-packages
|
||||
|
||||
- name: Configure Detox Environment, Install applesimutils
|
||||
if: steps.check-changes.outputs.has_changes == 'true'
|
||||
run: |
|
||||
# Ensure Xcode command line tools are installed and configured
|
||||
xcode-select --print-path || sudo xcode-select --reset
|
||||
sudo xcode-select -s /Applications/Xcode.app
|
||||
|
||||
# Install or update applesimutils with error handling
|
||||
if ! brew list applesimutils &>/dev/null; then
|
||||
echo "Installing applesimutils..."
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew tap wix/brew >/dev/null
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install applesimutils >/dev/null || {
|
||||
echo "Failed to install applesimutils, retrying with update..."
|
||||
brew update
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install applesimutils
|
||||
}
|
||||
else
|
||||
echo "Updating applesimutils..."
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew upgrade applesimutils || true
|
||||
fi
|
||||
|
||||
# Verify applesimutils installation
|
||||
applesimutils --version || (echo "applesimutils installation failed" && exit 1)
|
||||
|
||||
# Configure environment for M-series Mac
|
||||
echo "DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer" >> $GITHUB_ENV
|
||||
echo "PLATFORM_NAME=iOS Simulator" >> $GITHUB_ENV
|
||||
|
||||
# Set additional environment variables for better debugging
|
||||
echo "DETOX_DISABLE_TELEMETRY=1" >> $GITHUB_ENV
|
||||
echo "DETOX_LOG_LEVEL=trace" >> $GITHUB_ENV
|
||||
|
||||
# Verify Xcode installation
|
||||
xcodebuild -version
|
||||
|
||||
# List available simulators
|
||||
xcrun simctl list devices available
|
||||
timeout-minutes: 10
|
||||
continue-on-error: false
|
||||
|
||||
- name: Reset iOS Simulators
|
||||
if: steps.check-changes.outputs.has_changes == 'true'
|
||||
id: reset-simulators
|
||||
run: |
|
||||
echo "Resetting iOS Simulators..."
|
||||
|
||||
# Kill simulator processes
|
||||
sudo killall -9 com.apple.CoreSimulator.CoreSimulatorService 2>/dev/null || true
|
||||
killall "Simulator" 2>/dev/null || true
|
||||
killall "iOS Simulator" 2>/dev/null || true
|
||||
|
||||
# Wait for processes to terminate
|
||||
sleep 3
|
||||
|
||||
# Shutdown and erase all simulators (ignore failures)
|
||||
xcrun simctl shutdown all 2>/dev/null || true
|
||||
sleep 5
|
||||
xcrun simctl erase all 2>/dev/null || true
|
||||
|
||||
# If erase failed, try the nuclear option
|
||||
if xcrun simctl list devices | grep -q "Booted" 2>/dev/null; then
|
||||
echo "Standard reset failed, using nuclear option..."
|
||||
rm -rf ~/Library/Developer/CoreSimulator/Devices/* 2>/dev/null || true
|
||||
launchctl remove com.apple.CoreSimulator.CoreSimulatorService 2>/dev/null || true
|
||||
sleep 3
|
||||
fi
|
||||
|
||||
# Clean up additional directories
|
||||
rm -rf ~/Library/Developer/CoreSimulator/Caches/* 2>/dev/null || true
|
||||
rm -rf ~/Library/Logs/CoreSimulator/* 2>/dev/null || true
|
||||
rm -rf ~/Library/Developer/Xcode/DerivedData/* 2>/dev/null || true
|
||||
|
||||
echo "Simulator reset completed"
|
||||
timeout-minutes: 5
|
||||
continue-on-error: true
|
||||
|
||||
- name: Verify Simulator Reset
|
||||
if: steps.check-changes.outputs.has_changes == 'true' && steps.reset-simulators.outcome == 'success'
|
||||
run: |
|
||||
# Verify CoreSimulator service restarted
|
||||
pgrep -fl "CoreSimulator" || (echo "CoreSimulator service not running" && exit 1)
|
||||
|
||||
# Check simulator list is clean
|
||||
xcrun simctl list devices
|
||||
|
||||
# Verify simulator runtime paths exist and are writable
|
||||
test -d ~/Library/Developer/CoreSimulator/Devices || (echo "Simulator devices directory missing" && exit 1)
|
||||
touch ~/Library/Developer/CoreSimulator/Devices/test || (echo "Simulator devices directory not writable" && exit 1)
|
||||
rm ~/Library/Developer/CoreSimulator/Devices/test
|
||||
timeout-minutes: 5
|
||||
|
||||
- name: Diagnose Simulator Reset Failure
|
||||
if: steps.check-changes.outputs.has_changes == 'true' && steps.reset-simulators.outcome == 'failure'
|
||||
run: |
|
||||
echo "Simulator reset failed. Collecting diagnostic information..."
|
||||
xcrun simctl list
|
||||
echo "Checking simulator logs..."
|
||||
ls -la ~/Library/Logs/CoreSimulator/ || echo "No simulator logs found"
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew tap wix/brew >/dev/null
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install applesimutils >/dev/null
|
||||
xcrun simctl shutdown all && xcrun simctl erase all
|
||||
timeout-minutes: 20
|
||||
|
||||
- name: Save Homebrew Cache
|
||||
if: steps.check-changes.outputs.has_changes == 'true'
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
/opt/homebrew
|
||||
/usr/local/Homebrew
|
||||
~/Library/Caches/Homebrew
|
||||
key: nrwl-nx-homebrew-packages
|
||||
|
||||
- name: Get pnpm store directory
|
||||
if: steps.check-changes.outputs.has_changes == 'true'
|
||||
id: pnpm-cache-macos
|
||||
run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache pnpm store
|
||||
if: steps.check-changes.outputs.has_changes == 'true'
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache-macos.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
version: 9.8.0
|
||||
run_install: false
|
||||
|
||||
- name: Install project dependencies
|
||||
if: steps.check-changes.outputs.has_changes == 'true'
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm playwright install --with-deps
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Set SHAs
|
||||
uses: nrwl/nx-set-shas@v4
|
||||
with:
|
||||
main-branch-name: 'master'
|
||||
|
||||
- name: Run E2E Tests for macOS
|
||||
if: steps.check-changes.outputs.has_changes == 'true'
|
||||
run: |
|
||||
pnpm nx affected -t e2e-macos-local --parallel=1 --base=$NX_BASE --head=$NX_HEAD
|
||||
HAS_CHANGED=$(node ./scripts/check-react-native-changes.js $NX_BASE $NX_HEAD);
|
||||
if $HAS_CHANGED; then
|
||||
pnpm nx affected -t e2e-macos-local --parallel=1 --base=$NX_BASE --head=$NX_HEAD
|
||||
else
|
||||
echo "Skip E2E tests for macOS as there are no changes in React Native projects."
|
||||
fi
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
schedule:
|
||||
- cron: '20 14 * * 6'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
# required to fetch internal or private CodeQL packs
|
||||
packages: read
|
||||
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: actions
|
||||
build-mode: none
|
||||
|
||||
# We would like to test our Java / Kotlin... but its currently failing. We can follow up.
|
||||
# - language: java-kotlin
|
||||
# build-mode: autobuild
|
||||
- language: javascript-typescript
|
||||
build-mode: none
|
||||
- language: rust
|
||||
build-mode: none
|
||||
- language: csharp
|
||||
build-mode: autobuild
|
||||
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
|
||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Language Tooling
|
||||
uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3
|
||||
|
||||
- name: Enable corepack and install pnpm
|
||||
run: |
|
||||
corepack enable
|
||||
corepack prepare --activate
|
||||
|
||||
# Add any setup steps before running the `github/codeql-action/init` action.
|
||||
# This includes steps like installing compilers or runtimes (`actions/setup-node`
|
||||
# or others). This is typically only required for manual builds.
|
||||
# - name: Setup runtime (example)
|
||||
# uses: actions/setup-example@v1
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@15403aac29bd91419968e066cded66bde56b0283 # v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# If the analyze step fails for one of the languages you are analyzing with
|
||||
# "We were unable to automatically build your code", modify the matrix above
|
||||
# to set the build mode to "manual" for that language. Then modify this step
|
||||
# to build your code.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
- if: matrix.build-mode == 'manual'
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||
'languages you are analyzing, replace this with the commands to build' \
|
||||
'your code, for example:'
|
||||
echo ' make bootstrap'
|
||||
echo ' make release'
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@15403aac29bd91419968e066cded66bde56b0283 # v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
@@ -1,111 +0,0 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "**" ]
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
# required to fetch internal or private CodeQL packs
|
||||
packages: read
|
||||
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: actions
|
||||
build-mode: none
|
||||
|
||||
# See comment in @./codeql-master.yml about Java / Kotlin
|
||||
# - language: java-kotlin
|
||||
# build-mode: autobuild
|
||||
- language: javascript-typescript
|
||||
build-mode: none
|
||||
- language: rust
|
||||
build-mode: none
|
||||
- language: csharp
|
||||
build-mode: autobuild
|
||||
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
|
||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Language Tooling
|
||||
uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3
|
||||
|
||||
- name: Enable corepack and install pnpm
|
||||
run: |
|
||||
corepack enable
|
||||
corepack prepare --activate
|
||||
|
||||
# Add any setup steps before running the `github/codeql-action/init` action.
|
||||
# This includes steps like installing compilers or runtimes (`actions/setup-node`
|
||||
# or others). This is typically only required for manual builds.
|
||||
# - name: Setup runtime (example)
|
||||
# uses: actions/setup-example@v1
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@15403aac29bd91419968e066cded66bde56b0283 # v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# If the analyze step fails for one of the languages you are analyzing with
|
||||
# "We were unable to automatically build your code", modify the matrix above
|
||||
# to set the build mode to "manual" for that language. Then modify this step
|
||||
# to build your code.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
- if: matrix.build-mode == 'manual'
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||
'languages you are analyzing, replace this with the commands to build' \
|
||||
'your code, for example:'
|
||||
echo ' make bootstrap'
|
||||
echo ' make release'
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@15403aac29bd91419968e066cded66bde56b0283 # v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
upload: 'never'
|
||||
upload-database: false
|
||||
@@ -1,43 +0,0 @@
|
||||
name: Curate Community Plugins
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# 1st of every month at 9am UTC
|
||||
- cron: '0 9 1 * *'
|
||||
workflow_dispatch: # allow manual trigger
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
curate:
|
||||
if: ${{ github.repository_owner == 'nrwl' }}
|
||||
permissions:
|
||||
contents: write # to push branch
|
||||
pull-requests: write # to create PR
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d # v4.1.0
|
||||
with:
|
||||
version: 10.28.2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: '24'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Assess and prune community plugins
|
||||
run: pnpm exec tsx scripts/documentation/assess-community-plugins.ts --prune
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
+231
-237
@@ -2,7 +2,7 @@ name: E2E matrix
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 5 * * *'
|
||||
- cron: "0 5 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
debug_enabled:
|
||||
@@ -14,65 +14,63 @@ on:
|
||||
env:
|
||||
CYPRESS_CACHE_FOLDER: ${{ github.workspace }}/.cypress
|
||||
|
||||
permissions: {}
|
||||
permissions: { }
|
||||
jobs:
|
||||
preinstall:
|
||||
if: ${{ github.repository_owner == 'nrwl' }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
NODE_VERSION: ${{ matrix.node_version }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
# - windows-latest Windows fails to build gradle wrapper which always runs when we build nx.
|
||||
## https://staging.nx.app/runs/LgD4vxGn8w?utm_source=pull-request&utm_medium=comment
|
||||
- windows-latest
|
||||
node_version:
|
||||
# TODO(v23): remove node 20 - EOL April 2026
|
||||
- 18
|
||||
- 20
|
||||
- 22
|
||||
- 24
|
||||
# - 23
|
||||
exclude:
|
||||
# run just node v24 on macos and windows
|
||||
# run just node v20 on macos and windows
|
||||
- os: macos-latest
|
||||
node_version: 20
|
||||
node_version: 18
|
||||
- os: macos-latest
|
||||
node_version: 22
|
||||
# - os: windows-latest TODO(Jack): Windows fails to build gradle wrapper which always runs when we build nx. Re-enable when we fix this.
|
||||
# node_version: 20
|
||||
# - os: windows-latest TODO (Jack): Windows fails to build gradle wrapper which always runs when we build nx. Re-enable when we fix this.
|
||||
# node_version: 22
|
||||
# - os: macos-latest
|
||||
# node_version: 23
|
||||
- os: windows-latest
|
||||
node_version: 18
|
||||
- os: windows-latest
|
||||
node_version: 22
|
||||
# - os: windows-latest
|
||||
# node_version: 23
|
||||
|
||||
name: Cache install (${{ matrix.os }}, node v${{ matrix.node_version }})
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
filter: tree:0
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup dev tools with mise
|
||||
uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3
|
||||
|
||||
- name: Enable corepack and install pnpm
|
||||
run: |
|
||||
npm install -g corepack@latest
|
||||
corepack enable
|
||||
corepack prepare --activate
|
||||
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
run: echo "path=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.path }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
version: 9.8.0
|
||||
run_install: false
|
||||
|
||||
- name: Set node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Cache node_modules
|
||||
id: cache-modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
lookup-only: true
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-modules-${{ matrix.node_version }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: Ensure Python setuptools Installed on Macos
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
@@ -80,9 +78,8 @@ jobs:
|
||||
run: brew install python-setuptools
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm playwright install --with-deps
|
||||
if: steps.cache-modules.outputs.cache-hit != 'true'
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Homebrew cache directory path
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
@@ -91,7 +88,7 @@ jobs:
|
||||
|
||||
- name: Cache Homebrew
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
lookup-only: true
|
||||
path: ${{ steps.homebrew-cache-dir-path.outputs.dir }}
|
||||
@@ -101,7 +98,7 @@ jobs:
|
||||
|
||||
- name: Cache Cypress
|
||||
id: cache-cypress
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
lookup-only: true
|
||||
path: '${{ github.workspace }}/.cypress'
|
||||
@@ -114,20 +111,19 @@ jobs:
|
||||
prepare-matrix:
|
||||
name: Prepare matrix combinations
|
||||
if: ${{ github.repository_owner == 'nrwl' }}
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.process-json.outputs.MATRIX }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
filter: tree:0
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Process matrix data
|
||||
id: process-json
|
||||
run: echo "MATRIX=$(npx tsx .github/workflows/nightly/process-matrix.ts | jq -c .)" >> $GITHUB_OUTPUT
|
||||
run:
|
||||
echo "MATRIX=$(npx tsx .github/workflows/nightly/process-matrix.ts | jq -c .)" >> $GITHUB_OUTPUT
|
||||
|
||||
e2e:
|
||||
if: ${{ github.repository_owner == 'nrwl' }}
|
||||
@@ -137,9 +133,6 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 200 # <- cap each job to 200 minutes
|
||||
env:
|
||||
NODE_VERSION: ${{ matrix.node_version }}
|
||||
strategy:
|
||||
matrix: ${{fromJson(needs.prepare-matrix.outputs.matrix)}} # Load matrix from previous job
|
||||
fail-fast: false
|
||||
@@ -147,27 +140,34 @@ jobs:
|
||||
name: ${{ matrix.os_name }}/${{ matrix.package_manager }}/${{ matrix.node_version }} ${{ join(matrix.project) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
filter: tree:0
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Prepare dir for output
|
||||
run: mkdir -p outputs
|
||||
|
||||
- name: Setup dev tools with mise
|
||||
uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9.8.0
|
||||
run_install: false
|
||||
|
||||
- name: Enable corepack and install pnpm
|
||||
run: |
|
||||
npm install -g corepack@latest
|
||||
corepack enable
|
||||
corepack prepare --activate
|
||||
- name: Use Node.js ${{ matrix.node_version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Cache node_modules
|
||||
id: cache-modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-modules-${{ matrix.node_version }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm playwright install --with-deps
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Cleanup
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
@@ -176,7 +176,7 @@ jobs:
|
||||
# https://github.com/actions/virtual-environments/issues/2840
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf '/usr/local/share/boost'
|
||||
sudo rm -rf "/usr/local/share/boost"
|
||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||
sudo apt-get install lsof
|
||||
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
|
||||
@@ -188,7 +188,7 @@ jobs:
|
||||
|
||||
- name: Cache Homebrew
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.homebrew-cache-dir-path.outputs.dir }}
|
||||
key: brew-${{ matrix.node_version }}
|
||||
@@ -197,7 +197,7 @@ jobs:
|
||||
|
||||
- name: Cache Cypress
|
||||
id: cache-cypress
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: '${{ github.workspace }}/.cypress'
|
||||
key: ${{ runner.os }}-cypress
|
||||
@@ -206,116 +206,26 @@ jobs:
|
||||
if: steps.cache-cypress.outputs.cache-hit != 'true'
|
||||
run: npx cypress install
|
||||
|
||||
- name: Configure Detox Environment, Install applesimutils
|
||||
- name: Install applesimutils, reset ios simulators
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
run: |
|
||||
# Ensure Xcode command line tools are installed and configured
|
||||
xcode-select --print-path || sudo xcode-select --reset
|
||||
sudo xcode-select -s /Applications/Xcode.app
|
||||
|
||||
# Install or update applesimutils with error handling
|
||||
if ! brew list applesimutils &>/dev/null; then
|
||||
echo 'Installing applesimutils...'
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew tap wix/brew >/dev/null
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install applesimutils >/dev/null || {
|
||||
echo 'Failed to install applesimutils, retrying with update...'
|
||||
brew update
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install applesimutils
|
||||
}
|
||||
else
|
||||
echo 'Updating applesimutils...'
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew upgrade applesimutils || true
|
||||
fi
|
||||
|
||||
# Verify applesimutils installation
|
||||
applesimutils --version || (echo 'applesimutils installation failed' && exit 1)
|
||||
|
||||
# Configure environment for M-series Mac
|
||||
echo 'DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer' >> $GITHUB_ENV
|
||||
echo 'PLATFORM_NAME=iOS Simulator' >> $GITHUB_ENV
|
||||
|
||||
# Set additional environment variables for better debugging
|
||||
echo 'DETOX_DISABLE_TELEMETRY=1' >> $GITHUB_ENV
|
||||
echo 'DETOX_LOG_LEVEL=trace' >> $GITHUB_ENV
|
||||
|
||||
# Verify Xcode installation
|
||||
xcodebuild -version
|
||||
|
||||
timeout-minutes: 10
|
||||
continue-on-error: false
|
||||
|
||||
- name: Reset iOS Simulators
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
id: reset-simulators
|
||||
run: |
|
||||
echo 'Resetting iOS Simulators...'
|
||||
|
||||
# Kill simulator processes
|
||||
sudo killall -9 com.apple.CoreSimulator.CoreSimulatorService 2>/dev/null || true
|
||||
killall 'Simulator' 2>/dev/null || true
|
||||
killall 'iOS Simulator' 2>/dev/null || true
|
||||
|
||||
# Wait for processes to terminate
|
||||
sleep 3
|
||||
|
||||
# Shutdown and erase all simulators (ignore failures)
|
||||
xcrun simctl shutdown all 2>/dev/null || true
|
||||
sleep 5
|
||||
xcrun simctl erase all 2>/dev/null || true
|
||||
|
||||
# If erase failed, try the nuclear option
|
||||
if xcrun simctl list devices | grep -q 'Booted' 2>/dev/null; then
|
||||
echo 'Standard reset failed, using nuclear option...'
|
||||
rm -rf ~/Library/Developer/CoreSimulator/Devices/* 2>/dev/null || true
|
||||
launchctl remove com.apple.CoreSimulator.CoreSimulatorService 2>/dev/null || true
|
||||
sleep 3
|
||||
fi
|
||||
|
||||
# Clean up additional directories
|
||||
rm -rf ~/Library/Developer/CoreSimulator/Caches/* 2>/dev/null || true
|
||||
rm -rf ~/Library/Logs/CoreSimulator/* 2>/dev/null || true
|
||||
rm -rf ~/Library/Developer/Xcode/DerivedData/* 2>/dev/null || true
|
||||
|
||||
echo 'Simulator reset completed'
|
||||
timeout-minutes: 5
|
||||
continue-on-error: true
|
||||
|
||||
- name: Verify Simulator Reset
|
||||
if: ${{ matrix.os == 'macos-latest' && steps.reset-simulators.outcome == 'success' }}
|
||||
run: |
|
||||
# Verify CoreSimulator service restarted
|
||||
pgrep -fl 'CoreSimulator' || (echo 'CoreSimulator service not running' && exit 1)
|
||||
|
||||
# Verify simulator runtime paths exist and are writable
|
||||
test -d ~/Library/Developer/CoreSimulator/Devices || (echo 'Simulator devices directory missing' && exit 1)
|
||||
touch ~/Library/Developer/CoreSimulator/Devices/test || (echo 'Simulator devices directory not writable' && exit 1)
|
||||
rm ~/Library/Developer/CoreSimulator/Devices/test
|
||||
timeout-minutes: 5
|
||||
|
||||
- name: Diagnose Simulator Reset Failure
|
||||
if: ${{ matrix.os == 'macos-latest' && steps.reset-simulators.outcome == 'failure' }}
|
||||
run: |
|
||||
echo 'Simulator reset failed. Collecting diagnostic information...'
|
||||
xcrun simctl list
|
||||
echo 'Checking simulator logs...'
|
||||
ls -la ~/Library/Logs/CoreSimulator/ || echo 'No simulator logs found'
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew tap wix/brew >/dev/null
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install applesimutils >/dev/null
|
||||
xcrun simctl shutdown all && xcrun simctl erase all
|
||||
|
||||
- name: Configure git metadata (needed for lerna smoke tests)
|
||||
if: ${{ (matrix.os != 'macos-latest') || (matrix.os == 'macos-latest' && steps.reset-simulators.outcome == 'success') }}
|
||||
run: |
|
||||
git config --global user.email test@test.com
|
||||
git config --global user.name 'Test Test'
|
||||
git config --global user.name "Test Test"
|
||||
|
||||
- name: Set starting timestamp
|
||||
if: ${{ (matrix.os != 'macos-latest') || (matrix.os == 'macos-latest' && steps.reset-simulators.outcome == 'success') }}
|
||||
id: before-e2e
|
||||
shell: bash
|
||||
run: |
|
||||
echo "timestamp=$(date +%s)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Run e2e tests with pnpm (Linux/Windows)
|
||||
id: e2e-run-pnpm
|
||||
if: ${{ matrix.os != 'macos-latest' }}
|
||||
- name: Run e2e tests
|
||||
id: e2e-run
|
||||
run: pnpm nx run ${{ matrix.project }}:e2e-local
|
||||
shell: bash
|
||||
timeout-minutes: ${{ matrix.os_timeout }}
|
||||
@@ -332,33 +242,6 @@ jobs:
|
||||
SELECTED_PM: ${{ matrix.package_manager }}
|
||||
npm_config_registry: http://localhost:4872
|
||||
YARN_REGISTRY: http://localhost:4872
|
||||
CI: true
|
||||
|
||||
- name: Run e2e tests with npm (macOS)
|
||||
id: e2e-run-npm
|
||||
if: ${{ matrix.os == 'macos-latest' && steps.reset-simulators.outcome == 'success' }}
|
||||
run: |
|
||||
# Run the tests
|
||||
if [[ '${{ matrix.project }}' == 'e2e-detox' ]] || [[ '${{ matrix.project }}' == 'e2e-react-native' ]] || [[ '${{ matrix.project }}' == 'e2e-expo' ]]; then
|
||||
NX_E2E_VERBOSE_DEBUG=1 pnpm nx run ${{ matrix.project }}:e2e-macos-local
|
||||
else
|
||||
NX_E2E_VERBOSE_DEBUG=1 pnpm nx run ${{ matrix.project }}:e2e-local
|
||||
fi
|
||||
|
||||
env:
|
||||
NX_E2E_CI_CACHE_KEY: e2e-gha-${{ matrix.os }}-${{ matrix.node_version }}-${{ matrix.package_manager }}
|
||||
NX_PERF_LOGGING: 'false'
|
||||
NX_CI_EXECUTION_ENV: 'macos'
|
||||
NX_E2E_VERBOSE_LOGGING: 'true'
|
||||
NX_NATIVE_LOGGING: 'false'
|
||||
NX_E2E_RUN_E2E: 'true'
|
||||
NX_E2E_SKIP_CLEANUP: 'true'
|
||||
NODE_OPTIONS: --max_old_space_size=8192
|
||||
SELECTED_PM: 'npm'
|
||||
npm_config_registry: http://localhost:4872
|
||||
YARN_REGISTRY: http://localhost:4872
|
||||
DEVELOPER_DIR: '/Applications/Xcode.app/Contents/Developer'
|
||||
CI: true
|
||||
|
||||
- name: Save matrix config in file
|
||||
if: ${{ always() }}
|
||||
@@ -368,17 +251,13 @@ jobs:
|
||||
before=${{ steps.before-e2e.outputs.timestamp }}
|
||||
now=$(date +%s)
|
||||
delta=$(($now - $before))
|
||||
|
||||
# Determine the outcome based on which step ran
|
||||
outcome='${{ matrix.os == 'macos-latest' && steps.e2e-run-npm.outcome || steps.e2e-run-pnpm.outcome }}'
|
||||
|
||||
matrix=$((
|
||||
echo '${{ toJSON(matrix) }}'
|
||||
) | jq --argjson delta $delta -c '. + { "status": "'"$outcome"'", "duration": $delta }')
|
||||
) | jq --argjson delta $delta -c '. + { "status": "${{ steps.e2e-run.outcome}}", "duration": $delta }')
|
||||
echo "$matrix" > 'outputs/matrix.json'
|
||||
|
||||
- name: Upload matrix config
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: ${{ matrix.os_name}}-${{ matrix.node_version}}-${{ matrix.package_manager}}-${{ matrix.project }}
|
||||
@@ -388,81 +267,198 @@ jobs:
|
||||
|
||||
- name: Setup tmate session
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled && failure() }}
|
||||
uses: mxschmitt/action-tmate@1fb8b1023602bf1fd0e2994d7f1e93015cb5bbec # v3.22
|
||||
uses: mxschmitt/action-tmate@v3.8
|
||||
timeout-minutes: 15
|
||||
with:
|
||||
sudo: ${{ matrix.os != 'windows-latest' }} # disable sudo for windows debugging
|
||||
|
||||
process-result:
|
||||
if: ${{ always() && github.repository_owner == 'nrwl' && github.event_name != 'workflow_dispatch' }}
|
||||
if: ${{ always() && github.repository_owner == 'nrwl' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: e2e
|
||||
timeout-minutes: 15
|
||||
outputs:
|
||||
message: ${{ steps.process-json.outputs.slack_message }}
|
||||
proj_duration: ${{ steps.process-json.outputs.slack_proj_duration }}
|
||||
pm_duration: ${{ steps.process-json.outputs.slack_pm_duration }}
|
||||
codeowners: ${{ steps.process-json.outputs.codeowners }}
|
||||
has_golden_failures: ${{ steps.process-json.outputs.has_golden_failures }}
|
||||
message: ${{ steps.process-json.outputs.SLACK_MESSAGE }}
|
||||
proj-duration: ${{ steps.process-json.outputs.SLACK_PROJ_DURATION }}
|
||||
pm-duration: ${{ steps.process-json.outputs.SLACK_PM_DURATION }}
|
||||
codeowners: ${{ steps.process-json.outputs.CODEOWNERS }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
filter: tree:0
|
||||
|
||||
- name: Prepare dir for output
|
||||
run: mkdir -p outputs
|
||||
|
||||
- name: Load outputs
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: outputs
|
||||
|
||||
- name: Join and stringify matrix configs
|
||||
id: combine-json
|
||||
run: |
|
||||
combined=$(jq -sc . outputs/*/matrix.json)
|
||||
combined=$((jq -s . outputs/*/matrix.json) | jq tostring)
|
||||
echo "combined=$combined" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Process results and collect failure details
|
||||
- name: Make slack outputs
|
||||
id: process-json
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
echo '${{ steps.combine-json.outputs.combined }}' | npx tsx .github/workflows/nightly/process-result.ts
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
script: |
|
||||
const combined = JSON.parse(${{ steps.combine-json.outputs.combined }});
|
||||
const failedProjects = combined.filter(c => c.status === 'failure').sort((a, b) => a.project.localeCompare(b.project));
|
||||
|
||||
// codeowners
|
||||
const codeowners = new Set();
|
||||
failedProjects.forEach(c => {
|
||||
codeowners.add(c.codeowners);
|
||||
});
|
||||
core.setOutput('CODEOWNERS', Array.from(codeowners).join(','));
|
||||
|
||||
function trimSpace(res) {
|
||||
return res.split('\n').map((l) => l.trim()).join('\n');
|
||||
}
|
||||
|
||||
// failed message
|
||||
let lastProject;
|
||||
let result = `
|
||||
\`\`\`
|
||||
| Failed project | PM | OS | Node |
|
||||
|--------------------------------|------|-------|------|`;
|
||||
failedProjects.forEach(matrix => {
|
||||
const project = matrix.project !== lastProject ? matrix.project : '...';
|
||||
result += `\n| ${project.padEnd(30)} | ${matrix.package_manager.padEnd(4)} | ${matrix.os_name} | v${matrix.node_version} |`
|
||||
lastProject = matrix.project;
|
||||
});
|
||||
result += `\`\`\``;
|
||||
core.setOutput('SLACK_MESSAGE', trimSpace(result));
|
||||
console.log(trimSpace(result));
|
||||
|
||||
function humanizeDuration(num) {
|
||||
let res = '';
|
||||
const hours = Math.floor(num / 3600);
|
||||
if (hours) {
|
||||
res += `${hours}h `;
|
||||
}
|
||||
const mins = Math.floor((num % 3600) / 60);
|
||||
if (mins) {
|
||||
res += `${mins}m `;
|
||||
}
|
||||
const sec = num % 60;
|
||||
if (sec) {
|
||||
res += `${sec}s`
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// duration message
|
||||
const timeReport = {};
|
||||
const pmReport = {
|
||||
npm: 0,
|
||||
yarn: 0,
|
||||
pnpm: 0
|
||||
};
|
||||
const macosProjects = ['e2e-detox', 'e2e-expo', 'e2e-react-native'];
|
||||
combined.forEach((matrix) => {
|
||||
if (matrix.os_name === 'Linux' && matrix.node_version === 20) {
|
||||
pmReport[matrix.package_manager] += matrix.duration;
|
||||
}
|
||||
if (matrix.os_name === 'Linux' || macosProjects.includes(matrix.project)) {
|
||||
if (timeReport[matrix.project]) {
|
||||
if (matrix.duration > timeReport[matrix.project].max) {
|
||||
timeReport[matrix.project].max = matrix.duration;
|
||||
timeReport[
|
||||
matrix.project
|
||||
].maxEnv = `${matrix.os_name}, ${matrix.package_manager}`;
|
||||
}
|
||||
if (matrix.duration < timeReport[matrix.project].min) {
|
||||
timeReport[matrix.project].min = matrix.duration;
|
||||
timeReport[
|
||||
matrix.project
|
||||
].minEnv = `${matrix.os_name}, ${matrix.package_manager}`;
|
||||
}
|
||||
} else {
|
||||
timeReport[matrix.project] = {
|
||||
min: matrix.duration,
|
||||
max: matrix.duration,
|
||||
minEnv: `${matrix.os_name}, ${matrix.package_manager}`,
|
||||
maxEnv: `${matrix.os_name}, ${matrix.package_manager}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// project time report
|
||||
let resultPkg = `
|
||||
\`\`\`
|
||||
| Project | Time |
|
||||
|--------------------------------|---------------------------|`;
|
||||
function mapProjectTime(proj, section) {
|
||||
let res = '';
|
||||
res += `${humanizeDuration(timeReport[proj][section])}`;
|
||||
res += ` (${timeReport[proj][section + 'Env']})`
|
||||
return res;
|
||||
}
|
||||
function durationIcon(proj, section) {
|
||||
if (timeReport[proj][section] < 12 * 60) {
|
||||
return `${section} ✅`;
|
||||
}
|
||||
if (timeReport[proj][section] < 15 * 60) {
|
||||
return `${section} ❗`;
|
||||
}
|
||||
return `${section} ❌`;
|
||||
}
|
||||
Object.keys(timeReport).forEach(proj => {
|
||||
resultPkg += `\n| ${proj.padEnd(30)} | |`;
|
||||
resultPkg += `\n| ${durationIcon(proj, 'min').padStart(29)} | ${mapProjectTime(proj, 'min').padEnd(25)} |`;
|
||||
resultPkg += `\n| ${durationIcon(proj, 'max').padStart(29)} | ${mapProjectTime(proj, 'max').padEnd(25)} |`;
|
||||
});
|
||||
resultPkg += `\`\`\``;
|
||||
core.setOutput('SLACK_PROJ_DURATION', trimSpace(resultPkg));
|
||||
|
||||
// Print project duration report inline to allow reviewing on manual runs (when no slack message will be sent)
|
||||
console.log(trimSpace(resultPkg));
|
||||
|
||||
let resultPm = `
|
||||
\`\`\`
|
||||
| PM | Total time |
|
||||
|------|-------------|`;
|
||||
Object.keys(pmReport).forEach(pm => {
|
||||
resultPm += `\n| ${pm.padEnd(4)} | ${humanizeDuration(pmReport[pm]).padEnd(11)} |`
|
||||
});
|
||||
resultPm += `\`\`\``;
|
||||
core.setOutput('SLACK_PM_DURATION', trimSpace(resultPm));
|
||||
|
||||
// Print package manager duration report inline to allow reviewing on manual runs (when no slack message will be sent)
|
||||
console.log(trimSpace(resultPm));
|
||||
|
||||
report-failure:
|
||||
if: ${{ always() && needs.process-result.outputs.has_golden_failures == 'true' && github.repository_owner == 'nrwl' && github.event_name != 'workflow_dispatch' }}
|
||||
if: ${{ failure() && github.repository_owner == 'nrwl' && github.event_name != 'workflow_dispatch' }}
|
||||
needs: process-result
|
||||
runs-on: ubuntu-latest
|
||||
name: Report failure
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Send notification
|
||||
uses: ravsamhq/notify-slack-action@be814b201e233b2dc673608aa46e5447c8ab13f2 # v11
|
||||
uses: ravsamhq/notify-slack-action@v2
|
||||
with:
|
||||
status: 'failure'
|
||||
message_format: '${{ needs.process-result.outputs.message }}'
|
||||
notification_title: 'Golden Test Failure'
|
||||
message_format: '{emoji} Workflow has {status_message} ${{ needs.process-result.outputs.message }}'
|
||||
notification_title: '{workflow}'
|
||||
footer: '<{run_url}|View Run> / Last commit <{commit_url}|{commit_sha}>'
|
||||
mention_groups: ${{ needs.process-result.outputs.codeowners }}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }}
|
||||
|
||||
report-success:
|
||||
if: ${{ always() && needs.process-result.outputs.has_golden_failures == 'false' && github.repository_owner == 'nrwl' && github.event_name != 'workflow_dispatch' }}
|
||||
needs: process-result
|
||||
if: ${{ success() && github.repository_owner == 'nrwl' && github.event_name != 'workflow_dispatch' }}
|
||||
needs: e2e
|
||||
runs-on: ubuntu-latest
|
||||
name: Report status
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Send notification
|
||||
uses: ravsamhq/notify-slack-action@be814b201e233b2dc673608aa46e5447c8ab13f2 # v11
|
||||
uses: ravsamhq/notify-slack-action@v2
|
||||
with:
|
||||
status: 'success'
|
||||
message_format: '${{ needs.process-result.outputs.message }}'
|
||||
notification_title: '✅ Golden Tests: All Passed!'
|
||||
status: ${{ needs.e2e.result }}
|
||||
message_format: '{emoji} Workflow has {status_message}'
|
||||
notification_title: '{workflow}'
|
||||
footer: '<{run_url}|View Run> / Last commit <{commit_url}|{commit_sha}>'
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }}
|
||||
@@ -471,15 +467,14 @@ jobs:
|
||||
if: ${{ always() && github.repository_owner == 'nrwl' && github.event_name != 'workflow_dispatch' }}
|
||||
needs: process-result
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
name: Report duration per package manager
|
||||
steps:
|
||||
- name: Send notification
|
||||
uses: ravsamhq/notify-slack-action@be814b201e233b2dc673608aa46e5447c8ab13f2 # v11
|
||||
uses: ravsamhq/notify-slack-action@v2
|
||||
with:
|
||||
status: 'skipped'
|
||||
message_format: '${{ needs.process-result.outputs.pm_duration }}'
|
||||
notification_title: '⌛ Total duration per package manager (ubuntu only)'
|
||||
message_format: '${{ needs.process-result.outputs.pm-duration }}'
|
||||
notification_title: 'Total duration per package manager (ubuntu only)'
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }}
|
||||
|
||||
@@ -487,14 +482,13 @@ jobs:
|
||||
if: ${{ always() && github.repository_owner == 'nrwl' && github.event_name != 'workflow_dispatch' }}
|
||||
needs: process-result
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
name: Report duration per project
|
||||
name: Report duration per package manager
|
||||
steps:
|
||||
- name: Send notification
|
||||
uses: ravsamhq/notify-slack-action@be814b201e233b2dc673608aa46e5447c8ab13f2 # v11
|
||||
uses: ravsamhq/notify-slack-action@v2
|
||||
with:
|
||||
status: 'skipped'
|
||||
message_format: '${{ needs.process-result.outputs.proj_duration }}'
|
||||
notification_title: '⌛ E2E Project duration stats'
|
||||
message_format: '${{ needs.process-result.outputs.proj-duration }}'
|
||||
notification_title: 'E2E Project duration stats'
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }}
|
||||
|
||||
@@ -3,30 +3,29 @@ name: Generate embeddings
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 5 * * 0,4" # sunday, thursday 5AM
|
||||
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
cache-and-install:
|
||||
if: github.repository == 'nrwl/nx'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: ['24']
|
||||
node-version: [18]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '24'
|
||||
package-manager-cache: false
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d # v4.1.0
|
||||
uses: pnpm/action-setup@v4
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 10.28.2
|
||||
version: 9.8.0
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
@@ -36,7 +35,7 @@ jobs:
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
@@ -46,11 +45,8 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
|
||||
- name: Build docs
|
||||
run: npx nx build astro-docs
|
||||
|
||||
- name: Run embeddings script
|
||||
run: node --import tsx tools/documentation/create-embeddings/src/main.mts --mode=astro
|
||||
run: pnpm exec nx run tools-documentation-create-embeddings:run-node
|
||||
env:
|
||||
NX_NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NX_NEXT_PUBLIC_SUPABASE_URL }}
|
||||
NX_SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.NX_SUPABASE_SERVICE_ROLE_KEY }}
|
||||
|
||||
@@ -16,21 +16,21 @@ jobs:
|
||||
name: Report status
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d # v4.1.0
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.28.2
|
||||
version: 9.8.0
|
||||
|
||||
- name: Use Node.js ${{ matrix.node_version }}
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '24'
|
||||
node-version: '18'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Cache node_modules
|
||||
id: cache-modules
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
lookup-only: true
|
||||
path: '**/node_modules'
|
||||
@@ -41,12 +41,11 @@ jobs:
|
||||
|
||||
- name: Download artifact
|
||||
id: download-artifact
|
||||
uses: dawidd6/action-download-artifact@268677152d06ba59fcec7a7f0b5d961b6ccd7e1e # v2 # Needed since we are downloading artifact from a different workflow run, official actions/download-artifact doesn't support this.
|
||||
uses: dawidd6/action-download-artifact@v2 # Needed since we are downloading artifact from a different workflow run, official actions/download-artifact doesn't support this.
|
||||
with:
|
||||
name: cached-issue-data
|
||||
path: ${{ github.workspace }}/scripts/issues-scraper/cached
|
||||
search_artifacts: true
|
||||
allow_forks: false
|
||||
continue-on-error: true
|
||||
|
||||
- name: Collect Issue Data
|
||||
@@ -55,15 +54,16 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cached-issue-data
|
||||
path: ./scripts/issues-scraper/cached/data.json
|
||||
|
||||
- name: Send GitHub Action trigger data to Slack workflow
|
||||
id: slack
|
||||
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1
|
||||
uses: slackapi/slack-github-action@v1.23.0
|
||||
with:
|
||||
webhook: ${{ secrets.SLACK_ISSUES_REPORT_URL }}
|
||||
webhook-type: incoming-webhook
|
||||
payload: ${{ steps.collect.outputs.SLACK_MESSAGE }}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_ISSUES_REPORT_URL }}
|
||||
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
|
||||
|
||||
@@ -17,10 +17,9 @@ jobs:
|
||||
if: ${{ github.repository_owner == 'nrwl' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@6548363a2d763e3a4a3a0dc04ca4a10481d8e536 # v6.0.0
|
||||
- uses: dessant/lock-threads@v4
|
||||
id: lockthreads
|
||||
with:
|
||||
process-only: 'issues, prs'
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: "30" # Lock issues after 30 days of being closed
|
||||
pr-inactive-days: "5" # Lock closed PRs after 5 days. This ensures that issues that stem from a PR are opened as issues, rather than comments on the recently merged PR.
|
||||
|
||||
@@ -1,674 +0,0 @@
|
||||
import { exec } from 'child_process';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
const MAX_CONCURRENCY = 8;
|
||||
|
||||
interface MatrixResult {
|
||||
project: string;
|
||||
codeowners: string;
|
||||
node_version: number | string;
|
||||
package_manager: string;
|
||||
os: string;
|
||||
os_name: string;
|
||||
os_timeout: number;
|
||||
is_golden?: boolean;
|
||||
status: 'success' | 'failure' | 'cancelled';
|
||||
duration: number;
|
||||
}
|
||||
|
||||
interface Streak {
|
||||
consecutive_failures: number;
|
||||
failing_since: string | null;
|
||||
last_passing: string | null;
|
||||
}
|
||||
|
||||
interface HistoryEntry {
|
||||
date: string;
|
||||
failed: string[];
|
||||
}
|
||||
|
||||
interface ErrorDate {
|
||||
testFile: string;
|
||||
startDate: string;
|
||||
days: number;
|
||||
}
|
||||
|
||||
const REPO = process.env.GITHUB_REPOSITORY || 'nrwl/nx';
|
||||
const RUN_ID = process.env.GITHUB_RUN_ID || '0';
|
||||
|
||||
function gh(args: string): string {
|
||||
try {
|
||||
return execSync(`gh ${args}`, {
|
||||
encoding: 'utf-8',
|
||||
timeout: 60_000,
|
||||
maxBuffer: 10 * 1024 * 1024,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
}).trim();
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function ghAsync(args: string): Promise<string> {
|
||||
return new Promise((resolve) => {
|
||||
exec(
|
||||
`gh ${args}`,
|
||||
{ encoding: 'utf-8', timeout: 60_000, maxBuffer: 10 * 1024 * 1024 },
|
||||
(err, stdout) => resolve(err ? '' : (stdout || '').trim())
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async function ghParallel<T>(
|
||||
items: T[],
|
||||
fn: (item: T) => string,
|
||||
concurrency = MAX_CONCURRENCY
|
||||
): Promise<Map<T, string>> {
|
||||
const results = new Map<T, string>();
|
||||
const queue = [...items];
|
||||
|
||||
async function worker() {
|
||||
while (queue.length > 0) {
|
||||
const item = queue.shift()!;
|
||||
results.set(item, await ghAsync(fn(item)));
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
Array.from({ length: Math.min(concurrency, items.length) }, () => worker())
|
||||
);
|
||||
return results;
|
||||
}
|
||||
|
||||
function extractJestBlocks(raw: string): string {
|
||||
const lines = raw
|
||||
.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /gm, '')
|
||||
.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '')
|
||||
.split('\n');
|
||||
|
||||
const blocks: string[] = [];
|
||||
let capturing = false;
|
||||
for (const line of lines) {
|
||||
if (line.startsWith(' FAIL ')) capturing = true;
|
||||
if (capturing) blocks.push(line);
|
||||
if (line.startsWith('Ran all test suites')) capturing = false;
|
||||
}
|
||||
return blocks.slice(0, 50).join('\n');
|
||||
}
|
||||
|
||||
function extractTestFiles(block: string): string[] {
|
||||
const matches = block.match(/FAIL\s+\S+\s+(src\/[^\s]+\.test\.ts)/g) || [];
|
||||
return [...new Set(matches.map((m) => m.replace(/FAIL\s+\S+\s+/, '')))];
|
||||
}
|
||||
|
||||
// Extract a normalized error signature for a test file from a Jest block.
|
||||
// Used to distinguish different root causes for the same test file across runs.
|
||||
function extractErrorSignature(block: string, testFile: string): string {
|
||||
const lines = block.split('\n');
|
||||
let afterBullet = false;
|
||||
let inFile = false;
|
||||
for (const l of lines) {
|
||||
if (l.includes('FAIL') && l.includes(testFile)) { inFile = true; continue; }
|
||||
if (inFile && /●/.test(l)) { afterBullet = true; continue; }
|
||||
if (!inFile || !afterBullet) continue;
|
||||
const trimmed = l.trim();
|
||||
if (!trimmed) continue;
|
||||
// Skip generic "Command failed" and warnings — find the actual error
|
||||
if (/^Command failed:|^warning /i.test(trimmed)) continue;
|
||||
// Normalize dynamic parts
|
||||
return trimmed
|
||||
.replace(/\/tmp\/[^\s]+/g, '<tmpdir>')
|
||||
.replace(/\/Users\/[^\s]+/g, '<path>')
|
||||
.replace(/\/home\/[^\s]+/g, '<path>')
|
||||
.replace(/[a-z]+\d{5,}/gi, '<id>')
|
||||
.replace(/\d{4}-\d{2}-\d{2}T[\d:._Z-]+/g, '<ts>')
|
||||
.replace(/\d+\.\d+\.\d+/g, '<ver>')
|
||||
.trim();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// Extract signatures for all test files in a block
|
||||
function extractSignatures(
|
||||
block: string,
|
||||
testFiles: string[]
|
||||
): Map<string, string> {
|
||||
const sigs = new Map<string, string>();
|
||||
for (const tf of testFiles) {
|
||||
sigs.set(tf, extractErrorSignature(block, tf));
|
||||
}
|
||||
return sigs;
|
||||
}
|
||||
|
||||
function extractBlockForFile(fullBlock: string, testFile: string): string {
|
||||
const lines = fullBlock.split('\n');
|
||||
const result: string[] = [];
|
||||
let capturing = false;
|
||||
for (const line of lines) {
|
||||
if (line.includes('FAIL') && line.includes(testFile)) capturing = true;
|
||||
else if (capturing && line.match(/^ FAIL /)) capturing = false;
|
||||
if (capturing) result.push(line);
|
||||
}
|
||||
return result.slice(0, 20).join('\n');
|
||||
}
|
||||
|
||||
export interface JobLink {
|
||||
combo: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface FailureDetailsResult {
|
||||
report: string;
|
||||
goldenJobLinks: Map<string, JobLink[]>; // project -> [{combo, url}]
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects detailed failure information for golden projects.
|
||||
* Called by process-result.ts when golden failures exist.
|
||||
* Returns Slack mrkdwn report + job links for the summary section.
|
||||
*/
|
||||
export async function collectFailureDetails(
|
||||
combined: MatrixResult[],
|
||||
failedGoldenProjectNames: string[]
|
||||
): Promise<FailureDetailsResult> {
|
||||
const projectNames = failedGoldenProjectNames;
|
||||
if (projectNames.length === 0) {
|
||||
return { report: '', goldenJobLinks: new Map() };
|
||||
}
|
||||
|
||||
// Group failures by project for combo info
|
||||
const failuresByProject = new Map<string, MatrixResult[]>();
|
||||
for (const r of combined) {
|
||||
if (r.is_golden && (r.status === 'failure' || r.status === 'cancelled')) {
|
||||
if (!failuresByProject.has(r.project))
|
||||
failuresByProject.set(r.project, []);
|
||||
failuresByProject.get(r.project)!.push(r);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 1: 30-day failure history
|
||||
const histRunsRaw = gh(
|
||||
`run list --workflow=e2e-matrix.yml --repo ${REPO} --limit 40 --json databaseId,createdAt,event --jq '[.[] | select(.event == "schedule" and .databaseId != ${RUN_ID})] | .[0:30]'`
|
||||
);
|
||||
const histRuns: Array<{ databaseId: number; createdAt: string }> =
|
||||
histRunsRaw ? JSON.parse(histRunsRaw) : [];
|
||||
|
||||
const histResults = await ghParallel(
|
||||
histRuns.map((r) => r.databaseId),
|
||||
(rid) =>
|
||||
`run view ${rid} --repo ${REPO} --json jobs --jq '[.jobs[] | select(.conclusion == "failure") | .name | split(" ") | last] | unique'`
|
||||
);
|
||||
|
||||
const history: HistoryEntry[] = histRuns.map((run) => {
|
||||
const raw = histResults.get(run.databaseId) || '[]';
|
||||
try {
|
||||
return { date: run.createdAt, failed: JSON.parse(raw) };
|
||||
} catch {
|
||||
return { date: run.createdAt, failed: [] };
|
||||
}
|
||||
});
|
||||
|
||||
// Compute streaks
|
||||
const streaks = new Map<string, Streak>();
|
||||
for (const project of projectNames) {
|
||||
let streak = 0,
|
||||
firstSeen: string | null = null,
|
||||
lastPassing: string | null = null,
|
||||
broken = false;
|
||||
for (const entry of history) {
|
||||
if (broken) break;
|
||||
if (entry.failed.includes(project)) {
|
||||
streak++;
|
||||
firstSeen = entry.date;
|
||||
} else {
|
||||
broken = true;
|
||||
lastPassing = entry.date;
|
||||
}
|
||||
}
|
||||
streaks.set(project, {
|
||||
consecutive_failures: streak,
|
||||
failing_since: firstSeen ? firstSeen.split('T')[0] : null,
|
||||
last_passing: lastPassing ? lastPassing.split('T')[0] : null,
|
||||
});
|
||||
}
|
||||
|
||||
// Step 2: Fetch failure logs (one per OS/PM combo per project)
|
||||
const failedJobsRaw = gh(
|
||||
`run view ${RUN_ID} --repo ${REPO} --json jobs --jq '[.jobs[] | select(.conclusion == "failure") | {id: .databaseId, name: .name, project: (.name | split(" ") | last), combo: (.name | split(" ")[0])}]'`
|
||||
);
|
||||
const failedJobs: Array<{
|
||||
id: number;
|
||||
name: string;
|
||||
project: string;
|
||||
combo: string;
|
||||
}> = failedJobsRaw ? JSON.parse(failedJobsRaw) : [];
|
||||
|
||||
const jobsToFetch: Array<{ id: number; project: string }> = [];
|
||||
for (const project of projectNames) {
|
||||
const seen = new Set<string>();
|
||||
for (const job of failedJobs.filter((j) => j.project === project)) {
|
||||
const key = job.combo.split('/').slice(0, 2).join('/');
|
||||
if (!seen.has(key)) {
|
||||
seen.add(key);
|
||||
jobsToFetch.push({ id: job.id, project: job.project });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const logResults = await ghParallel(
|
||||
jobsToFetch,
|
||||
(job) => `api repos/${REPO}/actions/jobs/${job.id}/logs`
|
||||
);
|
||||
|
||||
// Keep per-combo logs separate AND a merged block per project
|
||||
interface ComboLog {
|
||||
combo: string;
|
||||
block: string;
|
||||
testFiles: string[];
|
||||
signatures: Map<string, string>; // testFile -> error signature
|
||||
}
|
||||
const projectComboLogs = new Map<string, ComboLog[]>();
|
||||
const projectLogs = new Map<string, string>(); // merged block for backwards compat
|
||||
|
||||
for (const [job, raw] of logResults) {
|
||||
if (!raw) continue;
|
||||
const cleaned = raw
|
||||
.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /gm, '')
|
||||
.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
|
||||
const block = extractJestBlocks(raw);
|
||||
const testFiles = extractTestFiles(cleaned);
|
||||
const sigs = extractSignatures(cleaned, testFiles);
|
||||
const combo =
|
||||
failedJobs.find((j) => j.id === job.id)?.combo || 'unknown';
|
||||
|
||||
if (!projectComboLogs.has(job.project))
|
||||
projectComboLogs.set(job.project, []);
|
||||
projectComboLogs.get(job.project)!.push({
|
||||
combo,
|
||||
block,
|
||||
testFiles,
|
||||
signatures: sigs,
|
||||
});
|
||||
|
||||
projectLogs.set(
|
||||
job.project,
|
||||
(projectLogs.get(job.project) || '') + '\n' + block
|
||||
);
|
||||
}
|
||||
|
||||
// Step 3: Build distinct failures per project — each (testFile, signature, combos) is a "failure"
|
||||
interface DistinctFailure {
|
||||
testFile: string;
|
||||
signature: string;
|
||||
combos: string[];
|
||||
block: string; // the Jest block from the first combo that has this signature
|
||||
}
|
||||
|
||||
const projectDistinctFailures = new Map<string, DistinctFailure[]>();
|
||||
for (const project of projectNames) {
|
||||
const comboLogs = projectComboLogs.get(project) || [];
|
||||
const seen = new Map<string, DistinctFailure>(); // "testFile|signature" -> failure
|
||||
|
||||
for (const cl of comboLogs) {
|
||||
for (const tf of cl.testFiles) {
|
||||
const sig = cl.signatures.get(tf) || '';
|
||||
const key = `${tf}|${sig}`;
|
||||
if (seen.has(key)) {
|
||||
seen.get(key)!.combos.push(cl.combo);
|
||||
} else {
|
||||
seen.set(key, {
|
||||
testFile: tf,
|
||||
signature: sig,
|
||||
combos: [cl.combo],
|
||||
block: extractBlockForFile(cl.block, tf),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
projectDistinctFailures.set(project, [...seen.values()]);
|
||||
}
|
||||
|
||||
// Step 3b: Validate each distinct failure against the first-failing run
|
||||
interface FailureValidation {
|
||||
status: 'new' | 'confirmed' | 'different' | 'unknown';
|
||||
startDate?: string;
|
||||
days?: number;
|
||||
}
|
||||
|
||||
// Key: "project|testFile|signature"
|
||||
const failureValidations = new Map<string, FailureValidation>();
|
||||
|
||||
for (const project of projectNames) {
|
||||
const streak = streaks.get(project)!;
|
||||
const failures = projectDistinctFailures.get(project) || [];
|
||||
|
||||
if (streak.consecutive_failures <= 1 || !streak.failing_since) {
|
||||
for (const f of failures) {
|
||||
failureValidations.set(`${project}|${f.testFile}|${f.signature}`, {
|
||||
status: 'new',
|
||||
startDate: streak.failing_since || undefined,
|
||||
days: streak.consecutive_failures || 1,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fetch first-failing run's signatures across all combos
|
||||
const firstRun = histRuns.find(
|
||||
(r) => r.createdAt.split('T')[0] === streak.failing_since
|
||||
);
|
||||
if (!firstRun) {
|
||||
for (const f of failures) {
|
||||
failureValidations.set(`${project}|${f.testFile}|${f.signature}`, {
|
||||
status: 'unknown',
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const firstJobIdsRaw = gh(
|
||||
`run view ${firstRun.databaseId} --repo ${REPO} --json jobs --jq '[.jobs[] | select(.conclusion == "failure" and (.name | split(" ") | last) == "${project}")] | group_by(.name | split("/")[0:2] | join("/")) | map(.[0].databaseId) | .[]'`
|
||||
);
|
||||
const firstJobIds = firstJobIdsRaw
|
||||
.split('\n')
|
||||
.filter((id) => id && id !== 'null');
|
||||
|
||||
// Collect ALL signatures from the first run
|
||||
const firstRunSigs = new Set<string>(); // "testFile|signature"
|
||||
for (const jobId of firstJobIds) {
|
||||
const log = gh(`api repos/${REPO}/actions/jobs/${jobId}/logs`);
|
||||
if (!log) continue;
|
||||
const cleanedLog = log
|
||||
.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /gm, '')
|
||||
.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
|
||||
const files = extractTestFiles(cleanedLog);
|
||||
for (const f of files) {
|
||||
const sig = extractErrorSignature(cleanedLog, f);
|
||||
firstRunSigs.add(`${f}|${sig}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate each current failure
|
||||
for (const f of failures) {
|
||||
const key = `${f.testFile}|${f.signature}`;
|
||||
const fullKey = `${project}|${key}`;
|
||||
if (firstRunSigs.has(key)) {
|
||||
failureValidations.set(fullKey, {
|
||||
status: 'confirmed',
|
||||
startDate: streak.failing_since!,
|
||||
days: streak.consecutive_failures,
|
||||
});
|
||||
} else {
|
||||
failureValidations.set(fullKey, {
|
||||
status: 'different',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Binary search for start date of each "different" failure
|
||||
for (const project of projectNames) {
|
||||
const streak = streaks.get(project)!;
|
||||
const failures = projectDistinctFailures.get(project) || [];
|
||||
const different = failures.filter((f) => {
|
||||
const v = failureValidations.get(
|
||||
`${project}|${f.testFile}|${f.signature}`
|
||||
);
|
||||
return v?.status === 'different';
|
||||
});
|
||||
if (!different.length) continue;
|
||||
|
||||
const projRunIds = histRuns
|
||||
.slice(0, streak.consecutive_failures)
|
||||
.map((r) => r.databaseId);
|
||||
if (projRunIds.length <= 1) continue;
|
||||
|
||||
for (const failure of different) {
|
||||
const targetSig = failure.signature;
|
||||
if (!targetSig) continue;
|
||||
|
||||
function runHasSignature(runId: number): boolean {
|
||||
const jobIdsRaw = gh(
|
||||
`run view ${runId} --repo ${REPO} --json jobs --jq '[.jobs[] | select(.conclusion == "failure" and (.name | split(" ") | last) == "${project}")] | group_by(.name | split("/")[0:2] | join("/")) | map(.[0].databaseId) | .[]'`
|
||||
);
|
||||
for (const jid of jobIdsRaw.split('\n').filter(Boolean)) {
|
||||
const log = gh(`api repos/${REPO}/actions/jobs/${jid}/logs`);
|
||||
if (!log) continue;
|
||||
const cleaned = log
|
||||
.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /gm, '')
|
||||
.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
|
||||
const sig = extractErrorSignature(cleaned, failure.testFile);
|
||||
if (sig === targetSig) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
let low = 0,
|
||||
high = projRunIds.length - 1;
|
||||
|
||||
// Check oldest run
|
||||
const fullKey = `${project}|${failure.testFile}|${failure.signature}`;
|
||||
|
||||
const oldestHas = runHasSignature(projRunIds[high]);
|
||||
if (oldestHas) {
|
||||
const run = histRuns.find((r) => r.databaseId === projRunIds[high]);
|
||||
failureValidations.set(fullKey, {
|
||||
status: 'different',
|
||||
startDate: run?.createdAt.split('T')[0] || 'unknown',
|
||||
days: projRunIds.length,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Binary search
|
||||
while (high - low > 1) {
|
||||
const mid = Math.floor((low + high) / 2);
|
||||
if (runHasSignature(projRunIds[mid])) low = mid;
|
||||
else high = mid;
|
||||
}
|
||||
|
||||
const foundRun = histRuns.find((r) => r.databaseId === projRunIds[low]);
|
||||
failureValidations.set(fullKey, {
|
||||
status: 'different',
|
||||
startDate: foundRun?.createdAt.split('T')[0] || 'unknown',
|
||||
days: low + 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Recent commits
|
||||
let commitCount = 0;
|
||||
if (histRuns[0]?.createdAt) {
|
||||
try {
|
||||
const commits = execSync(
|
||||
`git log origin/master --after="${histRuns[0].createdAt}" --format="%h" --no-merges 2>/dev/null | head -30`,
|
||||
{ encoding: 'utf-8', timeout: 10_000 }
|
||||
).trim();
|
||||
commitCount = commits ? commits.split('\n').length : 0;
|
||||
} catch {
|
||||
/* git not available */
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6: Format report
|
||||
const lines: string[] = ['', '🔍 *Failure Details*', ''];
|
||||
|
||||
const sorted = [...projectNames].sort((a, b) => {
|
||||
const sa = streaks.get(a)?.consecutive_failures || 0;
|
||||
const sb = streaks.get(b)?.consecutive_failures || 0;
|
||||
return (
|
||||
sa - sb ||
|
||||
(failuresByProject.get(b)?.length || 0) -
|
||||
(failuresByProject.get(a)?.length || 0)
|
||||
);
|
||||
});
|
||||
|
||||
for (const project of sorted) {
|
||||
const streak = streaks.get(project)!;
|
||||
const projResults = failuresByProject.get(project) || [];
|
||||
const distinctFailures = projectDistinctFailures.get(project) || [];
|
||||
const block = projectLogs.get(project) || '';
|
||||
|
||||
const pms = [...new Set(projResults.map((r) => r.package_manager))];
|
||||
const pattern =
|
||||
pms.length === 1
|
||||
? `${pms[0]}-only`
|
||||
: pms.length >= 3
|
||||
? 'all PMs'
|
||||
: pms.join('+');
|
||||
|
||||
const since = streak.failing_since || 'today (new)';
|
||||
const lastPass = streak.last_passing || '—';
|
||||
const uniqueCombos = [
|
||||
...new Set(
|
||||
failedJobs.filter((j) => j.project === project).map((j) => j.combo)
|
||||
),
|
||||
];
|
||||
|
||||
lines.push('———————————————————————————');
|
||||
lines.push(`*${project}* — ${projResults.length} combos (${pattern})`);
|
||||
lines.push(
|
||||
`Project failing since ${since} | Last fully passing: ${lastPass}`
|
||||
);
|
||||
lines.push('');
|
||||
|
||||
if (distinctFailures.length > 0) {
|
||||
for (const failure of distinctFailures) {
|
||||
const fullKey = `${project}|${failure.testFile}|${failure.signature}`;
|
||||
const val = failureValidations.get(fullKey);
|
||||
|
||||
let errorDate = since;
|
||||
let errorDays: number | string = streak.consecutive_failures || 1;
|
||||
let label = '';
|
||||
|
||||
if (val?.startDate) {
|
||||
errorDate = val.startDate;
|
||||
errorDays = val.days || 1;
|
||||
}
|
||||
if (val?.status === 'different') {
|
||||
label = ' ⚠️ error changed mid-streak';
|
||||
}
|
||||
if (errorDays === 1 || errorDays === '1') {
|
||||
label = ' 🆕 NEW';
|
||||
}
|
||||
|
||||
const comboStr = failure.combos.join(', ');
|
||||
lines.push(
|
||||
`📋 \`${failure.testFile}\` (${comboStr}) — failing since ${errorDate} (${errorDays} ${errorDays === 1 || errorDays === '1' ? 'day' : 'days'})${label}`
|
||||
);
|
||||
|
||||
if (failure.block) {
|
||||
lines.push('```');
|
||||
lines.push(failure.block);
|
||||
lines.push('```');
|
||||
}
|
||||
}
|
||||
|
||||
const summaryMatch = block.match(/^Test Suites:.*$/m);
|
||||
if (summaryMatch) lines.push(`_${summaryMatch[0]}_`);
|
||||
} else {
|
||||
// No Jest blocks — find which step failed and extract its error output
|
||||
const firstJob = failedJobs.find((j) => j.project === project);
|
||||
if (firstJob) {
|
||||
// Get the failed step name from the jobs API
|
||||
const stepsRaw = gh(
|
||||
`run view ${RUN_ID} --repo ${REPO} --json jobs --jq '[.jobs[] | select(.databaseId == ${firstJob.id})][0].steps[] | select(.conclusion == "failure") | .name'`
|
||||
);
|
||||
const failedStep = stepsRaw || 'unknown step';
|
||||
|
||||
// Get the log and extract error lines
|
||||
const raw = gh(`api repos/${REPO}/actions/jobs/${firstJob.id}/logs`);
|
||||
const cleaned = raw
|
||||
.split('\n')
|
||||
.map((l) => l.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /, ''))
|
||||
.map((l) => l.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, ''));
|
||||
|
||||
// Extract the failed Nx task output block (❌ > nx run <task> ... until next ##[group] or NX summary)
|
||||
const failedTaskBlock: string[] = [];
|
||||
let capturingTask = false;
|
||||
for (const l of cleaned) {
|
||||
if (/❌.*> nx run /i.test(l)) {
|
||||
capturingTask = true;
|
||||
failedTaskBlock.push(l);
|
||||
continue;
|
||||
}
|
||||
if (capturingTask) {
|
||||
if (/^##\[group\]|NX.*Running target/i.test(l) || failedTaskBlock.length >= 15) {
|
||||
capturingTask = false;
|
||||
} else {
|
||||
failedTaskBlock.push(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract the Nx failure summary block ("Running target...failed" + "Failed tasks:" + task list)
|
||||
const nxFailureBlock: string[] = [];
|
||||
let capturingNx = false;
|
||||
for (const l of cleaned) {
|
||||
if (/NX.*Running target.*failed/i.test(l)) capturingNx = true;
|
||||
if (capturingNx) {
|
||||
nxFailureBlock.push(l);
|
||||
if (/^Hint:/i.test(l.trim()) || nxFailureBlock.length >= 10) {
|
||||
capturingNx = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: if no Nx blocks or task blocks found, extract generic error lines
|
||||
let fallbackErrors: string[] = [];
|
||||
if (nxFailureBlock.length === 0 && failedTaskBlock.length === 0) {
|
||||
fallbackErrors = cleaned.filter((l) => {
|
||||
const t = l.trim();
|
||||
if (t.length < 10) return false;
|
||||
if (/warning|warn\b|deprecated|orphan|Node\.js 20|FORCE_JAVASCRIPT|\* \[new branch\]|\* \[new tag\]/i.test(t)) return false;
|
||||
return (
|
||||
/error TS\d+:|^Error:|^\s*error\b[:\s]|ERR!|ERESOLVE|##\[error\]/i.test(t) ||
|
||||
/Cannot find module|ENOENT|EACCES|permission denied/i.test(t) ||
|
||||
/Segmentation fault|killed|OOM|out of memory/i.test(t) ||
|
||||
/command not found|No such file or directory/i.test(t) ||
|
||||
/Process completed with exit code [^0]/i.test(t)
|
||||
);
|
||||
}).slice(0, 5);
|
||||
}
|
||||
|
||||
// Combine: Nx summary first, then task output, then fallback errors
|
||||
const relevantErrors = [
|
||||
...nxFailureBlock,
|
||||
...(failedTaskBlock.length > 0 ? ['', ...failedTaskBlock] : []),
|
||||
...(fallbackErrors.length > 0 ? ['', ...fallbackErrors] : []),
|
||||
];
|
||||
|
||||
lines.push(`⚠️ Tests did not run — failed at step: *${failedStep}*`);
|
||||
if (relevantErrors.length > 0) {
|
||||
lines.push('```');
|
||||
lines.push(relevantErrors.join('\n'));
|
||||
lines.push('```');
|
||||
}
|
||||
lines.push(`Failing combos: ${uniqueCombos.join(', ')}`);
|
||||
} else {
|
||||
lines.push('⏱️ No job data available');
|
||||
}
|
||||
}
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
if (commitCount > 0) {
|
||||
lines.push(`_${commitCount} commits since last nightly_`);
|
||||
}
|
||||
|
||||
// Build job links for the summary section
|
||||
const runUrl = `https://github.com/${REPO}/actions/runs/${RUN_ID}`;
|
||||
const goldenJobLinks = new Map<string, JobLink[]>();
|
||||
for (const project of projectNames) {
|
||||
const projectJobs = failedJobs.filter((j) => j.project === project);
|
||||
goldenJobLinks.set(
|
||||
project,
|
||||
projectJobs.map((j) => ({
|
||||
combo: j.combo,
|
||||
url: `${runUrl}/job/${j.id}`,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
return { report: lines.join('\n'), goldenJobLinks };
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
type MatrixDataProject = {
|
||||
name: string,
|
||||
codeowners: string,
|
||||
is_golden?: boolean, // true if this is a golden project, false otherwise
|
||||
};
|
||||
|
||||
type MatrixDataOS = {
|
||||
@@ -9,7 +8,7 @@ type MatrixDataOS = {
|
||||
os_name: string, // short name that will be printed in the report and on the action
|
||||
os_timeout: number, // 60
|
||||
package_managers: string[], // package managers to run on this OS
|
||||
node_versions: Array<number | string>, // node versions to run on this OS
|
||||
node_versions: number[], // node versions to run on this OS
|
||||
excluded?: string[], // projects to exclude from running on this OS
|
||||
};
|
||||
|
||||
@@ -20,76 +19,60 @@ type MatrixData = {
|
||||
setup: MatrixDataOS[],
|
||||
}
|
||||
|
||||
export type MatrixItem = {
|
||||
project: string,
|
||||
codeowners: string,
|
||||
node_version: number | string,
|
||||
package_manager: string,
|
||||
os: string,
|
||||
os_name: string,
|
||||
os_timeout: number,
|
||||
is_golden?: boolean,
|
||||
};
|
||||
|
||||
// TODO: Extract Slack groups into named groups for easier maintenance
|
||||
const matrixData: MatrixData = {
|
||||
coreProjects: [
|
||||
{ name: 'e2e-lerna-smoke-tests', codeowners: 'S04TNCVEETS', is_golden: true },
|
||||
{ name: 'e2e-js', codeowners: 'S04SJ6HHP0X', is_golden: true },
|
||||
{ name: 'e2e-nx-init', codeowners: 'S04SYHYKGNP', is_golden: true },
|
||||
{ name: 'e2e-lerna-smoke-tests', codeowners: 'S04TNCVEETS' },
|
||||
{ name: 'e2e-js', codeowners: 'S04SJ6HHP0X' },
|
||||
{ name: 'e2e-nx-init', codeowners: 'S04SYHYKGNP' },
|
||||
{ name: 'e2e-nx', codeowners: 'S04SYHYKGNP' },
|
||||
{ name: 'e2e-release', codeowners: 'S04SYHYKGNP' },
|
||||
{ name: 'e2e-workspace-create', codeowners: 'S04SYHYKGNP' }
|
||||
],
|
||||
projects: [
|
||||
{ name: 'e2e-cypress', codeowners: 'S04T16BTJJY', is_golden: true },
|
||||
{ name: 'e2e-docker', codeowners: 'S04SJ6HHP0X', is_golden: true },
|
||||
{ name: 'e2e-detox', codeowners: 'S04TNCNJG5N', is_golden: true },
|
||||
{ name: 'e2e-esbuild', codeowners: 'S04SJ6HHP0X', is_golden: true },
|
||||
{ name: 'e2e-gradle', codeowners: 'S04TNCNJG5N', is_golden: true },
|
||||
{ name: 'e2e-eslint', codeowners: 'S04SYJGKSCT', is_golden: true },
|
||||
{ name: 'e2e-node', codeowners: 'S04SJ6HHP0X', is_golden: true },
|
||||
{ name: 'e2e-playwright', codeowners: 'S04SVQ8H0G5', is_golden: true },
|
||||
{ name: 'e2e-remix', codeowners: 'S04SVQ8H0G5', is_golden: true },
|
||||
{ name: 'e2e-rspack', codeowners: 'S04SJ6HHP0X', is_golden: true },
|
||||
{ name: 'e2e-vite', codeowners: 'S04SJ6PL98X', is_golden: true },
|
||||
{ name: 'e2e-vue', codeowners: 'S04SJ6PL98X', is_golden: true },
|
||||
{ name: 'e2e-web', codeowners: 'S04SJ6PL98X', is_golden: true },
|
||||
{ name: 'e2e-webpack', codeowners: 'S04SJ6PL98X', is_golden: true },
|
||||
{ name: 'e2e-jest', codeowners: 'S04T16BTJJY', is_golden: true },
|
||||
{ name: 'e2e-expo', codeowners: 'S04TNCNJG5N', is_golden: true },
|
||||
{ name: 'e2e-react-native', codeowners: 'S04TNCNJG5N', is_golden: true },
|
||||
{ name: 'e2e-angular', codeowners: 'S04SS457V38' },
|
||||
{ name: 'e2e-cypress', codeowners: 'S04T16BTJJY' },
|
||||
{ name: 'e2e-detox', codeowners: 'S04TNCNJG5N' },
|
||||
{ name: 'e2e-esbuild', codeowners: 'S04SJ6HHP0X' },
|
||||
{ name: 'e2e-expo', codeowners: 'S04TNCNJG5N' },
|
||||
{ name: 'e2e-gradle', codeowners: 'S04TNCNJG5N' },
|
||||
{ name: 'e2e-jest', codeowners: 'S04T16BTJJY' },
|
||||
{ name: 'e2e-eslint', codeowners: 'S04SYJGKSCT' },
|
||||
{ name: 'e2e-next', codeowners: 'S04TNCNJG5N' },
|
||||
{ name: 'e2e-node', codeowners: 'S04SJ6HHP0X' },
|
||||
{ name: 'e2e-plugin', codeowners: 'S04SYHYKGNP' },
|
||||
{ name: 'e2e-react', codeowners: 'S04TNCNJG5N' },
|
||||
{ name: 'e2e-react-native', codeowners: 'S04TNCNJG5N' },
|
||||
{ name: 'e2e-web', codeowners: 'S04SJ6PL98X' },
|
||||
{ name: 'e2e-rollup', codeowners: 'S04SJ6PL98X' },
|
||||
{ name: 'e2e-storybook', codeowners: 'S04SVQ8H0G5' },
|
||||
{ name: 'e2e-nuxt', codeowners: 'S04SJ6PL98X' }
|
||||
{ name: 'e2e-playwright', codeowners: 'S04SVQ8H0G5' },
|
||||
{ name: 'e2e-remix', codeowners: 'S04SVQ8H0G5' },
|
||||
{ name: 'e2e-rspack', codeowners: 'S04SJ6HHP0X' },
|
||||
{ name: 'e2e-vite', codeowners: 'S04SJ6PL98X' },
|
||||
{ name: 'e2e-vue', codeowners: 'S04SJ6PL98X' },
|
||||
{ name: 'e2e-nuxt', codeowners: 'S04SJ6PL98X' },
|
||||
{ name: 'e2e-webpack', codeowners: 'S04SJ6PL98X' }
|
||||
],
|
||||
// TODO(v23): remove node 20 - EOL April 2026
|
||||
nodeTLS: 20,
|
||||
setup: [
|
||||
{
|
||||
os: 'ubuntu-latest',
|
||||
os_name: 'Linux',
|
||||
os_timeout: 60,
|
||||
package_managers: ['npm', 'pnpm', 'yarn'],
|
||||
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)
|
||||
// See: https://github.com/docker/setup-docker-action and https://github.com/douglascamata/setup-docker-macos-action
|
||||
// We may want to look into adding intel only for this docker case, at least until vm-in-vm works on latest macos
|
||||
{ os: 'macos-latest', os_name: 'MacOS', os_timeout: 90, package_managers: ['npm'], node_versions: ['24.0.0'], excluded: ['e2e-docker'] }
|
||||
// TODO (Jack): Fix Windows support as gradle fails when running nx build https://staging.nx.app/runs/LgD4vxGn8w?utm_source=pull-request&utm_medium=comment
|
||||
// { os: 'windows-latest', os_name: 'WinOS', os_timeout: 180, package_managers: ['npm'], node_versions: ['24.0.0'], excluded: ['e2e-detox', 'e2e-react-native', 'e2e-expo'] }
|
||||
{ os: 'ubuntu-latest', os_name: 'Linux', os_timeout: 60, package_managers: ['npm', 'pnpm', 'yarn'], node_versions: [18, 20, 22], excluded: ['e2e-detox', 'e2e-react-native', 'e2e-expo'] },
|
||||
{ os: 'macos-latest', os_name: 'MacOS', os_timeout: 90, package_managers: ['npm'], node_versions: [20] },
|
||||
{ os: 'windows-latest', os_name: 'WinOS', os_timeout: 180, package_managers: ['npm'], node_versions: [20], excluded: ['e2e-detox', 'e2e-react-native', 'e2e-expo'] }
|
||||
]
|
||||
};
|
||||
|
||||
const matrix: Array<MatrixItem> = [];
|
||||
const matrix: Array<{
|
||||
project: string,
|
||||
codeowners: string,
|
||||
node_version: number,
|
||||
package_manager: string,
|
||||
os: string,
|
||||
os_name: string,
|
||||
os_timeout: number
|
||||
}> = [];
|
||||
|
||||
function addMatrixCombo(project: MatrixDataProject, nodeVersion: number | string, pm: number, os: number) {
|
||||
function addMatrixCombo(project: MatrixDataProject, nodeVersion: number, pm: number, os: number) {
|
||||
matrix.push({
|
||||
project: project.name,
|
||||
codeowners: project.codeowners,
|
||||
@@ -98,7 +81,6 @@ function addMatrixCombo(project: MatrixDataProject, nodeVersion: number | string
|
||||
os: matrixData.setup[os].os,
|
||||
os_name: matrixData.setup[os].os_name,
|
||||
os_timeout: matrixData.setup[os].os_timeout,
|
||||
is_golden: !!project.is_golden // Mark golden projects as true, others as false
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,229 +0,0 @@
|
||||
import * as fs from 'fs';
|
||||
import { MatrixItem } from './process-matrix';
|
||||
import { collectFailureDetails, JobLink } from './analyze-failures';
|
||||
|
||||
interface MatrixResult extends MatrixItem {
|
||||
status: 'success' | 'failure' | 'cancelled';
|
||||
duration: number;
|
||||
}
|
||||
|
||||
interface ProcessedResults {
|
||||
codeowners: string;
|
||||
slack_message: string;
|
||||
slack_proj_duration: string;
|
||||
slack_pm_duration: string;
|
||||
has_golden_failures: string;
|
||||
}
|
||||
|
||||
function trimSpace(res: string): string {
|
||||
return res.split('\n').map((l) => l.trim()).join('\n');
|
||||
}
|
||||
|
||||
function humanizeDuration(num: number): string {
|
||||
let res = '';
|
||||
const hours = Math.floor(num / 3600);
|
||||
if (hours) res += `${hours}h `;
|
||||
const mins = Math.floor((num % 3600) / 60);
|
||||
if (mins) res += `${mins}m `;
|
||||
const sec = num % 60;
|
||||
if (sec) res += `${sec}s`;
|
||||
return res;
|
||||
}
|
||||
|
||||
function processResults(combined: MatrixResult[]): ProcessedResults {
|
||||
const failedProjects = combined.filter(c => c.status === 'failure' || c.status === 'cancelled').sort((a, b) => a.project.localeCompare(b.project));
|
||||
const failedGoldenProjects = failedProjects.filter(c => c.is_golden);
|
||||
const hasGoldenFailures = failedGoldenProjects.length > 0;
|
||||
const codeowners = new Set<string>();
|
||||
failedGoldenProjects.forEach(c => codeowners.add(c.codeowners));
|
||||
|
||||
let result = '';
|
||||
|
||||
const allGoldenProjects = combined.filter(c => c.is_golden);
|
||||
const uniqueGoldenProjects = new Set(allGoldenProjects.map(c => c.project));
|
||||
const uniqueFailedGoldenProjects = new Set(failedGoldenProjects.map(c => c.project));
|
||||
const goldenPassingCount = uniqueGoldenProjects.size - uniqueFailedGoldenProjects.size;
|
||||
const goldenFailingCount = uniqueFailedGoldenProjects.size;
|
||||
|
||||
const allOtherProjects = combined.filter(c => !c.is_golden);
|
||||
const uniqueOtherProjects = new Set(allOtherProjects.map(c => c.project));
|
||||
const failedRegularProjects = failedProjects.filter(c => !c.is_golden);
|
||||
const uniqueFailedOtherProjects = new Set(failedRegularProjects.map(c => c.project));
|
||||
const otherPassingCount = uniqueOtherProjects.size - uniqueFailedOtherProjects.size;
|
||||
const otherFailingCount = uniqueFailedOtherProjects.size;
|
||||
|
||||
result += `\n🌟 *Golden Projects*`;
|
||||
result += `\n✅ Passing: ${goldenPassingCount} | ❌ Failing: ${goldenFailingCount}`;
|
||||
|
||||
if (failedGoldenProjects.length > 0) {
|
||||
result += `\n\n🚨 *Failed Golden Projects*`;
|
||||
// Project names listed here — combo links added later by main() with job data
|
||||
const seenProjects = new Set<string>();
|
||||
failedGoldenProjects.forEach(matrix => {
|
||||
if (!seenProjects.has(matrix.project)) {
|
||||
seenProjects.add(matrix.project);
|
||||
result += `\n\n*${matrix.project}*`;
|
||||
// Placeholder — main() will replace with linked combos
|
||||
result += `\n {{COMBOS:${matrix.project}}}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (otherFailingCount > 0) {
|
||||
const otherProjectCounts = new Map<string, number>();
|
||||
failedRegularProjects.forEach(m => {
|
||||
otherProjectCounts.set(m.project, (otherProjectCounts.get(m.project) || 0) + 1);
|
||||
});
|
||||
const otherSummary = [...otherProjectCounts.entries()]
|
||||
.map(([p, c]) => `${p} (${c})`)
|
||||
.join(', ');
|
||||
result += `\n\n⚠️ *Failed Other Projects:* ${otherSummary}`;
|
||||
}
|
||||
|
||||
if (failedProjects.length === 0) {
|
||||
result = '🎉 *No test failures detected!* All systems green! 🟢';
|
||||
}
|
||||
|
||||
const timeReport: Record<string, { min: number; max: number; minEnv: string; maxEnv: string }> = {};
|
||||
const pmReport = { npm: 0, yarn: 0, pnpm: 0 };
|
||||
const macosProjects = ['e2e-detox', 'e2e-expo', 'e2e-react-native'];
|
||||
|
||||
combined.forEach(matrix => {
|
||||
const nodeVersion = parseInt(matrix.node_version.toString());
|
||||
if (matrix.os_name === 'Linux' && nodeVersion === 20 && matrix.package_manager in pmReport) {
|
||||
pmReport[matrix.package_manager as keyof typeof pmReport] += matrix.duration;
|
||||
}
|
||||
if (matrix.os_name === 'Linux' || macosProjects.includes(matrix.project)) {
|
||||
if (timeReport[matrix.project]) {
|
||||
if (matrix.duration > timeReport[matrix.project].max) {
|
||||
timeReport[matrix.project].max = matrix.duration;
|
||||
timeReport[matrix.project].maxEnv = `${matrix.os_name}, ${matrix.package_manager}`;
|
||||
}
|
||||
if (matrix.duration < timeReport[matrix.project].min) {
|
||||
timeReport[matrix.project].min = matrix.duration;
|
||||
timeReport[matrix.project].minEnv = `${matrix.os_name}, ${matrix.package_manager}`;
|
||||
}
|
||||
} else {
|
||||
timeReport[matrix.project] = {
|
||||
min: matrix.duration,
|
||||
max: matrix.duration,
|
||||
minEnv: `${matrix.os_name}, ${matrix.package_manager}`,
|
||||
maxEnv: `${matrix.os_name}, ${matrix.package_manager}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let resultPkg = `
|
||||
\`\`\`
|
||||
| Project | Time |
|
||||
|--------------------------------|---------------------------|`;
|
||||
|
||||
function mapProjectTime(proj: string, section: 'min' | 'max'): string {
|
||||
return `${humanizeDuration(timeReport[proj][section])} (${timeReport[proj][`${section}Env`]})`;
|
||||
}
|
||||
|
||||
function durationIcon(proj: string, section: 'min' | 'max'): string {
|
||||
const duration = timeReport[proj][section];
|
||||
if (duration < 12 * 60) return `${section} ✅`;
|
||||
if (duration < 15 * 60) return `${section} ❗`;
|
||||
return `${section} ❌`;
|
||||
}
|
||||
|
||||
Object.keys(timeReport).forEach(proj => {
|
||||
resultPkg += `\n| ${proj.padEnd(30)} | |`;
|
||||
resultPkg += `\n| ${durationIcon(proj, 'min').padStart(29)} | ${mapProjectTime(proj, 'min').padEnd(25)} |`;
|
||||
resultPkg += `\n| ${durationIcon(proj, 'max').padStart(29)} | ${mapProjectTime(proj, 'max').padEnd(25)} |`;
|
||||
});
|
||||
resultPkg += `\`\`\``;
|
||||
|
||||
let resultPm = `
|
||||
\`\`\`
|
||||
| PM | Total time |
|
||||
|------|-------------|`;
|
||||
Object.keys(pmReport).forEach(pm => {
|
||||
resultPm += `\n| ${pm.padEnd(4)} | ${humanizeDuration(pmReport[pm as keyof typeof pmReport]).padEnd(11)} |`;
|
||||
});
|
||||
resultPm += `\`\`\``;
|
||||
|
||||
return {
|
||||
codeowners: Array.from(codeowners).join(','),
|
||||
slack_message: trimSpace(result),
|
||||
slack_proj_duration: trimSpace(resultPkg),
|
||||
slack_pm_duration: trimSpace(resultPm),
|
||||
has_golden_failures: hasGoldenFailures.toString(),
|
||||
};
|
||||
}
|
||||
|
||||
function setOutput(key: string, value: string) {
|
||||
const outputPath = process.env.GITHUB_OUTPUT;
|
||||
if (!outputPath) {
|
||||
console.warn(`GITHUB_OUTPUT not set. Skipping output for "${key}".`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.includes('\n')) {
|
||||
const delimiter = `EOF_${key}_${Date.now()}`;
|
||||
fs.appendFileSync(outputPath, `${key}<<${delimiter}\n${value}\n${delimiter}\n`);
|
||||
} else {
|
||||
fs.appendFileSync(outputPath, `${key}=${value}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const combinedInput = process.argv[2]
|
||||
? process.argv[2]
|
||||
: fs.readFileSync(0, 'utf-8').trim();
|
||||
|
||||
const combined: MatrixResult[] = JSON.parse(combinedInput);
|
||||
const results = processResults(combined);
|
||||
|
||||
// Collect detailed failure info if golden failures exist
|
||||
if (results.has_golden_failures === 'true') {
|
||||
try {
|
||||
const failedProjects = [
|
||||
...new Set(
|
||||
combined
|
||||
.filter((c) => c.is_golden && (c.status === 'failure' || c.status === 'cancelled'))
|
||||
.map((c) => c.project)
|
||||
),
|
||||
];
|
||||
const { report, goldenJobLinks } = await collectFailureDetails(combined, failedProjects);
|
||||
|
||||
// Replace combo placeholders in the summary with linked combos
|
||||
for (const [project, links] of goldenJobLinks) {
|
||||
const placeholder = `{{COMBOS:${project}}}`;
|
||||
const linkedCombos =
|
||||
links.length > 0
|
||||
? links.map((l) => ` · <${l.url}|${l.combo}>`).join('\n')
|
||||
: ' (no job data)';
|
||||
results.slack_message = results.slack_message.replace(
|
||||
placeholder,
|
||||
linkedCombos
|
||||
);
|
||||
}
|
||||
|
||||
// Remove any unreplaced placeholders (if collectFailureDetails didn't have data for a project)
|
||||
results.slack_message = results.slack_message.replace(
|
||||
/ \{\{COMBOS:[^}]+\}\}/g,
|
||||
' (no job data)'
|
||||
);
|
||||
|
||||
if (report) {
|
||||
results.slack_message += '\n\n' + report;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to collect failure details (brief report will still be posted):', e);
|
||||
results.slack_message += '\n\n⚠️ _Failed to collect detailed failure information_';
|
||||
}
|
||||
}
|
||||
|
||||
Object.entries(results).forEach(([key, value]) => {
|
||||
setOutput(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('Error processing results:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -14,11 +14,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d # v4.1.0
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.28.2 # Aligned with root package.json (pnpm/action-setup will helpfully error if out of sync)
|
||||
version: 9.8.0 # 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
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
name: Report status
|
||||
steps:
|
||||
- name: Send notification
|
||||
uses: ravsamhq/notify-slack-action@be814b201e233b2dc673608aa46e5447c8ab13f2 # v11
|
||||
uses: ravsamhq/notify-slack-action@v2
|
||||
with:
|
||||
status: ${{ needs.audit.result }}
|
||||
message_format: '{emoji} Audit has {status_message}'
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
name: PR Title Validation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, synchronize, reopened]
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
validate-pr-title:
|
||||
name: Validate PR Title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
# Ensure's validate-pr-title.js is the copy from master
|
||||
ref: master
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: 24
|
||||
package-manager-cache: false
|
||||
|
||||
- name: Validate PR title
|
||||
env:
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
PR_BODY: ${{ github.event.pull_request.body }}
|
||||
run: node ./scripts/validate-pr-title.js
|
||||
+115
-355
@@ -3,7 +3,7 @@ name: publish
|
||||
on:
|
||||
# Automated schedule - canary releases from master
|
||||
schedule:
|
||||
- cron: "0 19 * * 1-5" # Monday - Friday, at 19:00 UTC (7pm UTC)
|
||||
- cron: "0 3 * * 2-6" # Tuesdays - Saturdays, at 3am UTC
|
||||
# Manual trigger - PR releases or dry-runs (based on workflow inputs)
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -21,9 +21,8 @@ env:
|
||||
DEBUG: napi:*
|
||||
NX_RUN_GROUP: ${{ github.run_id }}-${{ github.run_attempt }}
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
NODE_VERSION: 22.16.0
|
||||
PNPM_VERSION: 10.28.2 # Aligned with root package.json (pnpm/action-setup will helpfully error if out of sync)
|
||||
NX_GRADLE_PROJECT_GRAPH_TIMEOUT: 600
|
||||
NODE_VERSION: 18
|
||||
PNPM_VERSION: 9.8.0 # 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.
|
||||
@@ -38,7 +37,7 @@ jobs:
|
||||
# ref resolution in actions/checkout. The exact version will be generated within scripts/nx-release.ts.
|
||||
#
|
||||
# - workflow_dispatch:
|
||||
# - We are either running a dry-run on the current branch, in which case the version will be static and we can use
|
||||
# - We are either running a dry-run on the current branch, in which case the version will be statica and we can use
|
||||
# default ref resolution in actions/checkout, or we are creating a PR release for the given PR number, in which case
|
||||
# we should generate an applicable version number within publish-resolve-data.js and use a custom ref of the PR branch name.
|
||||
resolve-required-data:
|
||||
@@ -52,23 +51,31 @@ jobs:
|
||||
publish_branch: ${{ steps.script.outputs.publish_branch }}
|
||||
ref: ${{ steps.script.outputs.ref }}
|
||||
repo: ${{ steps.script.outputs.repo }}
|
||||
pr_number: ${{ steps.script.outputs.pr_number }}
|
||||
pr_author: ${{ steps.script.outputs.pr_author }}
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
steps:
|
||||
# Default checkout on the triggering branch so that the latest publish-resolve-data.js script is available
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Set up pnpm and node so that we can verify our setup and that the NPM_TOKEN secret will work later
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
check-latest: true
|
||||
package-manager-cache: false
|
||||
|
||||
# Ensure that the NPM_TOKEN secret is still valid before wasting any time deriving data or building projects
|
||||
- name: Check NPM Credentials
|
||||
run: npm whoami && echo "NPM credentials are valid" || (echo "NPM credentials are invalid or have expired." && exit 1)
|
||||
|
||||
- name: Resolve and set checkout and version data to use for release
|
||||
id: script
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.inputs.pr }}
|
||||
with:
|
||||
@@ -79,7 +86,7 @@ jobs:
|
||||
|
||||
- name: (PR Release Only) Check out latest master
|
||||
if: ${{ steps.script.outputs.ref != '' }}
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Check out the latest master branch to get its copy of nx-release.ts
|
||||
repository: nrwl/nx
|
||||
@@ -88,31 +95,24 @@ jobs:
|
||||
|
||||
- name: (PR Release Only) Check out PR branch
|
||||
if: ${{ steps.script.outputs.ref != '' }}
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Check out the PR branch to get its copy of nx-release.ts
|
||||
repository: ${{ steps.script.outputs.repo }}
|
||||
ref: ${{ steps.script.outputs.ref }}
|
||||
path: pr-branch-checkout
|
||||
|
||||
- name: (PR Release Only) Ensure that release scripts have not changed in the PR being released
|
||||
- name: (PR Release Only) Ensure that nx-release.ts has not changed in the PR being released
|
||||
if: ${{ steps.script.outputs.ref != '' }}
|
||||
env:
|
||||
FILE_TO_COMPARE: "scripts/nx-release.ts"
|
||||
run: |
|
||||
# List of files that must not change in PR releases
|
||||
FILES_TO_CHECK=(
|
||||
"scripts/nx-release.ts"
|
||||
"scripts/publish-resolve-data.js"
|
||||
)
|
||||
|
||||
for FILE in "${FILES_TO_CHECK[@]}"; do
|
||||
if ! cmp -s "latest-master-checkout/$FILE" "pr-branch-checkout/$FILE"; then
|
||||
echo "🛑 Error: The file $FILE is different on the ${{ steps.script.outputs.ref }} branch on ${{ steps.script.outputs.repo }} vs latest master on nrwl/nx, cancelling workflow."
|
||||
echo "If you did not modify the file, then you likely just need to rebase/merge latest master."
|
||||
exit 1
|
||||
else
|
||||
echo "✅ The file $FILE is identical between the ${{ steps.script.outputs.ref }} branch on ${{ steps.script.outputs.repo }} and latest master on nrwl/nx."
|
||||
fi
|
||||
done
|
||||
if ! cmp -s "latest-master-checkout/${{ env.FILE_TO_COMPARE }}" "pr-branch-checkout/${{ env.FILE_TO_COMPARE }}"; then
|
||||
echo "🛑 Error: The file ${{ env.FILE_TO_COMPARE }} is different on the ${{ steps.script.outputs.ref }} branch on ${{ steps.script.outputs.repo }} vs latest master on nrwl/nx, cancelling workflow. If you did not modify the file, then you likely just need to rebase/merge latest master."
|
||||
exit 1
|
||||
else
|
||||
echo "✅ The file ${{ env.FILE_TO_COMPARE }} is identical between the ${{ steps.script.outputs.ref }} branch on ${{ steps.script.outputs.repo }} and latest master on nrwl/nx."
|
||||
fi
|
||||
|
||||
build:
|
||||
needs: [ resolve-required-data ]
|
||||
@@ -121,21 +121,12 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
settings:
|
||||
- host: macos-latest
|
||||
- host: macos-13
|
||||
target: x86_64-apple-darwin
|
||||
setup: |-
|
||||
rustup target add x86_64-apple-darwin
|
||||
build: |
|
||||
pnpm nx run-many --target=build-native -- --target=x86_64-apple-darwin
|
||||
- host: windows-latest
|
||||
setup: |-
|
||||
choco install openjdk --version=21.0.0 -y
|
||||
rustup target add aarch64-pc-windows-msvc
|
||||
build: |
|
||||
export JAVA_HOME="C:\Program Files\OpenJDK\jdk-21"
|
||||
export PATH="$JAVA_HOME\bin:$PATH"
|
||||
java -version
|
||||
pnpm nx run-many --target=build-native -- --target=x86_64-pc-windows-msvc
|
||||
build: pnpm nx run-many --target=build-native -- --target=x86_64-pc-windows-msvc
|
||||
target: x86_64-pc-windows-msvc
|
||||
# Windows 32bit (not needed)
|
||||
# - host: windows-latest
|
||||
@@ -145,69 +136,23 @@ jobs:
|
||||
- host: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
|
||||
build: |
|
||||
set -e
|
||||
apt-get update
|
||||
|
||||
# Install Java 21
|
||||
apt-get install -y openjdk-21-jdk
|
||||
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
|
||||
export PATH="$JAVA_HOME/bin:$PATH"
|
||||
java --version
|
||||
|
||||
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
||||
apt-get install -y nodejs=22.16.0-1nodesource1
|
||||
|
||||
export PATH="/usr/local/bin:$PATH"
|
||||
node --version
|
||||
npm --version
|
||||
|
||||
npm i -g pnpm@${PNPM_VERSION} --force
|
||||
pnpm --version
|
||||
|
||||
pnpm install --frozen-lockfile
|
||||
rustup target add x86_64-unknown-linux-gnu
|
||||
build: |-
|
||||
set -e &&
|
||||
npm i -g pnpm@9.8.0 --force &&
|
||||
pnpm --version &&
|
||||
pnpm install --frozen-lockfile &&
|
||||
pnpm nx run-many --verbose --target=build-native -- --target=x86_64-unknown-linux-gnu
|
||||
- host: ubuntu-latest
|
||||
target: x86_64-unknown-linux-musl
|
||||
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
|
||||
build: |
|
||||
bash -c "
|
||||
set -e
|
||||
echo 'https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories
|
||||
apk add --no-cache curl xz openjdk21 build-base lld
|
||||
|
||||
# Set up Java 21
|
||||
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk
|
||||
export PATH=\"\$JAVA_HOME/bin:\$PATH\"
|
||||
java --version
|
||||
|
||||
curl -fsSL https://unofficial-builds.nodejs.org/download/release/v22.16.0/node-v22.16.0-linux-x64-musl.tar.xz -o node.tar.xz
|
||||
tar -xJf node.tar.xz
|
||||
mv node-v22.16.0-linux-x64-musl /usr/local/node
|
||||
|
||||
export PATH=\"/usr/local/node/bin:\$PATH\"
|
||||
|
||||
echo Node: \$(node -v)
|
||||
echo NPM: \$(npm -v)
|
||||
|
||||
# Install PNPM
|
||||
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
|
||||
pnpm nx run-many --verbose --target=build-native -- --target=x86_64-unknown-linux-musl
|
||||
"
|
||||
- host: macos-latest
|
||||
build: |-
|
||||
set -e &&
|
||||
npm i -g pnpm@9.8.0 --force &&
|
||||
pnpm --version &&
|
||||
pnpm install --frozen-lockfile &&
|
||||
pnpm nx run-many --verbose --target=build-native -- --target=x86_64-unknown-linux-musl
|
||||
- host: macos-13
|
||||
target: aarch64-apple-darwin
|
||||
setup: |-
|
||||
rustup target add aarch64-apple-darwin
|
||||
build: |
|
||||
sudo rm -Rf /Library/Developer/CommandLineTools/SDKs/*;
|
||||
export CC=$(xcrun -f clang);
|
||||
@@ -218,38 +163,17 @@ jobs:
|
||||
- host: ubuntu-latest
|
||||
target: aarch64-unknown-linux-gnu
|
||||
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64
|
||||
build: |
|
||||
set -e
|
||||
apt-get update
|
||||
|
||||
# Install Java 21
|
||||
apt-get install -y openjdk-21-jdk
|
||||
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
|
||||
export PATH="$JAVA_HOME/bin:$PATH"
|
||||
java --version
|
||||
|
||||
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
||||
apt-get install -y nodejs=22.16.0-1nodesource1
|
||||
|
||||
export PATH="/usr/local/bin:$PATH"
|
||||
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
|
||||
|
||||
pnpm install --frozen-lockfile
|
||||
rustup target add aarch64-unknown-linux-gnu
|
||||
build: |-
|
||||
set -e &&
|
||||
npm i -g pnpm@9.8.0 --force &&
|
||||
pnpm --version &&
|
||||
pnpm install --frozen-lockfile &&
|
||||
pnpm nx run-many --verbose --target=build-native -- --target=aarch64-unknown-linux-gnu
|
||||
- host: ubuntu-latest
|
||||
target: armv7-unknown-linux-gnueabihf
|
||||
setup: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install gcc-arm-linux-gnueabihf -y
|
||||
rustup target add armv7-unknown-linux-gnueabihf
|
||||
build: |
|
||||
CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=/usr/bin/arm-linux-gnueabihf-gcc pnpm nx run-many --target=build-native -- --target=armv7-unknown-linux-gnueabihf
|
||||
# Android (not needed)
|
||||
@@ -264,73 +188,44 @@ jobs:
|
||||
- host: ubuntu-latest
|
||||
target: aarch64-unknown-linux-musl
|
||||
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
|
||||
build: |
|
||||
bash -c "
|
||||
set -e
|
||||
echo 'https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories
|
||||
apk add --no-cache curl xz openjdk21 build-base lld
|
||||
|
||||
# Set up Java 21
|
||||
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk
|
||||
export PATH=\"\$JAVA_HOME/bin:\$PATH\"
|
||||
java --version
|
||||
|
||||
curl -fsSL https://unofficial-builds.nodejs.org/download/release/v22.16.0/node-v22.16.0-linux-x64-musl.tar.xz -o node.tar.xz
|
||||
tar -xJf node.tar.xz
|
||||
mv node-v22.16.0-linux-x64-musl /usr/local/node
|
||||
|
||||
export PATH=\"/usr/local/node/bin:\$PATH\"
|
||||
|
||||
echo Node: \$(node -v)
|
||||
echo NPM: \$(npm -v)
|
||||
|
||||
# Install PNPM
|
||||
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
|
||||
pnpm nx run-many --verbose --target=build-native -- --target=aarch64-unknown-linux-musl
|
||||
"
|
||||
build: |-
|
||||
set -e &&
|
||||
rustup target add aarch64-unknown-linux-musl &&
|
||||
npm i -g pnpm@9.8.0 --force &&
|
||||
pnpm --version &&
|
||||
pnpm install --frozen-lockfile &&
|
||||
pnpm nx run-many --verbose --target=build-native -- --target=aarch64-unknown-linux-musl
|
||||
- host: windows-latest
|
||||
target: aarch64-pc-windows-msvc
|
||||
setup: |-
|
||||
choco install openjdk --version=21.0.0 -y
|
||||
rustup target add aarch64-pc-windows-msvc
|
||||
build: |
|
||||
export JAVA_HOME="C:\Program Files\OpenJDK\jdk-21"
|
||||
export PATH="$JAVA_HOME\bin:$PATH"
|
||||
java -version
|
||||
pnpm nx run-many --target=build-native -- --target=aarch64-pc-windows-msvc
|
||||
name: stable - ${{ matrix.settings.target }} - node@22.16.0
|
||||
build: pnpm nx run-many --target=build-native -- --target=aarch64-pc-windows-msvc
|
||||
name: stable - ${{ matrix.settings.target }} - node@18
|
||||
runs-on: ${{ matrix.settings.host }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ needs.resolve-required-data.outputs.repo || github.repository }}
|
||||
ref: ${{ needs.resolve-required-data.outputs.ref || github.ref }}
|
||||
repository: ${{ needs.resolve-required-data.outputs.repo }}
|
||||
ref: ${{ needs.resolve-required-data.outputs.ref }}
|
||||
|
||||
- name: Set verbose logging from debug mode
|
||||
if: runner.debug == '1'
|
||||
run: echo "NX_VERBOSE_LOGGING=true" >> "$GITHUB_ENV"
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Setup dev tools with mise
|
||||
uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
if: ${{ !matrix.settings.docker }}
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
check-latest: true
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Enable corepack and install pnpm
|
||||
- name: Install
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
if: ${{ !matrix.settings.docker }}
|
||||
run: |
|
||||
corepack enable
|
||||
corepack prepare --activate
|
||||
with:
|
||||
targets: ${{ matrix.settings.target }}
|
||||
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index/
|
||||
@@ -340,7 +235,7 @@ jobs:
|
||||
target/
|
||||
key: ${{ matrix.settings.target }}-cargo-registry
|
||||
|
||||
- uses: goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406 # v2.2.1
|
||||
- uses: goto-bus-stop/setup-zig@v2
|
||||
if: ${{ matrix.settings.target == 'armv7-unknown-linux-gnueabihf' }}
|
||||
with:
|
||||
version: 0.10.0
|
||||
@@ -361,7 +256,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
|
||||
- name: Setup node x86
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
uses: actions/setup-node@v4
|
||||
if: matrix.settings.target == 'i686-pc-windows-msvc'
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
@@ -370,26 +265,12 @@ jobs:
|
||||
architecture: x86
|
||||
|
||||
- name: Build in docker
|
||||
uses: addnab/docker-run-action@v3
|
||||
if: ${{ matrix.settings.docker }}
|
||||
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 \
|
||||
-e NX_GRADLE_PROJECT_GRAPH_TIMEOUT \
|
||||
-e NX_VERBOSE_LOGGING \
|
||||
-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
|
||||
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 }}
|
||||
|
||||
- name: Build
|
||||
run: ${{ matrix.settings.build }}
|
||||
@@ -397,12 +278,12 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bindings-${{ matrix.settings.target }}
|
||||
path: |
|
||||
packages/nx/src/native/*.node
|
||||
packages/nx/src/native/*.wasm
|
||||
packages/**/*.node
|
||||
packages/**/*.wasm
|
||||
if-no-files-found: error
|
||||
|
||||
build-freebsd:
|
||||
@@ -412,33 +293,30 @@ jobs:
|
||||
name: Build FreeBSD
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ needs.resolve-required-data.outputs.repo || github.repository }}
|
||||
ref: ${{ needs.resolve-required-data.outputs.ref || github.ref }}
|
||||
repository: ${{ needs.resolve-required-data.outputs.repo }}
|
||||
ref: ${{ needs.resolve-required-data.outputs.ref }}
|
||||
|
||||
- name: Build
|
||||
id: build
|
||||
uses: cross-platform-actions/action@462ed697694d2ac9aa49e1225f395f7bb6dd49fe # v0.29.0
|
||||
uses: cross-platform-actions/action@v0.25.0
|
||||
env:
|
||||
DEBUG: napi:*
|
||||
RUSTUP_IO_THREADS: 1
|
||||
NX_PREFER_TS_NODE: true
|
||||
PLAYWRIGHT_BROWSERS_PATH: 0
|
||||
NODE_VERSION: 22.16.0
|
||||
NX_GRADLE_DISABLE: 'true'
|
||||
NODE_OPTIONS: '--max-old-space-size=4096'
|
||||
with:
|
||||
operating_system: freebsd
|
||||
version: '14.0'
|
||||
architecture: x86-64
|
||||
environment_variables: DEBUG RUSTUP_IO_THREADS CI NX_PREFER_TS_NODE PLAYWRIGHT_BROWSERS_PATH NODE_VERSION NX_GRADLE_DISABLE NODE_OPTIONS
|
||||
environment_variables: DEBUG RUSTUP_IO_THREADS CI NX_PREFER_TS_NODE PLAYWRIGHT_BROWSERS_PATH
|
||||
shell: bash
|
||||
run: |
|
||||
env
|
||||
whoami
|
||||
sudo pkg install -y -f node libnghttp2 www/npm git ca_root_nss
|
||||
sudo npm install --location=global --ignore-scripts pnpm@10.28.2
|
||||
sudo pkg install -y -f node libnghttp2 www/npm git
|
||||
sudo npm install --location=global --ignore-scripts pnpm@9.8.0
|
||||
curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
sh rustup.sh -y --profile minimal --default-toolchain stable
|
||||
source "$HOME/.cargo/env"
|
||||
@@ -453,88 +331,8 @@ jobs:
|
||||
whoami
|
||||
env
|
||||
freebsd-version
|
||||
echo "Installing dependencies"
|
||||
pnpm install --frozen-lockfile --ignore-scripts
|
||||
|
||||
echo "Checking disk space before cleanup"
|
||||
df -h
|
||||
echo "Removing unnecessary preinstalled packages"
|
||||
# List all packages first to see what's installed
|
||||
sudo pkg info -a
|
||||
echo "Cleaning up to free disk space"
|
||||
# Clean package caches
|
||||
sudo pkg clean -a -y
|
||||
sudo pkg autoremove -y
|
||||
# Remove unnecessary system files
|
||||
sudo rm -rf /usr/local/lib/*.a
|
||||
sudo rm -rf /usr/local/share/doc/*
|
||||
sudo rm -rf /usr/local/share/man/*
|
||||
sudo rm -rf /usr/local/share/examples/*
|
||||
sudo rm -rf /usr/local/share/locale/*
|
||||
sudo rm -rf /usr/local/share/gtk-doc/*
|
||||
sudo rm -rf /usr/local/share/info/*
|
||||
sudo rm -rf /usr/src/*
|
||||
sudo rm -rf /usr/obj/*
|
||||
sudo rm -rf /usr/tests/*
|
||||
sudo rm -rf /usr/lib/debug/*
|
||||
# Clean var directories
|
||||
sudo rm -rf /var/cache/pkg/*
|
||||
sudo rm -rf /var/db/pkg/*.tbz
|
||||
sudo rm -rf /var/log/*.log
|
||||
sudo rm -rf /var/log/*.old
|
||||
# Clean temporary files
|
||||
sudo rm -rf /tmp/*
|
||||
sudo rm -rf /var/tmp/*
|
||||
# Remove Python cache if present
|
||||
sudo find /usr/local -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
|
||||
sudo find /usr/local -name "*.pyc" -delete 2>/dev/null || true
|
||||
sudo find /usr/local -name "*.pyo" -delete 2>/dev/null || true
|
||||
# Clean npm/pnpm caches
|
||||
npm cache clean --force || true
|
||||
pnpm store prune || true
|
||||
rm -rf ~/.npm || true
|
||||
rm -rf ~/.pnpm-store || true
|
||||
# Clean Rust extras but keep registry/git (needed for cargo to resolve deps)
|
||||
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
|
||||
|
||||
# Disable core dumps - OOM'd Node processes write multi-GB core files that fill the disk
|
||||
ulimit -c 0
|
||||
|
||||
echo "Building FreeBSD bindings"
|
||||
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"
|
||||
echo "=== Disk usage by top-level directories ==="
|
||||
du -sh /* 2>/dev/null | sort -rh | head -20
|
||||
echo "=== Disk usage in home directory ==="
|
||||
du -sh ~/* 2>/dev/null | sort -rh | head -20
|
||||
echo "=== Disk usage in workspace ==="
|
||||
du -sh /home/runner/work/nx/nx/* 2>/dev/null | sort -rh | head -20
|
||||
echo "=== Disk usage in .nx ==="
|
||||
du -sh /home/runner/work/nx/nx/.nx/* 2>/dev/null | sort -rh | head -20
|
||||
echo "=== Disk usage in cargo/rustup ==="
|
||||
du -sh ~/.cargo/* ~/.rustup/* 2>/dev/null | sort -rh | head -20
|
||||
echo "=== Core dumps ==="
|
||||
find / -name "*.core" -o -name "core.*" -o -name "core" 2>/dev/null | head -10
|
||||
exit $BUILD_EXIT
|
||||
fi
|
||||
|
||||
echo "Build succeeded"
|
||||
|
||||
echo "Cleaning up"
|
||||
pnpm nx run-many --verbose --outputStyle stream --target=build-native -- --target=x86_64-unknown-freebsd
|
||||
pnpm nx reset
|
||||
rm -rf node_modules
|
||||
rm -rf dist
|
||||
@@ -543,18 +341,16 @@ jobs:
|
||||
echo "COMPLETE"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bindings-freebsd
|
||||
path: |
|
||||
packages/nx/src/native/*.node
|
||||
path: packages/**/*.node
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
if: ${{ github.repository_owner == 'nrwl' }}
|
||||
name: Publish
|
||||
runs-on: ubuntu-latest
|
||||
environment: npm-registry
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
@@ -565,32 +361,31 @@ jobs:
|
||||
- build
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
NPM_CONFIG_PROVENANCE: true
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ needs.resolve-required-data.outputs.repo || github.repository }}
|
||||
ref: ${{ needs.resolve-required-data.outputs.ref || github.ref }}
|
||||
repository: ${{ needs.resolve-required-data.outputs.repo }}
|
||||
ref: ${{ needs.resolve-required-data.outputs.ref }}
|
||||
|
||||
- name: Set verbose logging from debug mode
|
||||
if: runner.debug == '1'
|
||||
run: echo "NX_VERBOSE_LOGGING=true" >> "$GITHUB_ENV"
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Setup dev tools with mise
|
||||
uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3
|
||||
|
||||
- name: Enable corepack and install pnpm
|
||||
run: |
|
||||
corepack enable
|
||||
corepack prepare --activate
|
||||
|
||||
- name: Use npm 11.5.2
|
||||
run: npm install -g npm@11.5.2
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
check-latest: true
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
@@ -602,7 +397,6 @@ jobs:
|
||||
run: |
|
||||
wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-23/wasi-sdk-23.0-x86_64-linux.tar.gz
|
||||
tar -xvf wasi-sdk-23.0-x86_64-linux.tar.gz
|
||||
rustup toolchain install nightly-2025-05-09
|
||||
pnpm build:wasm
|
||||
- name: Publish
|
||||
env:
|
||||
@@ -626,11 +420,11 @@ jobs:
|
||||
|
||||
- name: (PR Release Only) Create comment for successful PR release
|
||||
if: success() && github.event.inputs.pr
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
SUCCESS_COMMENT: ${{ needs.resolve-required-data.outputs.success_comment }}
|
||||
with:
|
||||
# github-token defaults to ${{ github.token }} so we don't need to specify it
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const successComment = JSON.parse(process.env.SUCCESS_COMMENT);
|
||||
await github.rest.issues.createComment({
|
||||
@@ -640,55 +434,22 @@ jobs:
|
||||
body: successComment
|
||||
});
|
||||
|
||||
report-pending-publish:
|
||||
name: Report Pending Publish to Slack
|
||||
if: ${{ github.repository_owner == 'nrwl' }}
|
||||
needs:
|
||||
- resolve-required-data
|
||||
- build-freebsd
|
||||
- build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
continue-on-error: true # Don't fail the workflow if notification fails
|
||||
steps:
|
||||
- name: Send Slack notification
|
||||
uses: ravsamhq/notify-slack-action@be814b201e233b2dc673608aa46e5447c8ab13f2 # v11
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
notification_title: >-
|
||||
${{ needs.resolve-required-data.outputs.pr_number &&
|
||||
format('📦 PR #{0} Publish Pending Review', needs.resolve-required-data.outputs.pr_number) ||
|
||||
'📦 Publish Pending Review' }}
|
||||
message_format: >-
|
||||
${{ needs.resolve-required-data.outputs.pr_number &&
|
||||
format('Version {0} from PR #{1} by @{2} is being published to NPM - manual review is required',
|
||||
needs.resolve-required-data.outputs.version,
|
||||
needs.resolve-required-data.outputs.pr_number,
|
||||
needs.resolve-required-data.outputs.pr_author) ||
|
||||
format('Version {0} is being published to NPM - manual review is required',
|
||||
needs.resolve-required-data.outputs.version) }}
|
||||
footer: '<{run_url}|View Workflow Run>'
|
||||
mention_users: 'U9NPA6C90' # Jason
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }}
|
||||
|
||||
pr_failure_comment:
|
||||
# Run this job if it is a PR release, running on the nrwl origin, and any of the required jobs failed
|
||||
if: ${{ github.repository_owner == 'nrwl' && github.event.inputs.pr && always() && contains(needs.*.result, 'failure') }}
|
||||
needs: [ resolve-required-data, build, build-freebsd, publish ]
|
||||
name: (PR Release Failure Only) Create comment for failed PR release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Create comment for failed PR release
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# This script is intentionally kept inline (and e.g. not generated in publish-resolve-data.js)
|
||||
# to ensure that an error within the data generation itself is not missed.
|
||||
script: |
|
||||
const message = `
|
||||
Failed to publish a PR release of this pull request, triggered by @${{ github.triggering_actor }}.
|
||||
Failed to publish a PR release of this pull request, triggered by @${{ github.triggering_actor }}.
|
||||
See the failed workflow run at: https://github.com/nrwl/nx/actions/runs/${{ github.run_id }}
|
||||
`;
|
||||
await github.rest.issues.createComment({
|
||||
@@ -697,4 +458,3 @@ jobs:
|
||||
issue_number: ${{ github.event.inputs.pr }},
|
||||
body: message
|
||||
});
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
# This handles issues that need more info
|
||||
- name: stale-more-info-needed
|
||||
id: stale-more-info-needed
|
||||
uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0
|
||||
uses: actions/stale@v9.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 7
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
# This handles PRs that need to be rebased
|
||||
- name: stale-needs-rebase
|
||||
id: stale-needs-rebase
|
||||
uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0
|
||||
uses: actions/stale@v9.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 7
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
# This handles issues that do not have a repro
|
||||
- name: stale-repro-needed
|
||||
id: stale-repro-needed
|
||||
uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0
|
||||
uses: actions/stale@v9.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 7
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
|
||||
- name: stale-retry-with-latest
|
||||
id: stale-retry-with-latest
|
||||
uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0
|
||||
uses: actions/stale@v9.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 7
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
# This handles issues are really old and were made with a previous major
|
||||
- name: stale-bug
|
||||
id: stale-bug
|
||||
uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0
|
||||
uses: actions/stale@v9.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 180
|
||||
|
||||
+6
-95
@@ -2,8 +2,8 @@ node_modules
|
||||
/.idea
|
||||
/.fleet
|
||||
/.vscode
|
||||
/.cursor
|
||||
dist
|
||||
out-tsc
|
||||
/build
|
||||
/coverage
|
||||
./test
|
||||
@@ -13,8 +13,7 @@ tmp
|
||||
jest.debug.config.js
|
||||
.tool-versions
|
||||
/.nx-cache
|
||||
/.nx/cache
|
||||
/.nx/workspace-data
|
||||
/.nx
|
||||
/.verdaccio/build/local-registry
|
||||
/graph/client/src/assets/environment.js
|
||||
/graph/client/src/assets/dev/environment.js
|
||||
@@ -25,14 +24,7 @@ jest.debug.config.js
|
||||
/nx-dev/nx-dev/public/documentation
|
||||
/nx-dev/nx-dev/public/tutorials
|
||||
/nx-dev/nx-dev/public/images/open-graph
|
||||
/nx-dev/nx-dev/public/robots.txt
|
||||
/nx-dev/nx-dev/public/sitemap-0.xml
|
||||
/nx-dev/nx-dev/public/sitemap.xml
|
||||
|
||||
# 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
|
||||
@@ -44,7 +36,6 @@ CHANGELOG.md
|
||||
.next
|
||||
out
|
||||
|
||||
|
||||
# Angular Cache
|
||||
.angular
|
||||
|
||||
@@ -59,6 +50,8 @@ out
|
||||
|
||||
# Fix for issue when working on the repo in a dev container
|
||||
.pnpm-store
|
||||
.nx
|
||||
!.nx/workflows
|
||||
|
||||
.cargo/.package-cache
|
||||
.cargo/bin/
|
||||
@@ -69,89 +62,7 @@ out
|
||||
.profile
|
||||
.rustup/
|
||||
target
|
||||
.flattened-pom.xml
|
||||
dependency-reduced-pom.xml
|
||||
*.wasm
|
||||
/wasi-sdk*
|
||||
|
||||
*.config.timestamp*
|
||||
|
||||
storybook-static
|
||||
|
||||
# Ignore Gradle project-specific cache directory
|
||||
.gradle
|
||||
.kotlin
|
||||
|
||||
.claude/settings.local.json
|
||||
.claude/scheduled_tasks.lock
|
||||
CLAUDE.local.md
|
||||
|
||||
.cursor/mcp.json
|
||||
|
||||
# Added by Claude Task Master
|
||||
# Logs
|
||||
logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
dev-debug.log
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
# Environment variables
|
||||
.env
|
||||
!e2e/dotnet/.env
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.specstory/**
|
||||
.cursorindexingignore
|
||||
# OS specific
|
||||
# Task files
|
||||
/tasks.json
|
||||
/tasks
|
||||
|
||||
# Upstream docs local configuration (machine-specific)
|
||||
.upstreamdocs.local.json
|
||||
|
||||
# Netlify build artifacts
|
||||
.netlify
|
||||
|
||||
coverage
|
||||
|
||||
# Angular Rspack Specific Options
|
||||
packages/angular-rspack/coverage
|
||||
packages/angular-rspack-compiler/coverage
|
||||
|
||||
# Some Packages use a template to generate the correct README
|
||||
packages/angular-rspack/README.md
|
||||
packages/angular-rspack-compiler/README.md
|
||||
packages/dotnet/README.md
|
||||
packages/maven/README.md
|
||||
packages/nx/README.md
|
||||
|
||||
test-output
|
||||
test-results
|
||||
|
||||
# TypeScript build info files
|
||||
*.tsbuildinfo
|
||||
|
||||
# .NET build output
|
||||
/packages/dotnet/analyzer/bin
|
||||
/packages/dotnet/analyzer/obj
|
||||
/*.deb
|
||||
.nx/polygraph
|
||||
.claude/worktrees
|
||||
|
||||
.nx/self-healing
|
||||
# Nx Typings Output
|
||||
packages/nx/**/*.d.ts
|
||||
!packages/nx/src/utils/perf-hooks.d.ts
|
||||
!packages/nx/src/ai/set-up-ai-agents/schema.d.ts
|
||||
!packages/nx/src/native/index.d.ts
|
||||
e2e/**/*.d.ts
|
||||
e2e/**/*.d.ts.map
|
||||
vite.config.*.timestamp*
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
node ./scripts/commit-lint.js "$1"
|
||||
+1
-11
@@ -1,12 +1,2 @@
|
||||
# Skip if this is a worktree creation (previous ref is null)
|
||||
if [ "$1" = "0000000000000000000000000000000000000000" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Skip if this is a file checkout (not branch switch) - $3 would be 0
|
||||
if [ "$3" = "0" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
changedFiles="$(git diff-tree -r --name-only --no-commit-id $1 $2)"
|
||||
node ./scripts/notify-lockfile-changes.js $changedFiles
|
||||
node ./scripts/notify-lockfile-changes.js $changedFiles
|
||||
|
||||
+4
-1
@@ -1 +1,4 @@
|
||||
pnpm nx prepush --parallel 8 --tuiAutoExit 0
|
||||
pnpm check-lock-files
|
||||
pnpm check-commit
|
||||
pnpm documentation
|
||||
pnpm pretty-quick --check
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
NX_USE_V8_SERIALIZER=false
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
wrapperVersion=3.3.4
|
||||
distributionType=only-script
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/4.0.0-rc-5/apache-maven-4.0.0-rc-5-bin.zip
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"experimentalPolygraph": true
|
||||
}
|
||||
+126
-76
@@ -1,80 +1,130 @@
|
||||
common-env-vars: &common-env-vars
|
||||
GIT_AUTHOR_EMAIL: test@test.com
|
||||
GIT_AUTHOR_NAME: Test
|
||||
GIT_COMMITTER_EMAIL: test@test.com
|
||||
GIT_COMMITTER_NAME: Test
|
||||
SELECTED_PM: 'pnpm'
|
||||
NX_NATIVE_LOGGING: 'nx::native::db'
|
||||
# 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'
|
||||
|
||||
common-init-steps: &common-init-steps
|
||||
- name: Checkout
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/checkout/main.yaml'
|
||||
|
||||
- name: Cache restore
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/cache/main.yaml'
|
||||
inputs:
|
||||
key: 'pnpm-lock.yaml'
|
||||
paths: ~/.local/share/pnpm/store
|
||||
base-branch: 'master'
|
||||
|
||||
# reads mise.toml and installs toolchains needed for repo
|
||||
- name: Setup toolchains
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/install-mise/main.yaml'
|
||||
|
||||
- name: Verify toolchain versions
|
||||
script: |
|
||||
echo "mise: $(mise --version)"
|
||||
echo "node: $(node --version)"
|
||||
echo "pnpm: $(pnpm --version)"
|
||||
echo "bun: $(bun --version)"
|
||||
echo "rust: $(rustc --version) - $(cargo --version)"
|
||||
echo "dotnet: $(dotnet --version)"
|
||||
echo "java: $(javac --version)"
|
||||
|
||||
- name: Install system deps
|
||||
script: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ca-certificates lsof libvips-dev libglib2.0-dev libgirepository1.0-dev zip unzip
|
||||
|
||||
- name: Pnpm Install from lockfile
|
||||
script: |
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
- name: Install browsers
|
||||
script: |
|
||||
pnpm exec cypress install
|
||||
pnpm exec playwright install --with-deps
|
||||
|
||||
- name: Install rust deps
|
||||
script: |
|
||||
cargo fetch
|
||||
|
||||
- name: Install hyperfine
|
||||
script: |
|
||||
cargo install hyperfine
|
||||
|
||||
- name: Setup gradle
|
||||
script: |
|
||||
./gradlew wrapper
|
||||
./gradlew --version
|
||||
|
||||
- name: Configure git metadata (needed for lerna smoke tests)
|
||||
script: |
|
||||
git config --global user.email test@test.com
|
||||
git config --global user.name "Test Test"
|
||||
|
||||
launch-templates:
|
||||
linux-large:
|
||||
resource-class: 'docker_linux_amd64/large'
|
||||
image: 'us-east1-docker.pkg.dev/nxcloudoperations/nx-cloud/nx-agents-base-images:ubuntu22.04-node20.19-v1'
|
||||
env: *common-env-vars
|
||||
init-steps: *common-init-steps
|
||||
linux-medium:
|
||||
resource-class: 'docker_linux_amd64/medium+'
|
||||
image: 'ubuntu22.04-node20.11-v10'
|
||||
env:
|
||||
GIT_AUTHOR_EMAIL: test@test.com
|
||||
GIT_AUTHOR_NAME: Test
|
||||
GIT_COMMITTER_EMAIL: test@test.com
|
||||
GIT_COMMITTER_NAME: Test
|
||||
SELECTED_PM: 'pnpm'
|
||||
NPM_CONFIG_PREFIX: '/home/workflows/.npm-global'
|
||||
NX_NATIVE_LOGGING: 'nx::native::db'
|
||||
init-steps:
|
||||
- name: Checkout
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/checkout/main.yaml'
|
||||
- name: Cache restore
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/cache/main.yaml'
|
||||
inputs:
|
||||
key: 'pnpm-lock.yaml'
|
||||
paths: .pnpm-store
|
||||
base-branch: 'master'
|
||||
|
||||
- name: Install zip and unzip
|
||||
script: sudo apt-get -yqq install zip unzip
|
||||
|
||||
- name: Install bun
|
||||
script: |
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
echo "BUN_INSTALL=$HOME/.bun" >> $NX_CLOUD_ENV
|
||||
echo "PATH=$HOME/.bun/bin:$PATH" >> $NX_CLOUD_ENV
|
||||
|
||||
- name: Check bun
|
||||
script: |
|
||||
bun --version
|
||||
|
||||
- name: Install e2e deps
|
||||
script: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ca-certificates lsof libvips-dev libglib2.0-dev libgirepository1.0-dev
|
||||
- name: Install Pnpm
|
||||
script: |
|
||||
npm install -g pnpm@9.8.0
|
||||
|
||||
- name: Pnpm Install
|
||||
script: |
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
- name: Install Browsers
|
||||
script: |
|
||||
pnpm exec cypress install
|
||||
pnpm exec playwright install --with-deps
|
||||
|
||||
- name: Install Rust
|
||||
script: |
|
||||
curl --proto '=https' --tlsv1.3 https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
source "$HOME/.cargo/env"
|
||||
rustup toolchain install 1.70.0
|
||||
|
||||
- name: Configure git metadata (needed for lerna smoke tests)
|
||||
script: |
|
||||
git config --global user.email test@test.com
|
||||
git config --global user.name "Test Test"
|
||||
|
||||
- name: Load Cargo Env
|
||||
script: echo "PATH=$HOME/.cargo/bin:$PATH" >> $NX_CLOUD_ENV
|
||||
|
||||
linux-extra-large:
|
||||
resource-class: 'docker_linux_amd64/extra_large'
|
||||
image: 'us-east1-docker.pkg.dev/nxcloudoperations/nx-cloud/nx-agents-base-images:ubuntu22.04-node20.19-v1'
|
||||
env: *common-env-vars
|
||||
init-steps: *common-init-steps
|
||||
image: 'ubuntu22.04-node20.11-v10'
|
||||
env:
|
||||
GIT_AUTHOR_EMAIL: test@test.com
|
||||
GIT_AUTHOR_NAME: Test
|
||||
GIT_COMMITTER_EMAIL: test@test.com
|
||||
GIT_COMMITTER_NAME: Test
|
||||
SELECTED_PM: 'pnpm'
|
||||
NPM_CONFIG_PREFIX: '/home/workflows/.npm-global'
|
||||
NX_NATIVE_LOGGING: 'nx::native::db'
|
||||
init-steps:
|
||||
- name: Checkout
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/checkout/main.yaml'
|
||||
- name: Cache restore
|
||||
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/cache/main.yaml'
|
||||
inputs:
|
||||
key: 'pnpm-lock.yaml'
|
||||
paths: .pnpm-store
|
||||
base-branch: 'master'
|
||||
|
||||
- name: Install zip and unzip
|
||||
script: sudo apt-get -yqq install zip unzip
|
||||
|
||||
- name: Install bun
|
||||
script: |
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
echo "BUN_INSTALL=$HOME/.bun" >> $NX_CLOUD_ENV
|
||||
echo "PATH=$HOME/.bun/bin:$PATH" >> $NX_CLOUD_ENV
|
||||
|
||||
- name: Check bun
|
||||
script: |
|
||||
bun --version
|
||||
|
||||
- name: Install e2e deps
|
||||
script: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ca-certificates lsof libvips-dev libglib2.0-dev libgirepository1.0-dev
|
||||
- name: Install Pnpm
|
||||
script: |
|
||||
npm install -g pnpm@9.8.0
|
||||
|
||||
- name: Pnpm Install
|
||||
script: |
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
- name: Install Browsers
|
||||
script: |
|
||||
pnpm exec cypress install
|
||||
pnpm exec playwright install --with-deps
|
||||
|
||||
- name: Install Rust
|
||||
script: |
|
||||
curl --proto '=https' --tlsv1.3 https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
source "$HOME/.cargo/env"
|
||||
rustup toolchain install 1.70.0
|
||||
|
||||
- name: Configure git metadata (needed for lerna smoke tests)
|
||||
script: |
|
||||
git config --global user.email test@test.com
|
||||
git config --global user.name "Test Test"
|
||||
|
||||
- name: Load Cargo Env
|
||||
script: echo "PATH=$HOME/.cargo/bin:$PATH" >> $NX_CLOUD_ENV
|
||||
|
||||
@@ -1,125 +1,24 @@
|
||||
distribute-on:
|
||||
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
|
||||
default: auto linux-medium, 1 linux-extra-large
|
||||
assignment-rules:
|
||||
- projects:
|
||||
- e2e-gradle
|
||||
targets:
|
||||
- e2e-ci**
|
||||
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
|
||||
- workspace
|
||||
- remix
|
||||
- nx-maven-plugin
|
||||
targets:
|
||||
- install
|
||||
- test
|
||||
run-on:
|
||||
- agent: linux-large
|
||||
parallelism: 1
|
||||
- agent: linux-extra-large
|
||||
parallelism: 1
|
||||
|
||||
- projects:
|
||||
- e2e-release
|
||||
- e2e-nuxt
|
||||
- e2e-web
|
||||
- e2e-eslint
|
||||
- e2e-remix
|
||||
- e2e-cypress
|
||||
- e2e-docker
|
||||
- e2e-js
|
||||
- e2e-nx
|
||||
- e2e-nx-init
|
||||
- e2e-dotnet
|
||||
- e2e-workspace-create
|
||||
- e2e-rollup
|
||||
targets:
|
||||
- e2e-ci**
|
||||
run-on:
|
||||
- agent: linux-large
|
||||
parallelism: 1
|
||||
- agent: linux-extra-large
|
||||
parallelism: 2
|
||||
|
||||
# All other e2e tests can run in parallel
|
||||
- targets:
|
||||
- e2e-ci**
|
||||
run-on:
|
||||
- agent: linux-large
|
||||
parallelism: 2
|
||||
- agent: linux-extra-large
|
||||
parallelism: 3
|
||||
|
||||
- targets:
|
||||
- bench:*
|
||||
run-on:
|
||||
- agent: linux-large
|
||||
parallelism: 1
|
||||
|
||||
# These projects should not need to be isolated.
|
||||
- projects:
|
||||
- nx-dev
|
||||
- astro-docs
|
||||
targets:
|
||||
- build*
|
||||
run-on:
|
||||
- agent: linux-extra-large
|
||||
parallelism: 1
|
||||
- projects:
|
||||
- angular
|
||||
- react
|
||||
targets:
|
||||
- test
|
||||
run-on:
|
||||
- agent: linux-extra-large
|
||||
parallelism: 1
|
||||
|
||||
- targets:
|
||||
- lint
|
||||
run-on:
|
||||
- agent: linux-large
|
||||
parallelism: 6
|
||||
- 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
|
||||
- agent: linux-medium
|
||||
parallelism: 6
|
||||
|
||||
- targets:
|
||||
- "*"
|
||||
- '*'
|
||||
run-on:
|
||||
- agent: linux-large
|
||||
- agent: linux-medium
|
||||
parallelism: 3
|
||||
- agent: linux-extra-large
|
||||
parallelism: 3
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
exclude-reads:
|
||||
- packages/nx/src/native/*.node
|
||||
- 'dist/target/**'
|
||||
exclude-writes:
|
||||
- '**/.swc/**'
|
||||
- 'dist/target/**'
|
||||
|
||||
task-exclusions:
|
||||
- target: lint
|
||||
exclude-reads:
|
||||
- '**/dist/**/*.json'
|
||||
# TODO: populate-local-registry-storage is doing too much — it reads all build
|
||||
# outputs and writes version-bumped packages across the entire workspace during
|
||||
# nx-release. We're reworking this task to have more focused I/O and will fix
|
||||
# the inputs/outputs properly after that.
|
||||
- project: '@nx/nx-source'
|
||||
target: populate-local-registry-storage
|
||||
exclude-reads:
|
||||
- '**'
|
||||
exclude-writes:
|
||||
- '**'
|
||||
@@ -1,479 +0,0 @@
|
||||
---
|
||||
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
|
||||
@@ -1,437 +0,0 @@
|
||||
---
|
||||
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
|
||||
```
|
||||
@@ -1,437 +0,0 @@
|
||||
---
|
||||
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
|
||||
```
|
||||
@@ -1,228 +0,0 @@
|
||||
---
|
||||
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
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
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`.
|
||||
@@ -1,58 +0,0 @@
|
||||
---
|
||||
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`)
|
||||
@@ -1,186 +0,0 @@
|
||||
---
|
||||
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'
|
||||
```
|
||||
@@ -1,13 +0,0 @@
|
||||
# Enable pre/post-install which are disabled by default. Installing peer deps which is also disabled by default
|
||||
auto-install-peers=true
|
||||
enable-pre-post-scripts=true
|
||||
|
||||
# Enable lifecycle scripts for specific packages that require them (like post-install)
|
||||
enable-scripts=@napi-rs/canvas,sharp,@swc/core,@swc/cli,@swc-node/register,esbuild
|
||||
|
||||
# Compatibility
|
||||
strict-peer-dependencies=false
|
||||
lockfile-version-strict=false
|
||||
|
||||
# Consistency across environments
|
||||
use-node-version=20.19.0
|
||||
@@ -13,7 +13,6 @@ packages/express/src/schematics/**/files/**/*.json
|
||||
packages/nest/src/schematics/**/files/**/*.json
|
||||
packages/react/src/schematics/**/files/**/*.json
|
||||
packages/jest/src/schematics/**/files/**/*.json
|
||||
packages/gradle/project-graph/build/**/*.*
|
||||
packages/nx/src/plugins/js/lock-file/__fixtures__/**/*.*
|
||||
packages/**/schematics/**/files/**/*.html
|
||||
packages/**/generators/**/files/**/*.html
|
||||
@@ -51,11 +50,3 @@ CODEOWNERS
|
||||
/.nx/workflows/dynamic-changesets.yaml
|
||||
_files
|
||||
_solution
|
||||
|
||||
# this file uses TS import attributes which the current prettier version does not support
|
||||
tools/documentation/create-embeddings/src/main.mts
|
||||
|
||||
.nx/self-healing
|
||||
|
||||
# Inlined from `@yarnpkg/parser` keep as is
|
||||
packages/nx/src/utils/yarn-syml/syml-grammar.js
|
||||
|
||||
+1
-10
@@ -1,14 +1,5 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"endOfLine": "lf",
|
||||
"trailingComma": "es5",
|
||||
"plugins": ["prettier-plugin-tailwindcss"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.mdoc",
|
||||
"options": {
|
||||
"parser": "markdown"
|
||||
}
|
||||
}
|
||||
]
|
||||
"plugins": ["prettier-plugin-tailwindcss"]
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"include": ["apps/**/*", "libs/**/*", "packages/**/*"],
|
||||
"exclude": [
|
||||
"**/*.spec.*",
|
||||
"**/test/**/*",
|
||||
"**/__tests__/**/*",
|
||||
"**/*.test.*",
|
||||
"node_modules/**/*"
|
||||
]
|
||||
}
|
||||
@@ -5,7 +5,7 @@ auth:
|
||||
htpasswd:
|
||||
file: ./htpasswd
|
||||
|
||||
max_body_size: 20mb
|
||||
max_body_size: 15mb
|
||||
|
||||
# a list of other known repositories we can talk to
|
||||
uplinks:
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
When responding to queries about this repository:
|
||||
|
||||
1. Suggest relevant commands from the "Essential Commands" section when applicable
|
||||
2. Highlight Nx's focus on monorepos and its key features like smart task execution, code generation, and project graph
|
||||
analysis
|
||||
3. Mention the plugin ecosystem and support for various frameworks when relevant
|
||||
4. Emphasize the importance of running the full validation suite before committing changes
|
||||
|
||||
Always strive to provide accurate, helpful responses that align with the best practices and workflows described in this
|
||||
file.
|
||||
|
||||
## Documentation Contributions
|
||||
|
||||
When working on Nx documentation, all documentation content lives in the `astro-docs/` folder. This is the new Astro-based documentation site built with Starlight.
|
||||
|
||||
**Important**: Before making any documentation changes, read the `astro-docs/README.md` file for detailed guidance on:
|
||||
|
||||
- Project structure and architecture
|
||||
- Content types (regular docs, dynamic plugin docs, CLI docs)
|
||||
- Available Markdoc tags for rich content
|
||||
- Development workflow and commands
|
||||
- Sidebar management
|
||||
|
||||
### Quick Reference
|
||||
|
||||
- Documentation content: `astro-docs/src/content/docs/`
|
||||
- Use `.mdoc` (Markdoc) or `.mdx` format for documentation files
|
||||
- Run `nx serve astro-docs` to start the local dev server
|
||||
- Sidebar structure is defined in `astro-docs/sidebar.mts`
|
||||
|
||||
## GitHub Issue Response Mode
|
||||
|
||||
When responding to GitHub issues, determine your approach based on how the request is phrased:
|
||||
|
||||
### Plan-First Mode (Default)
|
||||
|
||||
Use this approach when users ask you to:
|
||||
|
||||
- "analyze", "investigate", "assess", "review", "examine", or "plan"
|
||||
- Or when the request is ambiguous
|
||||
|
||||
In this mode:
|
||||
|
||||
1. Provide a detailed analysis of the issue
|
||||
2. Create a comprehensive implementation plan
|
||||
3. Break down the solution into clear steps
|
||||
4. Then please post the plan as a comment on the issue
|
||||
|
||||
### Immediate Implementation Mode
|
||||
|
||||
Use this approach when users ask you to:
|
||||
|
||||
- "fix", "implement", "solve", "build", "create", "update", or "add"
|
||||
- Or when they explicitly request immediate action
|
||||
|
||||
In this mode:
|
||||
|
||||
1. Analyze the issue quickly
|
||||
2. Implement the complete solution immediately
|
||||
3. Make all necessary code changes. Please make multiple commits so that the changes are easier to review.
|
||||
4. Run appropriate tests and validation
|
||||
5. If the tests, are not passing, please fix the issues and continue doing this up to 3 more times until the tests pass
|
||||
6. Once the tests pass, push a branch and then suggest opening a PR which has a description of the changes made, and
|
||||
that
|
||||
it make sure that it explicitly says "Fixes #ISSUE_NUMBER" to automatically close the issue when the PR is merged.
|
||||
|
||||
## Avoid making changes to generated files
|
||||
|
||||
Files under `generated` directories are generated based on a different source file and should not be modified directly.
|
||||
Find the underlying source and modify that instead.
|
||||
|
||||
## Essential Commands
|
||||
|
||||
### Code Formatting
|
||||
|
||||
After code changes are made, please make sure to format the files with prettier via `npx prettier -- FILE_NAME`
|
||||
|
||||
### Pre-push Validation
|
||||
|
||||
```bash
|
||||
# Full validation suite - run before committing
|
||||
nx prepush
|
||||
```
|
||||
|
||||
If the prepush validation suite fails, please fix the issues before proceeding with your work. This ensures that all
|
||||
code adheres to the project's standards and passes all tests. DO NOT make a new commit to fix these issues. Instead,
|
||||
amend the current commit.
|
||||
|
||||
### Testing Changes
|
||||
|
||||
After code changes are made, first test the specific project where the changes were made:
|
||||
|
||||
```bash
|
||||
nx run-many -t test,build,lint -p PROJECT_NAME
|
||||
```
|
||||
|
||||
After verifying the individual project, validate that the changes in projects which have been affected:
|
||||
|
||||
```bash
|
||||
# Test only affected projects (recommended for development)
|
||||
nx affected -t build,test,lint
|
||||
```
|
||||
|
||||
As the last step, run the e2e tests to fully ensure that changes are valid:
|
||||
|
||||
```bash
|
||||
# Run affected e2e tests (recommended for development)
|
||||
nx affected -t e2e-local
|
||||
```
|
||||
|
||||
## Fixing GitHub Issues
|
||||
|
||||
When working on a GitHub issue, follow this systematic approach:
|
||||
|
||||
### 1. Get Issue Details
|
||||
|
||||
```bash
|
||||
# Get issue details using GitHub CLI (replace ISSUE_NUMBER with actual number)
|
||||
gh issue view ISSUE_NUMBER
|
||||
|
||||
# View multiple issues efficiently in one command
|
||||
gh issue list --limit 50 --json number,title,state,labels,assignees,updatedAt,body --jq '.[] | select(.number == 123 or .number == 456 or .number == 789)'
|
||||
|
||||
# Or filter by specific criteria to get multiple related issues
|
||||
gh issue list --label "bug" --state "open" --json number,title,body,labels --jq '.[]'
|
||||
gh issue list --assignee "@me" --json number,title,body,state --jq '.[]'
|
||||
```
|
||||
|
||||
**Tip**: Instead of running `gh issue view` multiple times, use `gh issue list` with JSON output and filtering to gather
|
||||
information about multiple issues in a single command. This is much more efficient than viewing issues one at a time.
|
||||
|
||||
**Always provide clickable links**: When discussing GitHub issues or PRs, always include the full GitHub URL so the user
|
||||
can easily open them in their browser. For example:
|
||||
|
||||
- Issue #12345: https://github.com/nrwl/nx/issues/12345
|
||||
- PR #67890: https://github.com/nrwl/nx/pull/67890
|
||||
|
||||
When cloning reproduction repos, please clone within `./tmp/claude/repro-ISSUE_NUMBER`
|
||||
|
||||
### 2. Analyze the Plan
|
||||
|
||||
- Look for a plan or implementation details in the issue description
|
||||
- Check comments for additional context or clarification
|
||||
- Identify affected projects and components
|
||||
|
||||
### 3. Implement the Solution
|
||||
|
||||
- Follow the plan outlined in the issue
|
||||
- Make focused changes that address the specific problem
|
||||
- Ensure code follows existing patterns and conventions
|
||||
|
||||
### 4. Run Full Validation
|
||||
|
||||
Use the testing workflow from the "Essential Commands" section.
|
||||
|
||||
### 5. Submit Pull Request
|
||||
|
||||
- Create a descriptive PR title that references the issue
|
||||
- **Always fill in the PR template** - don't leave it empty
|
||||
- Include "Fixes #ISSUE_NUMBER" in the PR description
|
||||
- Provide a clear summary of changes made
|
||||
- Request appropriate reviewers
|
||||
|
||||
## Pull Request Template
|
||||
|
||||
**IMPORTANT**: When creating a pull request, you MUST fill in the template found in `.github/PULL_REQUEST_TEMPLATE.md`.
|
||||
Do not leave the template sections empty. The template includes:
|
||||
|
||||
### Required Sections
|
||||
|
||||
1. **Current Behavior**: Describe the behavior we have today
|
||||
2. **Expected Behavior**: Describe the behavior we should expect with the changes in this PR
|
||||
3. **Related Issue(s)**: Link the issue being fixed so it gets closed when the PR is merged
|
||||
|
||||
### Template Format
|
||||
|
||||
```markdown
|
||||
## Current Behavior
|
||||
|
||||
<!-- This is the behavior we have today -->
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
<!-- This is the behavior we should expect with the changes in this PR -->
|
||||
|
||||
## Related Issue(s)
|
||||
|
||||
<!-- Please link the issue being fixed so it gets closed when this is merged. -->
|
||||
|
||||
Fixes #ISSUE_NUMBER
|
||||
```
|
||||
|
||||
### Guidelines
|
||||
|
||||
- Ensure your commit message follows the conventional commit format (use `pnpm commit`)
|
||||
- Use `fix:`, `feat:`, `chore:`, etc. as appropriate types.
|
||||
- Scope is **required** for all commits. Possible scopes are listed in `scripts/commitizen.js`.
|
||||
- Read the submission guidelines in CONTRIBUTING.md before posting
|
||||
- For complex changes, you can request a dedicated Nx release by mentioning the Nx team
|
||||
- Always link the related issue using "Fixes #ISSUE_NUMBER" to automatically close it when merged
|
||||
|
||||
<!-- nx configuration start-->
|
||||
<!-- Leave the start & end comments to automatically receive updates. -->
|
||||
|
||||
## General Guidelines for working with Nx
|
||||
|
||||
- For navigating/exploring the workspace, invoke the `nx-workspace` skill first - it has patterns for querying projects, targets, and dependencies
|
||||
- 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
|
||||
- Prefix nx commands with the workspace's package manager (e.g., `pnpm nx build`, `npm exec nx test`) - avoids using globally installed CLI
|
||||
- You have access to the Nx MCP server and its tools, use them to help the user
|
||||
- For Nx plugin best practices, check `node_modules/@nx/<plugin>/PLUGIN.md`. Not all plugins have this file - proceed without it if unavailable.
|
||||
- NEVER guess CLI flags - always check nx_docs or `--help` first when unsure
|
||||
|
||||
## Scaffolding & Generators
|
||||
|
||||
- For scaffolding tasks (creating apps, libs, project structure, setup), ALWAYS invoke the `nx-generate` skill FIRST before exploring or calling MCP tools
|
||||
|
||||
## When to use nx_docs
|
||||
|
||||
- USE for: advanced config options, unfamiliar flags, migration guides, plugin configuration, edge cases
|
||||
- DON'T USE for: basic generator syntax (`nx g @nx/react:app`), standard commands, things you already know
|
||||
- The `nx-generate` skill handles generator discovery internally - don't call nx_docs just to look up generator syntax
|
||||
|
||||
<!-- nx configuration end-->
|
||||
@@ -1,226 +0,0 @@
|
||||
When responding to queries about this repository:
|
||||
|
||||
1. Suggest relevant commands from the "Essential Commands" section when applicable
|
||||
2. Highlight Nx's focus on monorepos and its key features like smart task execution, code generation, and project graph
|
||||
analysis
|
||||
3. Mention the plugin ecosystem and support for various frameworks when relevant
|
||||
4. Emphasize the importance of running the full validation suite before committing changes
|
||||
|
||||
Always strive to provide accurate, helpful responses that align with the best practices and workflows described in this
|
||||
file.
|
||||
|
||||
## Documentation Contributions
|
||||
|
||||
When working on Nx documentation, all documentation content lives in the `astro-docs/` folder. This is the new Astro-based documentation site built with Starlight.
|
||||
|
||||
**Important**: Before making any documentation changes, read the `astro-docs/README.md` file for detailed guidance on:
|
||||
|
||||
- Project structure and architecture
|
||||
- Content types (regular docs, dynamic plugin docs, CLI docs)
|
||||
- Available Markdoc tags for rich content
|
||||
- Development workflow and commands
|
||||
- Sidebar management
|
||||
|
||||
**MANDATORY**: After editing any file in `astro-docs/src/content/`, run the `nx-docs-style-check` skill. No exceptions.
|
||||
|
||||
### Quick Reference
|
||||
|
||||
- Documentation content: `astro-docs/src/content/docs/`
|
||||
- Use `.mdoc` (Markdoc) or `.mdx` format for documentation files
|
||||
- Run `nx serve astro-docs` to start the local dev server
|
||||
- Sidebar structure is defined in `astro-docs/sidebar.mts`
|
||||
|
||||
## GitHub Issue Response Mode
|
||||
|
||||
When responding to GitHub issues, determine your approach based on how the request is phrased:
|
||||
|
||||
### Plan-First Mode (Default)
|
||||
|
||||
Use this approach when users ask you to:
|
||||
|
||||
- "analyze", "investigate", "assess", "review", "examine", or "plan"
|
||||
- Or when the request is ambiguous
|
||||
|
||||
In this mode:
|
||||
|
||||
1. Provide a detailed analysis of the issue
|
||||
2. Create a comprehensive implementation plan
|
||||
3. Break down the solution into clear steps
|
||||
4. Then please post the plan as a comment on the issue
|
||||
|
||||
### Immediate Implementation Mode
|
||||
|
||||
Use this approach when users ask you to:
|
||||
|
||||
- "fix", "implement", "solve", "build", "create", "update", or "add"
|
||||
- Or when they explicitly request immediate action
|
||||
|
||||
In this mode:
|
||||
|
||||
1. Analyze the issue quickly
|
||||
2. Implement the complete solution immediately
|
||||
3. Make all necessary code changes. Please make multiple commits so that the changes are easier to review.
|
||||
4. Run appropriate tests and validation
|
||||
5. If the tests, are not passing, please fix the issues and continue doing this up to 3 more times until the tests pass
|
||||
6. Once the tests pass, push a branch and then suggest opening a PR which has a description of the changes made, and
|
||||
that
|
||||
it make sure that it explicitly says "Fixes #ISSUE_NUMBER" to automatically close the issue when the PR is merged.
|
||||
|
||||
## Avoid making changes to generated files
|
||||
|
||||
Files under `generated` directories are generated based on a different source file and should not be modified directly.
|
||||
Find the underlying source and modify that instead.
|
||||
|
||||
## Essential Commands
|
||||
|
||||
### Code Formatting
|
||||
|
||||
After code changes are made, please make sure to format the files with prettier via `npx prettier -- FILE_NAME`
|
||||
|
||||
### Pre-push Validation
|
||||
|
||||
```bash
|
||||
# Full validation suite - run before committing
|
||||
nx prepush
|
||||
```
|
||||
|
||||
If the prepush validation suite fails, please fix the issues before proceeding with your work. This ensures that all
|
||||
code adheres to the project's standards and passes all tests. DO NOT make a new commit to fix these issues. Instead,
|
||||
amend the current commit.
|
||||
|
||||
### Testing Changes
|
||||
|
||||
After code changes are made, first test the specific project where the changes were made:
|
||||
|
||||
```bash
|
||||
nx run-many -t test,build,lint -p PROJECT_NAME
|
||||
```
|
||||
|
||||
After verifying the individual project, validate that the changes in projects which have been affected:
|
||||
|
||||
```bash
|
||||
# Test only affected projects (recommended for development)
|
||||
nx affected -t build,test,lint
|
||||
```
|
||||
|
||||
As the last step, run the e2e tests to fully ensure that changes are valid:
|
||||
|
||||
```bash
|
||||
# Run affected e2e tests (recommended for development)
|
||||
nx affected -t e2e-local
|
||||
```
|
||||
|
||||
## Fixing GitHub Issues
|
||||
|
||||
When working on a GitHub issue, follow this systematic approach:
|
||||
|
||||
### 1. Get Issue Details
|
||||
|
||||
```bash
|
||||
# Get issue details using GitHub CLI (replace ISSUE_NUMBER with actual number)
|
||||
gh issue view ISSUE_NUMBER
|
||||
|
||||
# View multiple issues efficiently in one command
|
||||
gh issue list --limit 50 --json number,title,state,labels,assignees,updatedAt,body --jq '.[] | select(.number == 123 or .number == 456 or .number == 789)'
|
||||
|
||||
# Or filter by specific criteria to get multiple related issues
|
||||
gh issue list --label "bug" --state "open" --json number,title,body,labels --jq '.[]'
|
||||
gh issue list --assignee "@me" --json number,title,body,state --jq '.[]'
|
||||
```
|
||||
|
||||
**Tip**: Instead of running `gh issue view` multiple times, use `gh issue list` with JSON output and filtering to gather
|
||||
information about multiple issues in a single command. This is much more efficient than viewing issues one at a time.
|
||||
|
||||
**Always provide clickable links**: When discussing GitHub issues or PRs, always include the full GitHub URL so the user
|
||||
can easily open them in their browser. For example:
|
||||
|
||||
- Issue #12345: https://github.com/nrwl/nx/issues/12345
|
||||
- PR #67890: https://github.com/nrwl/nx/pull/67890
|
||||
|
||||
When cloning reproduction repos, please clone within `./tmp/claude/repro-ISSUE_NUMBER`
|
||||
|
||||
### 2. Analyze the Plan
|
||||
|
||||
- Look for a plan or implementation details in the issue description
|
||||
- Check comments for additional context or clarification
|
||||
- Identify affected projects and components
|
||||
|
||||
### 3. Implement the Solution
|
||||
|
||||
- Follow the plan outlined in the issue
|
||||
- Make focused changes that address the specific problem
|
||||
- Ensure code follows existing patterns and conventions
|
||||
|
||||
### 4. Run Full Validation
|
||||
|
||||
Use the testing workflow from the "Essential Commands" section.
|
||||
|
||||
### 5. Submit Pull Request
|
||||
|
||||
- Create a descriptive PR title that references the issue
|
||||
- **Always fill in the PR template** - don't leave it empty
|
||||
- Include "Fixes #ISSUE_NUMBER" in the PR description
|
||||
- Provide a clear summary of changes made
|
||||
- Request appropriate reviewers
|
||||
|
||||
## Pull Request Template
|
||||
|
||||
**IMPORTANT**: When creating a pull request, you MUST fill in the template found in `.github/PULL_REQUEST_TEMPLATE.md`.
|
||||
Do not leave the template sections empty. The template includes:
|
||||
|
||||
### Required Sections
|
||||
|
||||
1. **Current Behavior**: Describe the behavior we have today
|
||||
2. **Expected Behavior**: Describe the behavior we should expect with the changes in this PR
|
||||
3. **Related Issue(s)**: Link the issue being fixed so it gets closed when the PR is merged
|
||||
|
||||
### Template Format
|
||||
|
||||
```markdown
|
||||
## Current Behavior
|
||||
|
||||
<!-- This is the behavior we have today -->
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
<!-- This is the behavior we should expect with the changes in this PR -->
|
||||
|
||||
## Related Issue(s)
|
||||
|
||||
<!-- Please link the issue being fixed so it gets closed when this is merged. -->
|
||||
|
||||
Fixes #ISSUE_NUMBER
|
||||
```
|
||||
|
||||
### Guidelines
|
||||
|
||||
- Ensure your commit message follows the conventional commit format (use `pnpm commit`)
|
||||
- Use `fix:`, `feat:`, `chore:`, etc. as appropriate types.
|
||||
- Scope is **required** for all commits. Possible scopes are listed in `scripts/commitizen.js`.
|
||||
- Read the submission guidelines in CONTRIBUTING.md before posting
|
||||
- For complex changes, you can request a dedicated Nx release by mentioning the Nx team
|
||||
- Always link the related issue using "Fixes #ISSUE_NUMBER" to automatically close it when merged
|
||||
|
||||
<!-- nx configuration start-->
|
||||
<!-- Leave the start & end comments to automatically receive updates. -->
|
||||
|
||||
## General Guidelines for working with Nx
|
||||
|
||||
- For navigating/exploring the workspace, invoke the `nx-workspace` skill first - it has patterns for querying projects, targets, and dependencies
|
||||
- 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
|
||||
- Prefix nx commands with the workspace's package manager (e.g., `pnpm nx build`, `npm exec nx test`) - avoids using globally installed CLI
|
||||
- You have access to the Nx MCP server and its tools, use them to help the user
|
||||
- For Nx plugin best practices, check `node_modules/@nx/<plugin>/PLUGIN.md`. Not all plugins have this file - proceed without it if unavailable.
|
||||
- NEVER guess CLI flags - always check nx_docs or `--help` first when unsure
|
||||
|
||||
## Scaffolding & Generators
|
||||
|
||||
- For scaffolding tasks (creating apps, libs, project structure, setup), ALWAYS invoke the `nx-generate` skill FIRST before exploring or calling MCP tools
|
||||
|
||||
## When to use nx_docs
|
||||
|
||||
- USE for: advanced config options, unfamiliar flags, migration guides, plugin configuration, edge cases
|
||||
- DON'T USE for: basic generator syntax (`nx g @nx/react:app`), standard commands, things you already know
|
||||
- The `nx-generate` skill handles generator discovery internally - don't call nx_docs just to look up generator syntax
|
||||
|
||||
<!-- nx configuration end-->
|
||||
+200
-1
@@ -1,2 +1,201 @@
|
||||
# Any file not covered by a rule below, will default to Jason + Victor and a few select others.
|
||||
* @nrwl/nx-cli-reviewers
|
||||
* @FrozenPandaz @vsavkin
|
||||
/packages/**/* @FrozenPandaz @vsavkin @AgentEnder @jaysoo @JamesHenry
|
||||
/e2e/**/* @FrozenPandaz @vsavkin @AgentEnder @jaysoo @JamesHenry
|
||||
/scripts/**/* @FrozenPandaz @vsavkin @AgentEnder @jaysoo @JamesHenry
|
||||
/tools/**/* @FrozenPandaz @vsavkin @AgentEnder @jaysoo @JamesHenry
|
||||
package.json @nrwl/nx-core-reviewers
|
||||
pnpm-lock.yaml @nrwl/nx-core-reviewers
|
||||
rust-toolchain @nrwl/nx-native-reviewers
|
||||
|
||||
# Docs Site + Graph
|
||||
/docs @nrwl/nx-docs-reviewers
|
||||
/docs/nx-cloud @StalkAltan @rarmatei @nixallover @nrwl/nx-docs-reviewers
|
||||
/graph/** @philipjfulcher @FrozenPandaz @bcabanes @MaxKless @xiongemi
|
||||
/images @nrwl/nx-docs-reviewers
|
||||
/nx-dev/** @nrwl/nx-docs-reviewers
|
||||
/typedoc-theme @nrwl/nx-docs-reviewers
|
||||
|
||||
# Plugin Verticals
|
||||
|
||||
## Angular
|
||||
/docs/generated/packages/angular/** @nrwl/nx-angular-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/shared/packages/angular/** @nrwl/nx-angular-reviewers @nrwl/nx-docs-reviewers
|
||||
/packages/angular/** @nrwl/nx-angular-reviewers
|
||||
/e2e/angular/** @nrwl/nx-angular-reviewers
|
||||
/packages/angular/plugins/component-testing.ts @nrwl/nx-angular-reviewers @nrwl/nx-testing-tools-reviewers
|
||||
/packages/angular/src/generators/cypress-component-configuration/** @nrwl/nx-angular-reviewers @nrwl/nx-testing-tools-reviewers
|
||||
/packages/angular/src/generators/component-test/** @nrwl/nx-angular-reviewers @nrwl/nx-testing-tools-reviewers
|
||||
|
||||
## React
|
||||
/docs/generated/packages/react/** @nrwl/nx-react-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/generated/packages/next/** @nrwl/nx-react-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/shared/packages/react/** @nrwl/nx-react-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/shared/packages/next/** @nrwl/nx-react-reviewers @nrwl/nx-docs-reviewers
|
||||
/packages/react/** @nrwl/nx-react-reviewers
|
||||
/e2e/react/** @nrwl/nx-react-reviewers
|
||||
/packages/next/** @nrwl/nx-react-reviewers
|
||||
/e2e/next/** @nrwl/nx-react-reviewers
|
||||
/packages/react/plugins/component-testing/** @nrwl/nx-react-reviewers @nrwl/nx-testing-tools-reviewers
|
||||
/packages/react/src/generators/cypress-component-configuration/** @nrwl/nx-react-reviewers @nrwl/nx-testing-tools-reviewers
|
||||
/packages/react/src/generators/component-test/** @nrwl/nx-react-reviewers @nrwl/nx-testing-tools-reviewers
|
||||
|
||||
# React Native
|
||||
/docs/generated/packages/detox/** @nrwl/nx-react-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/generated/packages/expo/** @nrwl/nx-react-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/generated/packages/react-native/** @nrwl/nx-react-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/shared/packages/react-native/** @nrwl/nx-react-reviewers @nrwl/nx-docs-reviewers
|
||||
/packages/detox/** @nrwl/nx-react-reviewers
|
||||
/e2e/detox/** @nrwl/nx-react-reviewers
|
||||
/packages/expo/** @nrwl/nx-react-reviewers
|
||||
/e2e/expo/** @nrwl/nx-react-reviewers
|
||||
/packages/react-native/** @nrwl/nx-react-reviewers
|
||||
/e2e/react-native/** @nrwl/nx-react-reviewers
|
||||
|
||||
## remix
|
||||
/docs/generated/packages/remix/** @nrwl/nx-react-reviewers @nrwl/nx-docs-reviewers @Coly010
|
||||
/packages/remix/** @nrwl/nx-react-reviewers @Coly010
|
||||
/e2e/remix/** @nrwl/nx-react-reviewers @Coly010
|
||||
|
||||
# Vue
|
||||
/packages/vue/** @nrwl/nx-vue-reviewers
|
||||
/e2e/vue/** @nrwl/nx-vue-reviewers
|
||||
/packages/nuxt/** @nrwl/nx-vue-reviewers
|
||||
/e2e/nuxt/** @nrwl/nx-vue-reviewers
|
||||
|
||||
## Node
|
||||
/docs/generated/packages/node/** @nrwl/nx-node-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/generated/packages/nest/** @nrwl/nx-node-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/generated/packages/express/** @nrwl/nx-node-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/shared/packages/node/** @nrwl/nx-node-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/shared/packages/express/** @nrwl/nx-node-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/shared/packages/nest/** @nrwl/nx-node-reviewers @FrozenPandaz @nrwl/nx-docs-reviewers
|
||||
/packages/node/** @nrwl/nx-node-reviewers
|
||||
/packages/express/** @nrwl/nx-node-reviewers
|
||||
/packages/nest/** @nrwl/nx-node-reviewers
|
||||
/e2e/node/** @nrwl/nx-node-reviewers
|
||||
|
||||
## JS
|
||||
/docs/generated/packages/js/** @nrwl/nx-js-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/generated/packages/web/** @nrwl/nx-js-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/generated/packages/webpack/** @nrwl/nx-js-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/generated/packages/rspack/** @nrwl/nx-js-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/generated/packages/esbuild/** @nrwl/nx-js-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/generated/packages/rollup/** @nrwl/nx-js-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/generated/packages/vite/** @nrwl/nx-js-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/shared/packages/js/** @nrwl/nx-js-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/shared/packages/web/** @nrwl/nx-js-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/shared/packages/webpack/** @nrwl/nx-js-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/shared/packages/rspack/** @nrwl/nx-js-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/shared/packages/esbuild/** @nrwl/nx-js-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/shared/packages/vite/** @nrwl/nx-js-reviewers @nrwl/nx-docs-reviewers
|
||||
/packages/js/** @nrwl/nx-js-reviewers
|
||||
/e2e/js/** @nrwl/nx-js-reviewers
|
||||
/packages/web/** @nrwl/nx-js-reviewers
|
||||
/e2e/web/** @nrwl/nx-js-reviewers
|
||||
/packages/webpack/** @nrwl/nx-js-reviewers
|
||||
/e2e/webpack/** @nrwl/nx-js-reviewers
|
||||
/packages/rspack/** @nrwl/nx-js-reviewers
|
||||
/e2e/rspack/** @nrwl/nx-js-reviewers
|
||||
/packages/rsbuild/** @nrwl/nx-js-reviewers
|
||||
/packages/esbuild/** @nrwl/nx-js-reviewers
|
||||
/e2e/esbuild/** @nrwl/nx-js-reviewers
|
||||
/packages/rollup/** @nrwl/nx-js-reviewers
|
||||
/e2e/rollup/** @nrwl/nx-js-reviewers
|
||||
/packages/vite/** @nrwl/nx-js-reviewers
|
||||
/e2e/vite/** @nrwl/nx-js-reviewers
|
||||
|
||||
## Module Federation
|
||||
/packages/module-federation/** @nrwl/nx-js-reviewers
|
||||
|
||||
## Tools
|
||||
/docs/generated/packages/cypress/** @nrwl/nx-testing-tools-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/shared/packages/cypress/** @nrwl/nx-testing-tools-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/generated/packages/jest/** @nrwl/nx-testing-tools-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/shared/packages/jest/** @nrwl/nx-testing-tools-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/generated/packages/playwright/** @nrwl/nx-testing-tools-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/shared/packages/playwright/** @nrwl/nx-testing-tools-reviewers @nrwl/nx-docs-reviewers
|
||||
/packages/cypress/** @nrwl/nx-testing-tools-reviewers
|
||||
/e2e/cypress/** @nrwl/nx-testing-tools-reviewers
|
||||
/packages/jest/** @nrwl/nx-testing-tools-reviewers
|
||||
/e2e/jest/** @nrwl/nx-testing-tools-reviewers
|
||||
/packages/playwright/** @nrwl/nx-testing-tools-reviewers
|
||||
/e2e/playwright/** @nrwl/nx-testing-tools-reviewers
|
||||
|
||||
# Linter
|
||||
/docs/generated/packages/eslint-plugin/** @nrwl/nx-linter-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/generated/packages/eslint/** @nrwl/nx-linter-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/shared/packages/eslint/** @nrwl/nx-linter-reviewers @nrwl/nx-docs-reviewers
|
||||
/packages/eslint-plugin/** @nrwl/nx-linter-reviewers
|
||||
/packages/eslint/** @nrwl/nx-linter-reviewers
|
||||
/e2e/eslint/** @nrwl/nx-linter-reviewers
|
||||
.eslint* @nrwl/nx-linter-reviewers
|
||||
|
||||
# Storybook
|
||||
/docs/generated/packages/storybook/** @nrwl/nx-storybook-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/shared/packages/storybook/** @nrwl/nx-storybook-reviewers @nrwl/nx-docs-reviewers
|
||||
/packages/storybook/** @nrwl/nx-storybook-reviewers
|
||||
/e2e/storybook/** @nrwl/nx-storybook-reviewers
|
||||
|
||||
## Devkit
|
||||
/docs/generated/devkit/** @nrwl/nx-devkit-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/generated/packages/devkit/** @nrwl/nx-devkit-reviewers @nrwl/nx-docs-reviewers
|
||||
/packages/devkit/** @nrwl/nx-devkit-reviewers
|
||||
/packages/devkit/index.ts @FrozenPandaz @vsavkin
|
||||
/packages/devkit/public-api.ts @FrozenPandaz @vsavkin
|
||||
|
||||
# Gradle
|
||||
/packages/gradle/** @FrozenPandaz @MaxKless @xiongemi
|
||||
/e2e/gradle/** @FrozenPandaz @MaxKless @xiongemi
|
||||
|
||||
# Nx-Plugin
|
||||
/docs/generated/packages/plugin/** @nrwl/nx-devkit-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/shared/packages/plugin/** @nrwl/nx-devkit-reviewers @nrwl/nx-docs-reviewers
|
||||
/packages/plugin/** @nrwl/nx-devkit-reviewers
|
||||
/e2e/plugin/** @nrwl/nx-devkit-reviewers
|
||||
|
||||
## Core
|
||||
/docs/generated/cli/** @nrwl/nx-core-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/generated/packages/nx/** @nrwl/nx-core-reviewers @nrwl/nx-docs-reviewers
|
||||
/docs/generated/packages/workspace/** @nrwl/nx-core-reviewers @nrwl/nx-docs-reviewers
|
||||
/packages/nx/** @nrwl/nx-core-reviewers
|
||||
/packages/nx/src/adapter @nrwl/nx-core-reviewers @leosvelperez
|
||||
/packages/nx/src/native @nrwl/nx-core-reviewers @nrwl/nx-native-reviewers
|
||||
/packages/nx/src/plugins/js/lock-file @nrwl/nx-core-reviewers @meeroslav
|
||||
/packages/nx/src/command-line/init/implementation/angular/** @nrwl/nx-angular-reviewers @nrwl/nx-core-reviewers
|
||||
/e2e/nx-init/src/nx-init-angular.test.ts @nrwl/nx-angular-reviewers
|
||||
/packages/nx/src/command-line/init/implementation/react/** @nrwl/nx-react-reviewers
|
||||
/e2e/nx-init/src/nx-init-react.test.ts @nrwl/nx-react-reviewers
|
||||
/e2e/nx-init/src/files/cra/** @nrwl/nx-react-reviewers
|
||||
/e2e/nx*/** @nrwl/nx-core-reviewers
|
||||
/packages/workspace/** @nrwl/nx-core-reviewers
|
||||
/e2e/workspace-create/** @nrwl/nx-core-reviewers
|
||||
/e2e/release/** @nrwl/nx-core-reviewers
|
||||
|
||||
# Misc
|
||||
/e2e/lerna-smoke-tests/** @vsavkin @JamesHenry
|
||||
/e2e/utils/** @meeroslav @nrwl/nx-testing-tools-reviewers @vsavkin @mandarini
|
||||
/community @nrwl/nx-docs-reviewers
|
||||
/CONTRIBUTING.md @FrozenPandaz @isaacplmann
|
||||
/CODE_OF_CONDUCT.md @FrozenPandaz @isaacplmann
|
||||
/CODEOWNERS @FrozenPandaz @AgentEnder
|
||||
/packages/nx/src/nx-cloud/utilities/url-shorten.ts @MaxKless
|
||||
|
||||
# Scripts
|
||||
/scripts/documentation @nrwl/nx-docs-reviewers
|
||||
/scripts/angular-support-upgrades @nrwl/nx-angular-reviewers
|
||||
|
||||
# CI
|
||||
/.nx/workflows/** @nrwl/nx-pipelines-reviewers
|
||||
/.github/** @nrwl/nx-pipelines-reviewers
|
||||
/.husky/** @nrwl/nx-pipelines-reviewers
|
||||
/packages/workspace/src/generators/ci-workflow/** @nrwl/nx-pipelines-reviewers
|
||||
|
||||
# Global Files
|
||||
project.json @FrozenPandaz @vsavkin
|
||||
jest.config.ts @nrwl/nx-testing-tools-reviewers @FrozenPandaz
|
||||
jest.preset.js @nrwl/nx-testing-tools-reviewers @FrozenPandaz
|
||||
|
||||
# Overrides - These are applied last, so override any matches above.
|
||||
docs/generated/manifests/* @nrwl/nrwlians
|
||||
docs/generated/packages-metadata.json @FrozenPandaz @jaysoo @AgentEnder @nrwl/nx-docs-reviewers
|
||||
|
||||
+52
-103
@@ -2,10 +2,19 @@
|
||||
|
||||
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="https://raw.githubusercontent.com/nrwl/nx/master/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.
|
||||
You can join our [Discord](https://go.nx.dev/community) for general questions and seeking help from others.
|
||||
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.
|
||||
|
||||
## Found an Issue?
|
||||
|
||||
@@ -18,25 +27,14 @@ can [submit a Pull Request](https://github.com/nrwl/nx/blob/master/CONTRIBUTING.
|
||||
|
||||
Source code and documentation are included in the top-level folders listed below.
|
||||
|
||||
- `packages` - Source code for Nx packages such as Angular, React, Web, NestJS, Next and others including generators and
|
||||
executors (or builders).
|
||||
- `e2e` - E2E tests for the Nx packages
|
||||
- `graph` - Source code for the Nx Graph application which shows the project graph, task graph, project details, and more in the browser.
|
||||
- `docs` - Markdown and configuration files for documentation including tutorials, guides for each supported platform,
|
||||
and API docs.
|
||||
- `nx-dev` - Source code for the Nx documentation site which displays the markdown in `docs` and more.
|
||||
- `tools` - Workspace-specific tooling and plugins
|
||||
- `e2e` - E2E tests.
|
||||
- `packages` - Source code for Nx packages such as Angular, React, Web, NestJS, Next and others including generators and
|
||||
executors (or builders).
|
||||
- `scripts` - Miscellaneous scripts for project tasks such as building documentation, testing, and code formatting.
|
||||
- `tmp` - Folder used by e2e tests. If you are a WebStorm user, make sure to mark this folder as excluded.
|
||||
|
||||
## Technologies
|
||||
|
||||
This repo contains a mix of different technologies, including:
|
||||
|
||||
- **Rust**: The core of Nx is written in Rust, which provides performance and safety.
|
||||
- **TypeScript**: The primary language for Nx packages and the Nx DevKit.
|
||||
- **Kotlin**: Used for the Gradle and Java plugins.
|
||||
|
||||
## Development Workstation Setup
|
||||
|
||||
If you are using `VSCode`, and provided you have [Docker](https://docker.com) installed on your machine, then you can leverage [Dev Containers](https://containers.dev) through this [VSCode extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers), to easily setup your development environment, with everything needed to contribute to Nx, already installed (namely `NodeJS`, `Yarn`, `Rust`, `Cargo`, plus some useful extensions like `Nx Console`).
|
||||
@@ -185,73 +183,76 @@ To build Nx on Windows, you need to use WSL.
|
||||
## Documentation Contributions
|
||||
|
||||
We would love for you to contribute to our documentation as well! Please feel welcome to submit fixes or enhancements to
|
||||
our existing documentation pages, `astro-docs` and the `nx-dev` application in this repo.
|
||||
our existing documentation pages and the `nx-dev` application in this repo.
|
||||
|
||||
### Documentation Structure
|
||||
|
||||
#### Documentation Pages
|
||||
|
||||
Our documentation pages can be found within this repo under the `astro-docs/src/content/docs` directory.
|
||||
Our documentation pages can be found within this repo under the `docs` directory.
|
||||
|
||||
Documentation is written in `.mdoc` (Markdoc) or `.mdx` (MDX) format and supports custom Markdoc tags for rich content
|
||||
such as videos, graphs, interactive components, and more. See the `astro-docs/README.md` for a full list of available
|
||||
custom tags and their usage.
|
||||
The `docs/map.json` file is considered our source of truth for our site's structure, and should be updated when adding a
|
||||
new page to our documentation to ensure that it is included in the documentation site. We also run automated scripts
|
||||
based on this `map.json` data to safeguard against common human errors that could break our site.
|
||||
|
||||
The sidebar structure is defined in `astro-docs/sidebar.mts` and should be updated when adding new sections or pages
|
||||
to ensure proper navigation.
|
||||
|
||||
#### Astro-Docs Application
|
||||
|
||||
Our public `nx.dev/docs` documentation site is built with [Astro](https://astro.build) and [Starlight](https://starlight.astro.build),
|
||||
and can be found in the `astro-docs` directory of this repo. See [docs README for more details](./astro-docs/README.md)
|
||||
When you make a change to the `map.json` file, make sure to run `pnpm documentation` to propagate your changes to the `nx-dev` application.
|
||||
|
||||
#### Nx-Dev Application
|
||||
|
||||
The `nx-dev` directory contains a [Next.js](https://nextjs.org/) application used for blog posts and landing pages.
|
||||
Our public `nx.dev` documentation site is a [Next.js](https://nextjs.org/) application, that can be found in
|
||||
the `nx-dev` directory of this repo.
|
||||
The documentation site is consuming the `docs/` directly by copy-ing its content while deploying, so the website is
|
||||
always in sync and reflects the latest version of `docs/`.
|
||||
|
||||
Jump to [Running the Documentation Site Locally](#running-the-documentation-site-locally) to see how to preview your
|
||||
changes while serving.
|
||||
|
||||
### Changing Generated API documentation
|
||||
|
||||
API documentation for CLI commands, executors, and generators is automatically generated during the build process from
|
||||
the corresponding `schema.json` files in each package.
|
||||
`.md` files documenting the API for our CLI (including executor and generator API docs) are generated via the
|
||||
corresponding `schema.json` file for the given command.
|
||||
|
||||
The documentation is generated using content loaders in the `astro-docs` application and requires a rebuild to reflect
|
||||
changes. After adjusting a `schema.json` file:
|
||||
After adjusting the `schema.json` file, `.md` files for these commands can be generated by running:
|
||||
|
||||
1. Restart the development server with `nx serve astro-docs` to see the changes
|
||||
2. Or run `nx preview astro-docs` to view the built site locally
|
||||
```bash
|
||||
pnpm documentation
|
||||
```
|
||||
|
||||
This will update the corresponding contents of the `docs` directory. These are generated automatically on push (via
|
||||
husky) as well.
|
||||
|
||||
Note that adjusting the `schema.json` files will also affect the CLI manuals and Nx Console behavior, in addition to
|
||||
the generated documentation.
|
||||
adjusting the docs.
|
||||
|
||||
### Running the Documentation Site Locally
|
||||
|
||||
To run the documentation site locally, run the command:
|
||||
|
||||
```shell
|
||||
nx serve astro-docs
|
||||
```
|
||||
|
||||
You can then access the application locally at `localhost:4321`. Changes to markdoc files should reflect automatically in the browser on save.
|
||||
|
||||
#### Working with Plugin Registry
|
||||
|
||||
To view plugin registry statistics (GitHub stars, npm downloads, etc.) during local development:
|
||||
To run `nx-dev` locally, run the command:
|
||||
|
||||
```bash
|
||||
NX_DOCS_PLUGIN_STATS=true nx serve astro-docs
|
||||
npx nx serve-docs nx-dev
|
||||
```
|
||||
|
||||
Note: Plugin stats are disabled by default in development to improve performance.
|
||||
You can then access the application locally at `localhost:4200`. Changes to markdown documentation files will be automatically applied to the site when you refresh the browser.
|
||||
|
||||
#### Troubleshooting: `JavaScript heap out of memory`
|
||||
|
||||
If you see an error that states: `FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory`,
|
||||
you need
|
||||
to [increase the max memory size of V8's old memory section](https://nodejs.org/api/cli.html#--max-old-space-sizesize-in-megabytes):
|
||||
|
||||
```bash
|
||||
export NODE_OPTIONS="--max-old-space-size=4096"
|
||||
```
|
||||
|
||||
After configuring this, try to run `npx nx serve nx-dev` again.
|
||||
|
||||
### PR Preview
|
||||
|
||||
When submitting a PR, this repo will automatically generate a preview of the documentation site based on the contents
|
||||
When submitting a PR, this repo will automatically generate a preview of the `nx-dev` application based on the contents
|
||||
of your pull request.
|
||||
|
||||
Once the preview site is launched, a comment will automatically be added to your PR with the link to your PR's preview.
|
||||
Once the preview site is launched, a comment will automatically be added to your PR with the link your PR's preview. To
|
||||
check your docs changes, make sure to select `Preview` from the version selection box of the site.
|
||||
|
||||
## Submission Guidelines
|
||||
|
||||
@@ -332,7 +333,6 @@ The scope must be one of the following:
|
||||
- express - anything Express specific
|
||||
- js - anything related to @nx/js package or general js/ts support
|
||||
- linter - anything Linter specific
|
||||
- module-federation - anything Nx Module Federation specific
|
||||
- nest - anything Nest specific
|
||||
- nextjs - anything Next specific
|
||||
- node - anything Node specific
|
||||
@@ -374,57 +374,6 @@ To simplify and automate the process of committing with this format,
|
||||
**Nx is a [Commitizen](https://github.com/commitizen/cz-cli) friendly repository**, just do `git add` and
|
||||
execute `pnpm commit`.
|
||||
|
||||
##### Using the Interactive Commit Tool
|
||||
|
||||
Instead of `git commit`, use:
|
||||
|
||||
```bash
|
||||
pnpm commit
|
||||
```
|
||||
|
||||
This will launch an interactive prompt that will:
|
||||
|
||||
1. Ask you to select the type of change (feat, fix, docs, cleanup, chore)
|
||||
2. Let you choose the appropriate scope from the predefined list
|
||||
3. Guide you through writing a clear, descriptive commit message
|
||||
4. Ensure your commit follows the conventional commit format
|
||||
|
||||
##### Available Commit Types
|
||||
|
||||
- **feat**: A new feature
|
||||
- **fix**: A bug fix
|
||||
- **docs**: Documentation only changes
|
||||
- **cleanup**: A code change that neither fixes a bug nor adds a feature
|
||||
- **chore**: Other changes that don't modify src or test files
|
||||
|
||||
##### Available Scopes
|
||||
|
||||
The repository includes many predefined scopes. Use the one which is most specific to the changes being committed
|
||||
|
||||
- **core**: anything Nx core specific
|
||||
- **angular**: anything Angular specific
|
||||
- **react**: anything React specific
|
||||
- **nextjs**: anything Next specific
|
||||
- **node**: anything Node specific
|
||||
- **devkit**: devkit-related changes
|
||||
- **graph**: anything graph app specific
|
||||
- **testing**: anything testing specific (e.g. jest or cypress)
|
||||
- **misc**: misc stuff
|
||||
- **repo**: anything related to managing the repo itself
|
||||
- **nx-dev**: anything related to docs infrastructure
|
||||
|
||||
For the complete list of available scopes, see `/scripts/commitizen.js`.
|
||||
|
||||
##### Example Commits
|
||||
|
||||
```bash
|
||||
feat(core): add new project graph visualization
|
||||
fix(angular): resolve build issues with standalone components
|
||||
docs(misc): update contributing guidelines
|
||||
chore(repo): bump dependencies
|
||||
cleanup(devkit): refactor utility functions for better readability
|
||||
```
|
||||
|
||||
#### PR releases
|
||||
|
||||
If you are working on a particularly complex change or feature addition, you can request a dedicated Nx release for the associated pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate.
|
||||
|
||||
Generated
+1351
-2133
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2017-2026 Narwhal Technologies Inc.
|
||||
Copyright (c) 2017-2025 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,52 +1,74 @@
|
||||
<div align="center">
|
||||
<p style="text-align: center;">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-dark.svg">
|
||||
<img alt="Nx - Smart Monorepos · Fast CI" src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-light.svg" width="100%">
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="images/nx-logo-light.svg">
|
||||
<img src="images/nx-logo.svg" alt="Nx Logo" width="140">
|
||||
</picture>
|
||||
</p>
|
||||
<div style="text-align: center;">
|
||||
|
||||
<h1 align="center">Smart Monorepos · Fast Builds</h1>
|
||||
|
||||
<p>
|
||||
<a href="https://www.npmjs.com/package/nx"><img src="https://img.shields.io/npm/v/nx.svg?style=for-the-badge" alt="NPM Version"></a>
|
||||
<a href="https://github.com/nrwl/nx"><img src="https://img.shields.io/github/stars/nrwl/nx?style=for-the-badge&logo=github" alt="GitHub Stars"></a>
|
||||
<a href=""><img src="https://img.shields.io/npm/l/nx.svg?style=for-the-badge" alt="License"></a>
|
||||
<a href="https://go.nx.dev/community"><img src="https://img.shields.io/discord/1143497901675401286?label=discord&style=for-the-badge" alt="Discord"></a>
|
||||
<a href="https://x.com/nxdevtools"><img src="https://img.shields.io/badge/@nxdevtools-555?style=for-the-badge&logo=x" alt="X (Twitter)"></a>
|
||||
</p>
|
||||
|
||||
<br />
|
||||
|
||||
[**Docs**](https://nx.dev/docs) • [**Changelog**](https://nx.dev/changelog) • [**Blog**](https://nx.dev/blog) • [**Courses**](https://nx.dev/courses) • [**YouTube**](https://youtube.com/@nxdevtools)
|
||||
|
||||
<br />
|
||||
[](https://circleci.com/gh/nrwl/nx)
|
||||
[]()
|
||||
[](https://www.npmjs.com/package/nx)
|
||||
[]()
|
||||
[](http://commitizen.github.io/cz-cli/)
|
||||
[](https://gitter.im/nrwl-nx/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://go.nx.dev/community)
|
||||
|
||||
</div>
|
||||
|
||||
Nx is a monorepo solution for TypeScript and polyglot codebases. Built with Rust for performance, extensible via TypeScript. Caches what didn't change, runs only what's affected, and comes with an integrated CI solution. Start simple, scale as you grow.
|
||||
<hr>
|
||||
|
||||
## Quick Start
|
||||
# Smart Monorepos · Fast CI
|
||||
|
||||
Visit the [Nx quickstart docs](https://nx.dev/docs/quickstart) to get started.
|
||||
Build system, optimized for monorepos, with AI-powered architectural awareness and advanced CI capabilities.
|
||||
|
||||
## Why Nx?
|
||||
Create a new Nx workspace with
|
||||
|
||||
- **Incremental by design -** Run `npx nx init` in any npm/pnpm/yarn workspace. Nx picks up your existing `package.json` scripts, caches their outputs, and runs only what's
|
||||
affected. No changes to your setup required.
|
||||
- **AI-native tooling -** The Nx CLI is optimized for autonomous AI agents so they get the context they need and can operate just like a human. [Learn more »](https://github.com/nrwl/nx-ai-agents-config)
|
||||
- **Polyglot plugin system -** Optional plugins auto-discover tasks, configure cache inputs/outputs, and scaffold code based on your actual tooling. Works with Vite, Webpack, Jest, Vitest, ESLint, Gradle, Maven, .NET, Go, and [more](https://nx.dev/technologies).
|
||||
- **Integrated CI solution -** [Connect Nx to your CI provider](https://nx.dev/ci/intro/ci-with-nx) (GitHub Actions, GitLab, Azure, etc.) to enable remote caching, task distribution across machines, affected-only runs, and automatic e2e test splitting. [Learn more »](https://nx.dev/ci/intro/ci-with-nx)
|
||||
- **Self-healing CI -** An AI agent on your CI pipeline that detects failures, analyzes root cause, proposes a fix, and verifies it automatically. Local agents connect to CI via MCP to autonomously detect and fix failures. [Learn more »](https://nx.dev/ci/features/self-healing)
|
||||
```shell
|
||||
npx create-nx-workspace
|
||||
```
|
||||
|
||||
## Who uses Nx?
|
||||
...or run
|
||||
|
||||
From startups to Fortune 500 companies. [See our Nx success stories »](https://nx.dev/customers)
|
||||
```
|
||||
npx nx init
|
||||
```
|
||||
|
||||
to add Nx to your existing workspace to get faster task scheduling, caching and more. More [in the docs](https://nx.dev/getting-started/intro#try-nx-yourself).
|
||||
|
||||
## Learn about CI with Nx Cloud
|
||||
|
||||
[Nx Cloud](https://nx.dev/nx-cloud) connects directly to your existing CI setup, helping you scale your monorepos on CI by leveraging [remote caching](https://nx.dev/ci/features/remote-cache?utm_source=nxrepo&utm_medium=readme&utm_campaign=nxrepo), [task distribution across multiple machines](https://nx.dev/ci/features/distribute-task-execution?utm_source=nxrepo&utm_medium=readme&utm_campaign=nxrepo), [automated e2e test splitting](https://nx.dev/ci/features/split-e2e-tasks?utm_source=nxrepo&utm_medium=readme&utm_campaign=nxrepo) and [automated task flakiness detection](https://nx.dev/ci/features/flaky-tasks?utm_source=nxrepo&utm_medium=readme&utm_campaign=nxrepo)
|
||||
|
||||
Connect your existing Nx workspace with
|
||||
|
||||
```
|
||||
npx nx connect
|
||||
```
|
||||
|
||||
Learn more in the [Nx CI docs »](https://nx.dev/ci/intro?utm_source=nxrepo&utm_medium=readme&utm_campaign=nxrepo)
|
||||
|
||||
## Useful links
|
||||
|
||||
- [Our docs](https://nx.dev/docs)
|
||||
- [Our blog](https://nx.dev/blog)
|
||||
- [Our community discord, live stream,...](https://nx.dev/community)
|
||||
- [Our YouTube channel](https://www.youtube.com/@NxDevtools)
|
||||
- [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 Monorepos · Fast CI"></a></p>
|
||||
|
||||
## Want to help?
|
||||
|
||||
If you want to file a bug or submit a PR, read up on our [guidelines for contributing](https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md).
|
||||
If you want to file a bug or submit a PR, read up on
|
||||
our [guidelines for contributing](https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md) and watch this video that will
|
||||
help you get started.
|
||||
|
||||
<a href="https://www.youtube.com/watch?v=8LCA_4qxc08" target="_blank" rel="noreferrer">
|
||||
<p style="text-align: center;"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/how-to-contribute.png" width="600" alt="Nx - How to contribute video"></p>
|
||||
</a>
|
||||
|
||||
## Core Team
|
||||
|
||||
@@ -55,27 +77,22 @@ If you want to file a bug or submit a PR, read up on our [guidelines for contrib
|
||||
|  |  |  |  |
|
||||
| [vsavkin](https://github.com/vsavkin) | [FrozenPandaz](https://github.com/FrozenPandaz) | [bcabanes](https://github.com/bcabanes) | [jaysoo](https://github.com/jaysoo) |
|
||||
|
||||
| James Henry | Jon Cammisuli | Max Kless | Juri Strumpflohner |
|
||||
| James Henry | Jon Cammisuli | Isaac Mann | Juri Strumpflohner |
|
||||
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | -------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
||||
|  |  |  |  |
|
||||
| [JamesHenry](https://github.com/JamesHenry) | [cammisuli](https://github.com/cammisuli) | [MaxKless](https://github.com/MaxKless) | [juristr](https://github.com/juristr) |
|
||||
|  |  |  |  |
|
||||
| [JamesHenry](https://github.com/JamesHenry) | [cammisuli](https://github.com/cammisuli) | [isaacplmann](https://github.com/isaacplmann) | [juristr](https://github.com/juristr) |
|
||||
|
||||
| Philip Fulcher | Caleb Ukle | Colum Ferry | Steven Nance |
|
||||
| ------------------------------------------------------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
||||
|  |  |  |  |
|
||||
| [philipjfulcher](https://github.com/philipjfulcher) | [barbados-clemens](https://github.com/barbados-clemens) | [Coly010](https://github.com/Coly010) | [llwt](https://github.com/llwt) |
|
||||
| Philip Fulcher | Caleb Ukle | Katerina Skroumpelou | Colum Ferry |
|
||||
| ------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
||||
|  |  |  |  |
|
||||
| [philipjfulcher](https://github.com/philipjfulcher) | [barbados-clemens](https://github.com/barbados-clemens) | [mandarini](https://github.com/mandarini) | [Coly010](https://github.com/Coly010) |
|
||||
|
||||
| Miroslav Jonaš | Leosvel Pérez Espinosa | Zachary DeRose | Craigory Coppola |
|
||||
| ----------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
|
||||
|  |  |  |  |
|
||||
| [meeroslav](https://github.com/meeroslav) | [leosvelperez](https://github.com/leosvelperez) | [ZackDeRose](https://github.com/ZackDeRose) | [AgentEnder](https://github.com/AgentEnder) |
|
||||
| Emily Xiong | Miroslav Jonaš | Leosvel Pérez Espinosa | Zachary DeRose |
|
||||
| ---------------------------------------------------------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
||||
|  |  |  |  |
|
||||
| [xiongemi](https://github.com/xiongemi) | [meeroslav](https://github.com/meeroslav) | [leosvelperez](https://github.com/leosvelperez) | [ZackDeRose](https://github.com/ZackDeRose) |
|
||||
|
||||
| Chau Tran | Nicole Oliver | Rares Matei | Altan Stalker |
|
||||
| -------------------------------------------------------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
||||
|  |  |  |  |
|
||||
| [nartc](https://github.com/nartc) | [nixallover](https://github.com/nixallover) | [rarmatei](https://github.com/rarmatei) | [StalkAltan](https://github.com/StalkAltan) |
|
||||
|
||||
| Josh VanAllen | Austin Fahsl | Louie Weng |
|
||||
| ----------------------------------------------------------------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
||||
|  |  |  |
|
||||
| [joshvanallen](https://github.com/joshvanallen) | [fahslaj](https://github.com/fahslaj) | [lourw](https://github.com/lourw) |
|
||||
| Craigory Coppola | Chau Tran | Nicholas Cunningham | Max Kless |
|
||||
| -------------------------------------------------------------------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------- |
|
||||
|  |  |  |  |
|
||||
| [AgentEnder](https://github.com/AgentEnder) | [nartc](https://github.com/nartc) | [ndcunningham](https://github.com/ndcunningham) | [MaxKless](https://github.com/MaxKless) |
|
||||
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
Nx/Nrwl takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations.
|
||||
|
||||
If you believe you have found a security vulnerability in any Nx-owned repository that meets Nx's definition of a security vulnerability, please report it to us as described below.
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||
|
||||
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.
|
||||
@@ -1,6 +0,0 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.astro/
|
||||
.netlify/
|
||||
test-output/
|
||||
playwright-report/
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"extends": ["plugin:playwright/recommended", "../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["**/*.spec.ts", "**/*.test.ts", "**/*.spec.js", "**/*.test.js"],
|
||||
"rules": {
|
||||
"playwright/no-standalone-expect": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["e2e/**/*.{ts,js,tsx,jsx}"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
# build output
|
||||
dist/
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# Local Netlify folder
|
||||
.netlify
|
||||
@@ -1,162 +0,0 @@
|
||||
StylesPath = .vale/styles
|
||||
MinAlertLevel = suggestion
|
||||
|
||||
# Treat Markdoc (.mdoc) files as markdown
|
||||
[formats]
|
||||
mdoc = md
|
||||
|
||||
# Ignore Markdoc tag syntax and @-scoped package names to avoid false positives
|
||||
TokenIgnores = (\{%.*?%\}), (@\w+/[\w-]+)
|
||||
|
||||
[src/content/docs/**/*.{mdoc,mdx,md}]
|
||||
BasedOnStyles = Nx
|
||||
|
||||
# Disable heading check for config option reference pages (headings are camelCase property names)
|
||||
[src/content/docs/technologies/angular/angular-rsbuild/introduction.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/angular/angular-rspack/create-config.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/build-tools/webpack/Guides/webpack-plugins.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/reference/Deprecated/affected-graph.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/reference/Deprecated/print-affected.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/react/next/Guides/next-config-setup.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
# Disable heading check for release notes (timestamps as headings)
|
||||
[src/content/docs/reference/Nx Cloud/release-notes.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
# Disable heading check for nx-cloud-cli (CLI flags as headings)
|
||||
[src/content/docs/reference/nx-cloud-cli.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
# Disable heading check for nx-console-settings (config option headings)
|
||||
[src/content/docs/reference/nx-console-settings.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
# Disable heading check for pages with camelCase API property headings
|
||||
[src/content/docs/reference/nx-json.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/reference/project-configuration.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/reference/Deprecated/legacy-cache.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/extending-nx/local-executors.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/guides/Nx Release/programmatic-api.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/node/Guides/wait-for-tasks.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/test-tools/vitest/Guides/testing-without-building-dependencies.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/angular/Guides/nx-and-angular.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
# Disable heading check for merge-atomized-outputs (warning message as heading)
|
||||
[src/content/docs/technologies/test-tools/playwright/Guides/merge-atomized-outputs.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
# Disable heading check for plugin introduction pages (@nx/ package name headings)
|
||||
[src/content/docs/technologies/build-tools/docker/introduction.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/build-tools/rspack/introduction.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/build-tools/webpack/introduction.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/dotnet/introduction.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/eslint/introduction.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/java/gradle/introduction.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/java/maven/introduction.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/react/expo/introduction.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/react/react-native/introduction.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/test-tools/cypress/introduction.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/test-tools/detox/introduction.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/test-tools/playwright/introduction.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/test-tools/storybook/introduction.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/vue/nuxt/introduction.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
# Disable heading check for remaining pages with structural false positives
|
||||
# (code identifiers, slashes, quotes, parenthetical words in headings)
|
||||
[src/content/docs/extending-nx/create-install-package.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/extending-nx/create-preset.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/getting-started/editor-setup.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/guides/Adopting Nx/from-turborepo.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/guides/Tasks & Caching/reduce-repetitive-configuration.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/guides/Tasks & Caching/workspace-watching.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/reference/Deprecated/custom-tasks-runner.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/reference/Deprecated/rescope.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/reference/Nx Cloud/credits-pricing.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/reference/nx-mcp.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/eslint/Guides/custom-workspace-rules.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/module-federation/Guides/nx-module-federation-plugin.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/module-federation/introduction.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/technologies/react/Guides/react-router.mdoc]
|
||||
Nx.Headings = NO
|
||||
|
||||
[src/content/docs/troubleshooting/unknown-local-cache.mdoc]
|
||||
Nx.Headings = NO
|
||||
@@ -1,29 +0,0 @@
|
||||
extends: existence
|
||||
message: "Avoid AI-sounding phrase '%s'. Rewrite to be direct."
|
||||
level: error
|
||||
ignorecase: true
|
||||
tokens:
|
||||
- "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'
|
||||
- 'This comprehensive guide will'
|
||||
- 'Without further ado'
|
||||
- 'In conclusion'
|
||||
- 'To summarize'
|
||||
- "As we've seen"
|
||||
- 'Needless to say'
|
||||
- 'As a matter of fact'
|
||||
- 'Generally speaking'
|
||||
- 'It is worth mentioning'
|
||||
- 'Game-changer'
|
||||
- 'Cutting-edge'
|
||||
- 'Groundbreaking'
|
||||
@@ -1,136 +0,0 @@
|
||||
extends: capitalization
|
||||
message: "Use sentence case for headings. '%s' should be '%s'."
|
||||
level: error
|
||||
scope: heading
|
||||
match: $sentence
|
||||
indicators:
|
||||
- ':'
|
||||
exceptions:
|
||||
- Nx
|
||||
- Nx Cloud
|
||||
- Nx Console
|
||||
- Nx Agents
|
||||
- Nx Replay
|
||||
- AI
|
||||
- CI
|
||||
- CD
|
||||
- API
|
||||
- APIs
|
||||
- URL
|
||||
- URLs
|
||||
- CLI
|
||||
- PR
|
||||
- PRs
|
||||
- IDE
|
||||
- TypeScript
|
||||
- JavaScript
|
||||
- Angular
|
||||
- React
|
||||
- Vue
|
||||
- Nuxt
|
||||
- Vite
|
||||
- Webpack
|
||||
- Rspack
|
||||
- Rollup
|
||||
- ESLint
|
||||
- Prettier
|
||||
- GitHub
|
||||
- GitHub Actions
|
||||
- GitLab
|
||||
- BitBucket
|
||||
- BitBucket Cloud
|
||||
- Azure DevOps
|
||||
- Azure
|
||||
- Docker
|
||||
- Dockerfile
|
||||
- Kubernetes
|
||||
- Gradle
|
||||
- Maven
|
||||
- Node.js
|
||||
- Deno
|
||||
- Bun
|
||||
- pnpm
|
||||
- npm
|
||||
- Yarn
|
||||
- Next.js
|
||||
- Remix
|
||||
- Astro
|
||||
- Storybook
|
||||
- Jest
|
||||
- Vitest
|
||||
- Cypress
|
||||
- Playwright
|
||||
- Express
|
||||
- Fastify
|
||||
- Nest.js
|
||||
- NestJS
|
||||
- Expo
|
||||
- React Native
|
||||
- Module Federation
|
||||
- IntelliJ
|
||||
- VS Code
|
||||
- VSCode
|
||||
- WebStorm
|
||||
- Turborepo
|
||||
- Lerna
|
||||
- Bazel
|
||||
- JSON
|
||||
- YAML
|
||||
- TOML
|
||||
- CSS
|
||||
- HTML
|
||||
- SSR
|
||||
- SSG
|
||||
- MFE
|
||||
- DTE
|
||||
- SWC
|
||||
- Rsbuild
|
||||
- Rolldown
|
||||
# Abbreviations and acronyms
|
||||
- AI
|
||||
- UI
|
||||
- DX
|
||||
- ID
|
||||
- FAQ
|
||||
- SAML
|
||||
- DPE
|
||||
- WSL
|
||||
- EJS
|
||||
- AST
|
||||
- TTG
|
||||
- PATs
|
||||
- IDEs
|
||||
- MCP
|
||||
- HTTP
|
||||
- HTTPS
|
||||
- INI
|
||||
- VCS
|
||||
- LTS
|
||||
- DTS
|
||||
- SVG
|
||||
- SVGs
|
||||
- SVGR
|
||||
- EAS
|
||||
- iOS
|
||||
- AWS
|
||||
- S3
|
||||
- E2E
|
||||
- TL;DR
|
||||
- NuGet
|
||||
- MongoDB
|
||||
- OpenShift
|
||||
- Vercel
|
||||
- Netlify
|
||||
# Proper nouns
|
||||
- RxJS
|
||||
- JetBrains
|
||||
- Neovim
|
||||
- PnP
|
||||
- Self-Healing
|
||||
- AMD64
|
||||
- ARM64
|
||||
# Filenames and env vars
|
||||
- SELF_HEALING.md
|
||||
- CLAUDE.md
|
||||
- NODE_AUTH_TOKEN
|
||||
- NX_REJECT_UNKNOWN_LOCAL_CACHE
|
||||
@@ -1,20 +0,0 @@
|
||||
extends: existence
|
||||
message: "Avoid marketing language '%s'. Be specific about what the feature does instead."
|
||||
level: suggestion
|
||||
ignorecase: true
|
||||
tokens:
|
||||
- 'effortless'
|
||||
- 'effortlessly'
|
||||
- 'seamless'
|
||||
- 'seamlessly'
|
||||
- 'powerful'
|
||||
- 'robust'
|
||||
- 'comprehensive'
|
||||
- 'leverage'
|
||||
- 'utilize'
|
||||
- 'facilitate'
|
||||
- 'aforementioned'
|
||||
- 'best-in-class'
|
||||
- 'world-class'
|
||||
- 'next-level'
|
||||
- 'supercharge'
|
||||
@@ -1,25 +0,0 @@
|
||||
extends: existence
|
||||
message: "Prefer active voice. '%s' could be rewritten."
|
||||
level: suggestion
|
||||
ignorecase: true
|
||||
tokens:
|
||||
- 'is cached by'
|
||||
- 'is built by'
|
||||
- 'is run by'
|
||||
- 'is executed by'
|
||||
- 'is generated by'
|
||||
- 'is created by'
|
||||
- 'is managed by'
|
||||
- 'is handled by'
|
||||
- 'is provided by'
|
||||
- 'is configured by'
|
||||
- 'is determined by'
|
||||
- 'is computed by'
|
||||
- 'is resolved by'
|
||||
- 'is stored by'
|
||||
- 'are cached by'
|
||||
- 'are built by'
|
||||
- 'are run by'
|
||||
- 'are executed by'
|
||||
- 'are generated by'
|
||||
- 'are created by'
|
||||
@@ -1,9 +0,0 @@
|
||||
extends: substitution
|
||||
message: "Use '%s' instead of '%s'. Product names must be capitalized."
|
||||
level: error
|
||||
ignorecase: false
|
||||
swap:
|
||||
'(?:nx cloud|NX Cloud|Nx cloud|NX cloud)': Nx Cloud
|
||||
'(?:nx console|NX Console|Nx console|NX console)': Nx Console
|
||||
'(?:nx agents|NX Agents|Nx agents|NX agents)': Nx Agents
|
||||
'(?:nx replay|NX Replay|Nx replay|NX replay)': Nx Replay
|
||||
@@ -1,6 +0,0 @@
|
||||
extends: existence
|
||||
message: "Don't use possessives on product names. Use 'the Nx configuration' instead of 'Nx's configuration'."
|
||||
level: error
|
||||
ignorecase: false
|
||||
tokens:
|
||||
- "Nx's"
|
||||
@@ -1,26 +0,0 @@
|
||||
extends: existence
|
||||
message: "Don't write about the document itself. Get right to the content. Remove '%s'."
|
||||
level: error
|
||||
ignorecase: true
|
||||
tokens:
|
||||
- 'This page explains'
|
||||
- 'This page describes'
|
||||
- 'This page covers'
|
||||
- 'This page shows'
|
||||
- 'This document covers'
|
||||
- 'This document explains'
|
||||
- 'This document describes'
|
||||
- 'In this guide'
|
||||
- 'In this tutorial'
|
||||
- 'In this section'
|
||||
- 'This guide will'
|
||||
- 'This tutorial will'
|
||||
- 'This section will'
|
||||
- "we'll walk through"
|
||||
- 'we will walk through'
|
||||
- "we'll explore"
|
||||
- 'we will explore'
|
||||
- "we'll cover"
|
||||
- 'we will cover'
|
||||
- "we'll look at"
|
||||
- 'we will look at'
|
||||
@@ -1,14 +0,0 @@
|
||||
extends: existence
|
||||
message: "Rewrite to lead with the reader's action instead of '%s'. For example, 'You can ...' or 'Run ...'."
|
||||
level: warning
|
||||
ignorecase: true
|
||||
tokens:
|
||||
- 'This allows you to'
|
||||
- 'This enables you to'
|
||||
- 'This lets you'
|
||||
- 'This provides you with'
|
||||
- 'This gives you the ability to'
|
||||
- 'Nx allows you to'
|
||||
- 'Nx enables you to'
|
||||
- 'Nx provides the ability to'
|
||||
- 'Nx provides you with'
|
||||
@@ -1,7 +0,0 @@
|
||||
extends: existence
|
||||
message: "Use the Oxford (serial) comma before 'and' or 'or' in a list of three or more items."
|
||||
level: suggestion
|
||||
scope: sentence
|
||||
tokens:
|
||||
- '\w+,\s\w+\sand\s'
|
||||
- '\w+,\s\w+\sor\s'
|
||||
@@ -1,9 +0,0 @@
|
||||
extends: substitution
|
||||
message: "Use '%s' instead of '%s'."
|
||||
level: warning
|
||||
ignorecase: true
|
||||
swap:
|
||||
schematic: generator
|
||||
schematics: generators
|
||||
memoized: cached
|
||||
stored results: cached
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user