Compare commits
2 Commits
dev
...
193-monito
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67081b0c6e | ||
|
|
8475d85bc8 |
@ -1,53 +0,0 @@
|
||||
---
|
||||
name: speckit-git-commit
|
||||
description: Auto-commit changes after a Spec Kit command completes
|
||||
compatibility: Requires spec-kit project structure with .specify/ directory
|
||||
metadata:
|
||||
author: github-spec-kit
|
||||
source: git:commands/speckit.git.commit.md
|
||||
---
|
||||
|
||||
# Auto-Commit Changes
|
||||
|
||||
Automatically stage and commit all changes after a Spec Kit command completes.
|
||||
|
||||
## Behavior
|
||||
|
||||
This command is invoked as a hook after (or before) core commands. It:
|
||||
|
||||
1. Determines the event name from the hook context (e.g., if invoked as an `after_specify` hook, the event is `after_specify`; if `before_plan`, the event is `before_plan`)
|
||||
2. Checks `.specify/extensions/git/git-config.yml` for the `auto_commit` section
|
||||
3. Looks up the specific event key to see if auto-commit is enabled
|
||||
4. Falls back to `auto_commit.default` if no event-specific key exists
|
||||
5. Uses the per-command `message` if configured, otherwise a default message
|
||||
6. If enabled and there are uncommitted changes, runs `git add .` + `git commit`
|
||||
|
||||
## Execution
|
||||
|
||||
Determine the event name from the hook that triggered this command, then run the script:
|
||||
|
||||
- **Bash**: `.specify/extensions/git/scripts/bash/auto-commit.sh <event_name>`
|
||||
- **PowerShell**: `.specify/extensions/git/scripts/powershell/auto-commit.ps1 <event_name>`
|
||||
|
||||
Replace `<event_name>` with the actual hook event (e.g., `after_specify`, `before_plan`, `after_implement`).
|
||||
|
||||
## Configuration
|
||||
|
||||
In `.specify/extensions/git/git-config.yml`:
|
||||
|
||||
```yaml
|
||||
auto_commit:
|
||||
default: false # Global toggle — set true to enable for all commands
|
||||
after_specify:
|
||||
enabled: true # Override per-command
|
||||
message: "[Spec Kit] Add specification"
|
||||
after_plan:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Add implementation plan"
|
||||
```
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
- If Git is not available or the current directory is not a repository: skips with a warning
|
||||
- If no config file exists: skips (disabled by default)
|
||||
- If no changes to commit: skips with a message
|
||||
@ -1,72 +0,0 @@
|
||||
---
|
||||
name: speckit-git-feature
|
||||
description: Create a feature branch with sequential or timestamp numbering
|
||||
compatibility: Requires spec-kit project structure with .specify/ directory
|
||||
metadata:
|
||||
author: github-spec-kit
|
||||
source: git:commands/speckit.git.feature.md
|
||||
---
|
||||
|
||||
# Create Feature Branch
|
||||
|
||||
Create and switch to a new git feature branch for the given specification. This command handles **branch creation only** — the spec directory and files are created by the core `/speckit.specify` workflow.
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Environment Variable Override
|
||||
|
||||
If the user explicitly provided `GIT_BRANCH_NAME` (e.g., via environment variable, argument, or in their request), pass it through to the script by setting the `GIT_BRANCH_NAME` environment variable before invoking the script. When `GIT_BRANCH_NAME` is set:
|
||||
- The script uses the exact value as the branch name, bypassing all prefix/suffix generation
|
||||
- `--short-name`, `--number`, and `--timestamp` flags are ignored
|
||||
- `FEATURE_NUM` is extracted from the name if it starts with a numeric prefix, otherwise set to the full branch name
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Verify Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null`
|
||||
- If Git is not available, warn the user and skip branch creation
|
||||
|
||||
## Branch Numbering Mode
|
||||
|
||||
Determine the branch numbering strategy by checking configuration in this order:
|
||||
|
||||
1. Check `.specify/extensions/git/git-config.yml` for `branch_numbering` value
|
||||
2. Check `.specify/init-options.json` for `branch_numbering` value (backward compatibility)
|
||||
3. Default to `sequential` if neither exists
|
||||
|
||||
## Execution
|
||||
|
||||
Generate a concise short name (2-4 words) for the branch:
|
||||
- Analyze the feature description and extract the most meaningful keywords
|
||||
- Use action-noun format when possible (e.g., "add-user-auth", "fix-payment-bug")
|
||||
- Preserve technical terms and acronyms (OAuth2, API, JWT, etc.)
|
||||
|
||||
Run the appropriate script based on your platform:
|
||||
|
||||
- **Bash**: `.specify/extensions/git/scripts/bash/create-new-feature.sh --json --short-name "<short-name>" "<feature description>"`
|
||||
- **Bash (timestamp)**: `.specify/extensions/git/scripts/bash/create-new-feature.sh --json --timestamp --short-name "<short-name>" "<feature description>"`
|
||||
- **PowerShell**: `.specify/extensions/git/scripts/powershell/create-new-feature.ps1 -Json -ShortName "<short-name>" "<feature description>"`
|
||||
- **PowerShell (timestamp)**: `.specify/extensions/git/scripts/powershell/create-new-feature.ps1 -Json -Timestamp -ShortName "<short-name>" "<feature description>"`
|
||||
|
||||
**IMPORTANT**:
|
||||
- Do NOT pass `--number` — the script determines the correct next number automatically
|
||||
- Always include the JSON flag (`--json` for Bash, `-Json` for PowerShell) so the output can be parsed reliably
|
||||
- You must only ever run this script once per feature
|
||||
- The JSON output will contain `BRANCH_NAME` and `FEATURE_NUM`
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If Git is not installed or the current directory is not a Git repository:
|
||||
- Branch creation is skipped with a warning: `[specify] Warning: Git repository not detected; skipped branch creation`
|
||||
- The script still outputs `BRANCH_NAME` and `FEATURE_NUM` so the caller can reference them
|
||||
|
||||
## Output
|
||||
|
||||
The script outputs JSON with:
|
||||
- `BRANCH_NAME`: The branch name (e.g., `003-user-auth` or `20260319-143022-user-auth`)
|
||||
- `FEATURE_NUM`: The numeric or timestamp prefix used
|
||||
@ -1,54 +0,0 @@
|
||||
---
|
||||
name: speckit-git-initialize
|
||||
description: Initialize a Git repository with an initial commit
|
||||
compatibility: Requires spec-kit project structure with .specify/ directory
|
||||
metadata:
|
||||
author: github-spec-kit
|
||||
source: git:commands/speckit.git.initialize.md
|
||||
---
|
||||
|
||||
# Initialize Git Repository
|
||||
|
||||
Initialize a Git repository in the current project directory if one does not already exist.
|
||||
|
||||
## Execution
|
||||
|
||||
Run the appropriate script from the project root:
|
||||
|
||||
- **Bash**: `.specify/extensions/git/scripts/bash/initialize-repo.sh`
|
||||
- **PowerShell**: `.specify/extensions/git/scripts/powershell/initialize-repo.ps1`
|
||||
|
||||
If the extension scripts are not found, fall back to:
|
||||
- **Bash**: `git init && git add . && git commit -m "Initial commit from Specify template"`
|
||||
- **PowerShell**: `git init; git add .; git commit -m "Initial commit from Specify template"`
|
||||
|
||||
The script handles all checks internally:
|
||||
- Skips if Git is not available
|
||||
- Skips if already inside a Git repository
|
||||
- Runs `git init`, `git add .`, and `git commit` with an initial commit message
|
||||
|
||||
## Customization
|
||||
|
||||
Replace the script to add project-specific Git initialization steps:
|
||||
- Custom `.gitignore` templates
|
||||
- Default branch naming (`git config init.defaultBranch`)
|
||||
- Git LFS setup
|
||||
- Git hooks installation
|
||||
- Commit signing configuration
|
||||
- Git Flow initialization
|
||||
|
||||
## Output
|
||||
|
||||
On success:
|
||||
- `✓ Git repository initialized`
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If Git is not installed:
|
||||
- Warn the user
|
||||
- Skip repository initialization
|
||||
- The project continues to function without Git (specs can still be created under `specs/`)
|
||||
|
||||
If Git is installed but `git init`, `git add .`, or `git commit` fails:
|
||||
- Surface the error to the user
|
||||
- Stop this command rather than continuing with a partially initialized repository
|
||||
@ -1,50 +0,0 @@
|
||||
---
|
||||
name: speckit-git-remote
|
||||
description: Detect Git remote URL for GitHub integration
|
||||
compatibility: Requires spec-kit project structure with .specify/ directory
|
||||
metadata:
|
||||
author: github-spec-kit
|
||||
source: git:commands/speckit.git.remote.md
|
||||
---
|
||||
|
||||
# Detect Git Remote URL
|
||||
|
||||
Detect the Git remote URL for integration with GitHub services (e.g., issue creation).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Check if Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null`
|
||||
- If Git is not available, output a warning and return empty:
|
||||
```
|
||||
[specify] Warning: Git repository not detected; cannot determine remote URL
|
||||
```
|
||||
|
||||
## Execution
|
||||
|
||||
Run the following command to get the remote URL:
|
||||
|
||||
```bash
|
||||
git config --get remote.origin.url
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
Parse the remote URL and determine:
|
||||
|
||||
1. **Repository owner**: Extract from the URL (e.g., `github` from `https://github.com/github/spec-kit.git`)
|
||||
2. **Repository name**: Extract from the URL (e.g., `spec-kit` from `https://github.com/github/spec-kit.git`)
|
||||
3. **Is GitHub**: Whether the remote points to a GitHub repository
|
||||
|
||||
Supported URL formats:
|
||||
- HTTPS: `https://github.com/<owner>/<repo>.git`
|
||||
- SSH: `git@github.com:<owner>/<repo>.git`
|
||||
|
||||
> [!CAUTION]
|
||||
> ONLY report a GitHub repository if the remote URL actually points to github.com.
|
||||
> Do NOT assume the remote is GitHub if the URL format doesn't match.
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If Git is not installed, the directory is not a Git repository, or no remote is configured:
|
||||
- Return an empty result
|
||||
- Do NOT error — other workflows should continue without Git remote information
|
||||
@ -1,54 +0,0 @@
|
||||
---
|
||||
name: speckit-git-validate
|
||||
description: Validate current branch follows feature branch naming conventions
|
||||
compatibility: Requires spec-kit project structure with .specify/ directory
|
||||
metadata:
|
||||
author: github-spec-kit
|
||||
source: git:commands/speckit.git.validate.md
|
||||
---
|
||||
|
||||
# Validate Feature Branch
|
||||
|
||||
Validate that the current Git branch follows the expected feature branch naming conventions.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Check if Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null`
|
||||
- If Git is not available, output a warning and skip validation:
|
||||
```
|
||||
[specify] Warning: Git repository not detected; skipped branch validation
|
||||
```
|
||||
|
||||
## Validation Rules
|
||||
|
||||
Get the current branch name:
|
||||
|
||||
```bash
|
||||
git rev-parse --abbrev-ref HEAD
|
||||
```
|
||||
|
||||
The branch name must match one of these patterns:
|
||||
|
||||
1. **Sequential**: `^[0-9]{3,}-` (e.g., `001-feature-name`, `042-fix-bug`, `1000-big-feature`)
|
||||
2. **Timestamp**: `^[0-9]{8}-[0-9]{6}-` (e.g., `20260319-143022-feature-name`)
|
||||
|
||||
## Execution
|
||||
|
||||
If on a feature branch (matches either pattern):
|
||||
- Output: `✓ On feature branch: <branch-name>`
|
||||
- Check if the corresponding spec directory exists under `specs/`:
|
||||
- For sequential branches, look for `specs/<prefix>-*` where prefix matches the numeric portion
|
||||
- For timestamp branches, look for `specs/<prefix>-*` where prefix matches the `YYYYMMDD-HHMMSS` portion
|
||||
- If spec directory exists: `✓ Spec directory found: <path>`
|
||||
- If spec directory missing: `⚠ No spec directory found for prefix <prefix>`
|
||||
|
||||
If NOT on a feature branch:
|
||||
- Output: `✗ Not on a feature branch. Current branch: <branch-name>`
|
||||
- Output: `Feature branches should be named like: 001-feature-name or 20260319-143022-feature-name`
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If Git is not installed or the directory is not a Git repository:
|
||||
- Check the `SPECIFY_FEATURE` environment variable as a fallback
|
||||
- If set, validate that value against the naming patterns
|
||||
- If not set, skip validation with a warning
|
||||
@ -1,4 +1,4 @@
|
||||
[mcp_servers.laravel-boost]
|
||||
command = "./scripts/platform-sail"
|
||||
command = "vendor/bin/sail"
|
||||
args = ["artisan", "boost:mcp"]
|
||||
cwd = "/Users/ahmeddarrazi/Documents/projects/wt-plattform"
|
||||
cwd = "/Users/ahmeddarrazi/Documents/projects/TenantAtlas"
|
||||
|
||||
@ -3,9 +3,6 @@ apps/platform/node_modules/
|
||||
apps/website/node_modules/
|
||||
apps/website/.astro/
|
||||
apps/website/dist/
|
||||
apps/website/playwright-report/
|
||||
apps/website/test-results/
|
||||
apps/website/blob-report/
|
||||
dist/
|
||||
build/
|
||||
vendor/
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
description = "Auto-commit changes after a Spec Kit command completes"
|
||||
|
||||
# Source: git
|
||||
|
||||
prompt = """
|
||||
# Auto-Commit Changes
|
||||
|
||||
Automatically stage and commit all changes after a Spec Kit command completes.
|
||||
|
||||
## Behavior
|
||||
|
||||
This command is invoked as a hook after (or before) core commands. It:
|
||||
|
||||
1. Determines the event name from the hook context (e.g., if invoked as an `after_specify` hook, the event is `after_specify`; if `before_plan`, the event is `before_plan`)
|
||||
2. Checks `.specify/extensions/git/git-config.yml` for the `auto_commit` section
|
||||
3. Looks up the specific event key to see if auto-commit is enabled
|
||||
4. Falls back to `auto_commit.default` if no event-specific key exists
|
||||
5. Uses the per-command `message` if configured, otherwise a default message
|
||||
6. If enabled and there are uncommitted changes, runs `git add .` + `git commit`
|
||||
|
||||
## Execution
|
||||
|
||||
Determine the event name from the hook that triggered this command, then run the script:
|
||||
|
||||
- **Bash**: `.specify/extensions/git/scripts/bash/auto-commit.sh <event_name>`
|
||||
- **PowerShell**: `.specify/extensions/git/scripts/powershell/auto-commit.ps1 <event_name>`
|
||||
|
||||
Replace `<event_name>` with the actual hook event (e.g., `after_specify`, `before_plan`, `after_implement`).
|
||||
|
||||
## Configuration
|
||||
|
||||
In `.specify/extensions/git/git-config.yml`:
|
||||
|
||||
```yaml
|
||||
auto_commit:
|
||||
default: false # Global toggle — set true to enable for all commands
|
||||
after_specify:
|
||||
enabled: true # Override per-command
|
||||
message: "[Spec Kit] Add specification"
|
||||
after_plan:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Add implementation plan"
|
||||
```
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
- If Git is not available or the current directory is not a repository: skips with a warning
|
||||
- If no config file exists: skips (disabled by default)
|
||||
- If no changes to commit: skips with a message
|
||||
"""
|
||||
@ -1,69 +0,0 @@
|
||||
description = "Create a feature branch with sequential or timestamp numbering"
|
||||
|
||||
# Source: git
|
||||
|
||||
prompt = """
|
||||
# Create Feature Branch
|
||||
|
||||
Create and switch to a new git feature branch for the given specification. This command handles **branch creation only** — the spec directory and files are created by the core `/speckit.specify` workflow.
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
{{args}}
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Environment Variable Override
|
||||
|
||||
If the user explicitly provided `GIT_BRANCH_NAME` (e.g., via environment variable, argument, or in their request), pass it through to the script by setting the `GIT_BRANCH_NAME` environment variable before invoking the script. When `GIT_BRANCH_NAME` is set:
|
||||
- The script uses the exact value as the branch name, bypassing all prefix/suffix generation
|
||||
- `--short-name`, `--number`, and `--timestamp` flags are ignored
|
||||
- `FEATURE_NUM` is extracted from the name if it starts with a numeric prefix, otherwise set to the full branch name
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Verify Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null`
|
||||
- If Git is not available, warn the user and skip branch creation
|
||||
|
||||
## Branch Numbering Mode
|
||||
|
||||
Determine the branch numbering strategy by checking configuration in this order:
|
||||
|
||||
1. Check `.specify/extensions/git/git-config.yml` for `branch_numbering` value
|
||||
2. Check `.specify/init-options.json` for `branch_numbering` value (backward compatibility)
|
||||
3. Default to `sequential` if neither exists
|
||||
|
||||
## Execution
|
||||
|
||||
Generate a concise short name (2-4 words) for the branch:
|
||||
- Analyze the feature description and extract the most meaningful keywords
|
||||
- Use action-noun format when possible (e.g., "add-user-auth", "fix-payment-bug")
|
||||
- Preserve technical terms and acronyms (OAuth2, API, JWT, etc.)
|
||||
|
||||
Run the appropriate script based on your platform:
|
||||
|
||||
- **Bash**: `.specify/extensions/git/scripts/bash/create-new-feature.sh --json --short-name "<short-name>" "<feature description>"`
|
||||
- **Bash (timestamp)**: `.specify/extensions/git/scripts/bash/create-new-feature.sh --json --timestamp --short-name "<short-name>" "<feature description>"`
|
||||
- **PowerShell**: `.specify/extensions/git/scripts/powershell/create-new-feature.ps1 -Json -ShortName "<short-name>" "<feature description>"`
|
||||
- **PowerShell (timestamp)**: `.specify/extensions/git/scripts/powershell/create-new-feature.ps1 -Json -Timestamp -ShortName "<short-name>" "<feature description>"`
|
||||
|
||||
**IMPORTANT**:
|
||||
- Do NOT pass `--number` — the script determines the correct next number automatically
|
||||
- Always include the JSON flag (`--json` for Bash, `-Json` for PowerShell) so the output can be parsed reliably
|
||||
- You must only ever run this script once per feature
|
||||
- The JSON output will contain `BRANCH_NAME` and `FEATURE_NUM`
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If Git is not installed or the current directory is not a Git repository:
|
||||
- Branch creation is skipped with a warning: `[specify] Warning: Git repository not detected; skipped branch creation`
|
||||
- The script still outputs `BRANCH_NAME` and `FEATURE_NUM` so the caller can reference them
|
||||
|
||||
## Output
|
||||
|
||||
The script outputs JSON with:
|
||||
- `BRANCH_NAME`: The branch name (e.g., `003-user-auth` or `20260319-143022-user-auth`)
|
||||
- `FEATURE_NUM`: The numeric or timestamp prefix used
|
||||
"""
|
||||
@ -1,51 +0,0 @@
|
||||
description = "Initialize a Git repository with an initial commit"
|
||||
|
||||
# Source: git
|
||||
|
||||
prompt = """
|
||||
# Initialize Git Repository
|
||||
|
||||
Initialize a Git repository in the current project directory if one does not already exist.
|
||||
|
||||
## Execution
|
||||
|
||||
Run the appropriate script from the project root:
|
||||
|
||||
- **Bash**: `.specify/extensions/git/scripts/bash/initialize-repo.sh`
|
||||
- **PowerShell**: `.specify/extensions/git/scripts/powershell/initialize-repo.ps1`
|
||||
|
||||
If the extension scripts are not found, fall back to:
|
||||
- **Bash**: `git init && git add . && git commit -m "Initial commit from Specify template"`
|
||||
- **PowerShell**: `git init; git add .; git commit -m "Initial commit from Specify template"`
|
||||
|
||||
The script handles all checks internally:
|
||||
- Skips if Git is not available
|
||||
- Skips if already inside a Git repository
|
||||
- Runs `git init`, `git add .`, and `git commit` with an initial commit message
|
||||
|
||||
## Customization
|
||||
|
||||
Replace the script to add project-specific Git initialization steps:
|
||||
- Custom `.gitignore` templates
|
||||
- Default branch naming (`git config init.defaultBranch`)
|
||||
- Git LFS setup
|
||||
- Git hooks installation
|
||||
- Commit signing configuration
|
||||
- Git Flow initialization
|
||||
|
||||
## Output
|
||||
|
||||
On success:
|
||||
- `✓ Git repository initialized`
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If Git is not installed:
|
||||
- Warn the user
|
||||
- Skip repository initialization
|
||||
- The project continues to function without Git (specs can still be created under `specs/`)
|
||||
|
||||
If Git is installed but `git init`, `git add .`, or `git commit` fails:
|
||||
- Surface the error to the user
|
||||
- Stop this command rather than continuing with a partially initialized repository
|
||||
"""
|
||||
@ -1,47 +0,0 @@
|
||||
description = "Detect Git remote URL for GitHub integration"
|
||||
|
||||
# Source: git
|
||||
|
||||
prompt = """
|
||||
# Detect Git Remote URL
|
||||
|
||||
Detect the Git remote URL for integration with GitHub services (e.g., issue creation).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Check if Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null`
|
||||
- If Git is not available, output a warning and return empty:
|
||||
```
|
||||
[specify] Warning: Git repository not detected; cannot determine remote URL
|
||||
```
|
||||
|
||||
## Execution
|
||||
|
||||
Run the following command to get the remote URL:
|
||||
|
||||
```bash
|
||||
git config --get remote.origin.url
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
Parse the remote URL and determine:
|
||||
|
||||
1. **Repository owner**: Extract from the URL (e.g., `github` from `https://github.com/github/spec-kit.git`)
|
||||
2. **Repository name**: Extract from the URL (e.g., `spec-kit` from `https://github.com/github/spec-kit.git`)
|
||||
3. **Is GitHub**: Whether the remote points to a GitHub repository
|
||||
|
||||
Supported URL formats:
|
||||
- HTTPS: `https://github.com/<owner>/<repo>.git`
|
||||
- SSH: `git@github.com:<owner>/<repo>.git`
|
||||
|
||||
> [!CAUTION]
|
||||
> ONLY report a GitHub repository if the remote URL actually points to github.com.
|
||||
> Do NOT assume the remote is GitHub if the URL format doesn't match.
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If Git is not installed, the directory is not a Git repository, or no remote is configured:
|
||||
- Return an empty result
|
||||
- Do NOT error — other workflows should continue without Git remote information
|
||||
"""
|
||||
@ -1,51 +0,0 @@
|
||||
description = "Validate current branch follows feature branch naming conventions"
|
||||
|
||||
# Source: git
|
||||
|
||||
prompt = """
|
||||
# Validate Feature Branch
|
||||
|
||||
Validate that the current Git branch follows the expected feature branch naming conventions.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Check if Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null`
|
||||
- If Git is not available, output a warning and skip validation:
|
||||
```
|
||||
[specify] Warning: Git repository not detected; skipped branch validation
|
||||
```
|
||||
|
||||
## Validation Rules
|
||||
|
||||
Get the current branch name:
|
||||
|
||||
```bash
|
||||
git rev-parse --abbrev-ref HEAD
|
||||
```
|
||||
|
||||
The branch name must match one of these patterns:
|
||||
|
||||
1. **Sequential**: `^[0-9]{3,}-` (e.g., `001-feature-name`, `042-fix-bug`, `1000-big-feature`)
|
||||
2. **Timestamp**: `^[0-9]{8}-[0-9]{6}-` (e.g., `20260319-143022-feature-name`)
|
||||
|
||||
## Execution
|
||||
|
||||
If on a feature branch (matches either pattern):
|
||||
- Output: `✓ On feature branch: <branch-name>`
|
||||
- Check if the corresponding spec directory exists under `specs/`:
|
||||
- For sequential branches, look for `specs/<prefix>-*` where prefix matches the numeric portion
|
||||
- For timestamp branches, look for `specs/<prefix>-*` where prefix matches the `YYYYMMDD-HHMMSS` portion
|
||||
- If spec directory exists: `✓ Spec directory found: <path>`
|
||||
- If spec directory missing: `⚠ No spec directory found for prefix <prefix>`
|
||||
|
||||
If NOT on a feature branch:
|
||||
- Output: `✗ Not on a feature branch. Current branch: <branch-name>`
|
||||
- Output: `Feature branches should be named like: 001-feature-name or 20260319-143022-feature-name`
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If Git is not installed or the directory is not a Git repository:
|
||||
- Check the `SPECIFY_FEATURE` environment variable as a fallback
|
||||
- If set, validate that value against the naming patterns
|
||||
- If not set, skip validation with a warning
|
||||
"""
|
||||
@ -1,80 +0,0 @@
|
||||
name: Browser Lane
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '43 4 * * 1-5'
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
browser:
|
||||
if: ${{ github.event_name != 'schedule' || vars.TENANTATLAS_ENABLE_BROWSER_SCHEDULE == '1' }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SAIL_TTY: 'false'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.4'
|
||||
coverage: none
|
||||
tools: composer:v2
|
||||
|
||||
- name: Install platform dependencies
|
||||
run: |
|
||||
cd apps/platform
|
||||
if [[ ! -f .env ]]; then
|
||||
cp .env.example .env
|
||||
fi
|
||||
composer install --no-interaction --prefer-dist --optimize-autoloader
|
||||
|
||||
- name: Boot Sail
|
||||
run: |
|
||||
cd apps/platform
|
||||
./vendor/bin/sail up -d
|
||||
./vendor/bin/sail artisan key:generate --force --no-interaction
|
||||
|
||||
- name: Resolve Browser context
|
||||
id: context
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "schedule" ]]; then
|
||||
echo "workflow_id=browser-scheduled" >> "$GITHUB_OUTPUT"
|
||||
echo "trigger_class=scheduled" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "workflow_id=browser-manual" >> "$GITHUB_OUTPUT"
|
||||
echo "trigger_class=manual" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Run Browser lane
|
||||
run: ./scripts/platform-test-lane browser --workflow-id=${{ steps.context.outputs.workflow_id }} --trigger-class=${{ steps.context.outputs.trigger_class }}
|
||||
|
||||
- name: Refresh Browser report
|
||||
if: always()
|
||||
env:
|
||||
TENANTATLAS_GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
run: ./scripts/platform-test-report browser --workflow-id=${{ steps.context.outputs.workflow_id }} --trigger-class=${{ steps.context.outputs.trigger_class }} --fetch-latest-history
|
||||
|
||||
- name: Stage Browser artifacts
|
||||
if: always()
|
||||
run: ./scripts/platform-test-artifacts browser .gitea-artifacts/browser --workflow-id=${{ steps.context.outputs.workflow_id }} --trigger-class=${{ steps.context.outputs.trigger_class }}
|
||||
|
||||
- name: Upload Browser artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: browser-artifacts
|
||||
path: .gitea-artifacts/browser
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Stop Sail
|
||||
if: always()
|
||||
run: |
|
||||
cd apps/platform
|
||||
./vendor/bin/sail stop
|
||||
@ -1,80 +0,0 @@
|
||||
name: Heavy Governance Lane
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '17 4 * * 1-5'
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
heavy-governance:
|
||||
if: ${{ github.event_name != 'schedule' || vars.TENANTATLAS_ENABLE_HEAVY_GOVERNANCE_SCHEDULE == '1' }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SAIL_TTY: 'false'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.4'
|
||||
coverage: none
|
||||
tools: composer:v2
|
||||
|
||||
- name: Install platform dependencies
|
||||
run: |
|
||||
cd apps/platform
|
||||
if [[ ! -f .env ]]; then
|
||||
cp .env.example .env
|
||||
fi
|
||||
composer install --no-interaction --prefer-dist --optimize-autoloader
|
||||
|
||||
- name: Boot Sail
|
||||
run: |
|
||||
cd apps/platform
|
||||
./vendor/bin/sail up -d
|
||||
./vendor/bin/sail artisan key:generate --force --no-interaction
|
||||
|
||||
- name: Resolve Heavy Governance context
|
||||
id: context
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "schedule" ]]; then
|
||||
echo "workflow_id=heavy-governance-scheduled" >> "$GITHUB_OUTPUT"
|
||||
echo "trigger_class=scheduled" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "workflow_id=heavy-governance-manual" >> "$GITHUB_OUTPUT"
|
||||
echo "trigger_class=manual" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Run Heavy Governance lane
|
||||
run: ./scripts/platform-test-lane heavy-governance --workflow-id=${{ steps.context.outputs.workflow_id }} --trigger-class=${{ steps.context.outputs.trigger_class }}
|
||||
|
||||
- name: Refresh Heavy Governance report
|
||||
if: always()
|
||||
env:
|
||||
TENANTATLAS_GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
run: ./scripts/platform-test-report heavy-governance --workflow-id=${{ steps.context.outputs.workflow_id }} --trigger-class=${{ steps.context.outputs.trigger_class }} --fetch-latest-history
|
||||
|
||||
- name: Stage Heavy Governance artifacts
|
||||
if: always()
|
||||
run: ./scripts/platform-test-artifacts heavy-governance .gitea-artifacts/heavy-governance --workflow-id=${{ steps.context.outputs.workflow_id }} --trigger-class=${{ steps.context.outputs.trigger_class }}
|
||||
|
||||
- name: Upload Heavy Governance artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: heavy-governance-artifacts
|
||||
path: .gitea-artifacts/heavy-governance
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Stop Sail
|
||||
if: always()
|
||||
run: |
|
||||
cd apps/platform
|
||||
./vendor/bin/sail stop
|
||||
@ -1,68 +0,0 @@
|
||||
name: Main Confidence
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
confidence:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SAIL_TTY: 'false'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.4'
|
||||
coverage: none
|
||||
tools: composer:v2
|
||||
|
||||
- name: Install platform dependencies
|
||||
run: |
|
||||
cd apps/platform
|
||||
if [[ ! -f .env ]]; then
|
||||
cp .env.example .env
|
||||
fi
|
||||
composer install --no-interaction --prefer-dist --optimize-autoloader
|
||||
|
||||
- name: Boot Sail
|
||||
run: |
|
||||
cd apps/platform
|
||||
./vendor/bin/sail up -d
|
||||
./vendor/bin/sail artisan key:generate --force --no-interaction
|
||||
|
||||
- name: Run Confidence lane
|
||||
run: ./scripts/platform-test-lane confidence --workflow-id=main-confidence --trigger-class=mainline-push
|
||||
|
||||
- name: Refresh Confidence report
|
||||
if: always()
|
||||
env:
|
||||
TENANTATLAS_GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
run: ./scripts/platform-test-report confidence --workflow-id=main-confidence --trigger-class=mainline-push --fetch-latest-history
|
||||
|
||||
- name: Stage Confidence artifacts
|
||||
if: always()
|
||||
run: ./scripts/platform-test-artifacts confidence .gitea-artifacts/main-confidence --workflow-id=main-confidence --trigger-class=mainline-push
|
||||
|
||||
- name: Upload Confidence artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: confidence-artifacts
|
||||
path: .gitea-artifacts/main-confidence
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Stop Sail
|
||||
if: always()
|
||||
run: |
|
||||
cd apps/platform
|
||||
./vendor/bin/sail stop
|
||||
@ -1,70 +0,0 @@
|
||||
name: PR Fast Feedback
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
fast-feedback:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SAIL_TTY: 'false'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.4'
|
||||
coverage: none
|
||||
tools: composer:v2
|
||||
|
||||
- name: Install platform dependencies
|
||||
run: |
|
||||
cd apps/platform
|
||||
if [[ ! -f .env ]]; then
|
||||
cp .env.example .env
|
||||
fi
|
||||
composer install --no-interaction --prefer-dist --optimize-autoloader
|
||||
|
||||
- name: Boot Sail
|
||||
run: |
|
||||
cd apps/platform
|
||||
./vendor/bin/sail up -d
|
||||
./vendor/bin/sail artisan key:generate --force --no-interaction
|
||||
|
||||
- name: Run Fast Feedback lane
|
||||
run: ./scripts/platform-test-lane fast-feedback --workflow-id=pr-fast-feedback --trigger-class=pull-request
|
||||
|
||||
- name: Refresh Fast Feedback report
|
||||
if: always()
|
||||
env:
|
||||
TENANTATLAS_GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
run: ./scripts/platform-test-report fast-feedback --workflow-id=pr-fast-feedback --trigger-class=pull-request --fetch-latest-history
|
||||
|
||||
- name: Stage Fast Feedback artifacts
|
||||
if: always()
|
||||
run: ./scripts/platform-test-artifacts fast-feedback .gitea-artifacts/pr-fast-feedback --workflow-id=pr-fast-feedback --trigger-class=pull-request
|
||||
|
||||
- name: Upload Fast Feedback artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: fast-feedback-artifacts
|
||||
path: .gitea-artifacts/pr-fast-feedback
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Stop Sail
|
||||
if: always()
|
||||
run: |
|
||||
cd apps/platform
|
||||
./vendor/bin/sail stop
|
||||
111
.github/agents/copilot-instructions.md
vendored
111
.github/agents/copilot-instructions.md
vendored
@ -7,7 +7,6 @@ ## Relocation override
|
||||
- Human-facing commands should use `cd apps/platform && ...`.
|
||||
- Repo-root tooling may delegate via `./scripts/platform-sail` when it cannot set a nested working directory.
|
||||
- Repo-root JavaScript orchestration uses `corepack pnpm install`, `corepack pnpm dev:platform`, `corepack pnpm dev:website`, `corepack pnpm dev`, `corepack pnpm build:website`, and `corepack pnpm build:platform`.
|
||||
- `corepack pnpm dev:platform` starts the platform Sail stack and the Laravel panel Vite watcher. `corepack pnpm dev` starts that platform watcher plus the website dev server.
|
||||
- `apps/website` is a standalone Astro app, not a second Laravel runtime, so Boost MCP remains platform-only.
|
||||
- If any generated technology note below conflicts with the current repo, trust `apps/platform/composer.json`, `apps/platform/package.json`, and the live Laravel application metadata over stale generated entries.
|
||||
|
||||
@ -174,98 +173,6 @@ ## Active Technologies
|
||||
- PostgreSQL through existing workspace-owned and tenant-owned resource models; no schema change planned (192-record-header-discipline)
|
||||
- PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `OperateHubShell`, `CanonicalNavigationContext`, `CanonicalAdminTenantFilterState`, `UiEnforcement`, `ActionSurfaceValidator`, and Filament page or resource action builders (193-monitoring-action-hierarchy)
|
||||
- PostgreSQL through existing workspace-owned and tenant-owned models; no schema change planned (193-monitoring-action-hierarchy)
|
||||
- PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `UiEnforcement`, existing audit loggers (`AuditLogger`, `WorkspaceAuditLogger`, `SystemConsoleAuditLogger`), existing mutation services (`FindingExceptionService`, `FindingWorkflowService`, `TenantReviewLifecycleService`, `EvidenceSnapshotService`, `OperationRunTriageService`) (194-governance-friction-hardening)
|
||||
- PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `ActionSurfaceDiscovery`, `ActionSurfaceValidator`, `ActionSurfaceExemptions`, `GovernanceActionCatalog`, `UiEnforcement`, `WorkspaceContext`, and existing system/onboarding/auth helpers (195-action-surface-closure)
|
||||
- PostgreSQL through existing workspace-owned, tenant-owned, and system-visible models; no schema change planned (195-action-surface-closure)
|
||||
- PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `DependencyQueryService`, `DependencyTargetResolver`, `TenantRequiredPermissionsViewModelBuilder`, `ArtifactTruthPresenter`, `WorkspaceContext`, Filament `InteractsWithTable`, Filament `TableComponent`, and existing badge and action-surface helpers (196-hard-filament-nativity-cleanup)
|
||||
- PostgreSQL through existing tenant-owned and workspace-context models (`InventoryItem`, `InventoryLink`, `TenantPermission`, `EvidenceSnapshot`, `TenantReview`); no schema change planned (196-hard-filament-nativity-cleanup)
|
||||
- PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, Laravel Sail, existing `BaselineScope`, `InventoryPolicyTypeMeta`, `BaselineSupportCapabilityGuard`, `BaselineCaptureService`, and `BaselineCompareService` (202-governance-subject-taxonomy)
|
||||
- PostgreSQL via existing `baseline_profiles.scope_jsonb`, `baseline_tenant_assignments.override_scope_jsonb`, and `operation_runs.context`; no new tables planned (202-governance-subject-taxonomy)
|
||||
- PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Laravel Sail, existing `BaselineCompareService`, `CompareBaselineToTenantJob`, `SubjectResolver`, `CurrentStateHashResolver`, `DriftHasher`, `BaselineCompareSummaryAssessor`, and finding lifecycle services (203-baseline-compare-strategy)
|
||||
- PostgreSQL via existing baseline snapshots, baseline snapshot items, `operation_runs`, findings, and baseline scope JSON; no new top-level tables planned (203-baseline-compare-strategy)
|
||||
- PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Laravel Sail, existing `GovernanceSubjectTaxonomyRegistry`, `BaselineScope`, `CompareStrategyRegistry`, `OperationCatalog`, `OperationRunType`, `ReasonTranslator`, `ReasonResolutionEnvelope`, `ProviderReasonTranslator`, and current Filament monitoring or review surfaces (204-platform-core-vocabulary-hardening)
|
||||
- PostgreSQL via existing `operation_runs.type`, `operation_runs.context`, `baseline_profiles.scope_jsonb`, `baseline_snapshot_items`, findings, evidence payloads, and current config-backed registries; no new top-level tables planned (204-platform-core-vocabulary-hardening)
|
||||
- PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Laravel Sail, existing `BaselineCompareService`, `CompareBaselineToTenantJob`, `CompareStrategyRegistry`, `IntuneCompareStrategy`, `CurrentStateHashResolver`, and current finding lifecycle services (205-compare-job-cleanup)
|
||||
- PostgreSQL via existing baseline snapshots, baseline snapshot items, inventory items, `operation_runs`, findings, and current run-context JSON; no new storage planned (205-compare-job-cleanup)
|
||||
- PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `VerificationReportViewer`, `VerificationReportChangeIndicator`, `PolicyNormalizer`, `VersionDiff`, `DriftFindingDiffBuilder`, and `SettingsCatalogSettingsTable` (197-shared-detail-contract)
|
||||
- PostgreSQL unchanged; no new persistence, cache store, or durable UI artifact (197-shared-detail-contract)
|
||||
- PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `CanonicalAdminTenantFilterState`, `CanonicalNavigationContext`, `OperateHubShell`, Filament `InteractsWithTable`, and page-local Livewire state on the affected Filament pages (198-monitoring-page-state)
|
||||
- PostgreSQL plus existing Laravel session-backed table filter, search, and sort persistence; no schema change planned (198-monitoring-page-state)
|
||||
- PHP 8.4.15 + Laravel 12, Pest v4, PHPUnit 12, Pest Browser plugin, Filament v5, Livewire v4, Laravel Sail (206-test-suite-governance)
|
||||
- SQLite `:memory:` for the default test configuration, dedicated PostgreSQL config for the schema-level `Pgsql` suite, and local runner artifacts under `apps/platform/storage/logs/test-lanes` (206-test-suite-governance)
|
||||
- PHP 8.4.15 + Laravel 12, Pest v4, PHPUnit 12, Filament v5, Livewire v4, Laravel Sail (207-shared-test-fixture-slimming)
|
||||
- SQLite `:memory:` for the default test environment, isolated PostgreSQL coverage via the existing dedicated suite, and lane-measurement artifacts under the app-root contract path `storage/logs/test-lanes` (207-shared-test-fixture-slimming)
|
||||
- SQLite `:memory:` for the default test environment, existing lane artifacts under the app-root contract path `storage/logs/test-lanes`, and no new product persistence (208-heavy-suite-segmentation)
|
||||
- SQLite `:memory:` for the default test environment, mixed database strategy for some heavy-governance families as declared in `TestLaneManifest`, and existing lane artifacts under the app-root contract path `storage/logs/test-lanes` (209-heavy-governance-cost)
|
||||
- PHP 8.4.15 for repo-truth test governance, Bash for repo-root wrappers, and GitHub-compatible Gitea Actions workflow YAML under `.gitea/workflows/` + Laravel 12, Pest v4, PHPUnit 12, Filament v5, Livewire v4, Laravel Sail, Gitea Actions backed by `act_runner`, and the existing `Tests\Support\TestLaneManifest`, `TestLaneBudget`, and `TestLaneReport` seams (210-ci-matrix-budget-enforcement)
|
||||
- SQLite `:memory:` for default lane execution, filesystem artifacts under the app-root contract path `storage/logs/test-lanes`, checked-in workflow YAML under `.gitea/workflows/`, and no new product database persistence (210-ci-matrix-budget-enforcement)
|
||||
- PHP 8.4.15 for repo-truth governance logic, Bash for repo-root wrappers, GitHub-compatible Gitea Actions workflow YAML under `.gitea/workflows/`, plus JSON Schema and logical OpenAPI for repository contracts + Laravel 12, Pest v4, PHPUnit 12, Filament v5, Livewire v4, Laravel Sail, Gitea Actions backed by `act_runner`, uploaded artifact bundles, and the existing `Tests\Support\TestLaneManifest`, `TestLaneBudget`, and `TestLaneReport` seams (211-runtime-trend-recalibration)
|
||||
- SQLite `:memory:` for lane execution, filesystem artifacts under `apps/platform/storage/logs/test-lanes`, staged CI bundles under `.gitea-artifacts/<workflow-profile>`, bounded derived trend/history artifacts adjacent to current lane artifacts, and no new product database persistence (211-runtime-trend-recalibration)
|
||||
- Markdown for repository governance artifacts, JSON Schema plus logical OpenAPI for planning contracts, and Bash-backed SpecKit scripts already present in the repo + `.specify/memory/constitution.md`, `.specify/templates/spec-template.md`, `.specify/templates/plan-template.md`, `.specify/templates/tasks-template.md`, `.specify/templates/checklist-template.md`, `.specify/README.md`, `README.md`, and the existing Specs 206 through 211 governance vocabulary (212-test-authoring-guardrails)
|
||||
- Repository-owned markdown and contract artifacts under `.specify/`, `specs/212-test-authoring-guardrails/`, and root documentation files; no product database persistence (212-test-authoring-guardrails)
|
||||
- PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `WorkspaceContext`, `OperateHubShell`, `EnsureFilamentTenantSelected`, `WorkspaceRedirectResolver`, `WorkspaceIntendedUrl`, `TenantPageCategory`, and `ResolvesPanelTenantContext` (199-global-context-shell-contract)
|
||||
- PostgreSQL unchanged plus existing Laravel session keys `current_workspace_id`, `workspace_intended_url`, and `workspace_last_tenant_ids`; no schema change planned (199-global-context-shell-contract)
|
||||
- Markdown governance artifacts in a PHP 8.4.15 / Laravel 12 / Filament v5 / Livewire v4 repository + `.specify/memory/constitution.md`, `docs/ui/operator-ux-surface-standards.md`, adjacent Specs 196 through 199, existing UI rule IDs `UI-SURF-001`, `ACTSURF-001`, `UI-HARD-001`, `UI-EX-001`, `UI-FIL-001`, `DECIDE-001`, and `UX-001` (200-filament-surface-rules)
|
||||
- Astro 6.0.0 templates + TypeScript 5.x (explicit setup in `apps/website`) + Astro 6, Tailwind CSS v4, custom Astro component primitives (shadcn-inspired), lightweight Playwright browser smoke tests (213-website-foundation-v0)
|
||||
- Static filesystem content, styles, and assets under `apps/website/src` and `apps/website/public`; no database (213-website-foundation-v0)
|
||||
- Astro 6.0.0 templates + TypeScript 5.9 strict + Astro 6, Tailwind CSS v4 via `@tailwindcss/vite`, Astro content collections, local Astro component primitives, Playwright browser smoke tests (214-website-visual-foundation)
|
||||
- Static filesystem content, styles, assets, and content collections under `apps/website/src` and `apps/website/public`; no database (214-website-visual-foundation)
|
||||
- Markdown governance artifacts, JSON Schema plus logical OpenAPI planning contracts, and Bash-backed SpecKit scripts inside a PHP 8.4.15 / Laravel 12 / Filament v5 / Livewire v4 repository + `.specify/memory/constitution.md`, `.specify/templates/spec-template.md`, `.specify/templates/plan-template.md`, `.specify/templates/tasks-template.md`, `.specify/templates/checklist-template.md`, `.specify/README.md`, `docs/ui/operator-ux-surface-standards.md`, and Specs 196 through 200 (201-enforcement-review-guardrails)
|
||||
- Repository-owned markdown and contract artifacts under `.specify/` and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/201-enforcement-review-guardrails/`; no product database persistence (201-enforcement-review-guardrails)
|
||||
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade + Filament v5, Livewire v4, Pest v4, Laravel Sail, `ArtifactTruthPresenter`, `ArtifactTruthEnvelope`, `OperatorExplanationBuilder`, `BaselineSnapshotPresenter`, `BadgeCatalog`, `BadgeRenderer`, existing governance Filament resources/pages, and current Enterprise Detail builders (214-governance-outcome-compression)
|
||||
- PostgreSQL via existing `baseline_snapshots`, `evidence_snapshots`, `evidence_snapshot_items`, `tenant_reviews`, `review_packs`, and `operation_runs` tables; no schema change planned (214-governance-outcome-compression)
|
||||
- Astro 6.0.0 templates + TypeScript 5.9 strict + Astro 6, Tailwind CSS v4 via `@tailwindcss/vite`, Astro content collections, local Astro layout/primitive/content helpers, Playwright smoke tests (215-website-core-pages)
|
||||
- Static filesystem pages, content modules, and Astro content collections under `apps/website/src` and `apps/website/public`; no database (215-website-core-pages)
|
||||
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4 + Filament Resources/Pages/Actions, Livewire 4, Pest 4, `ProviderOperationStartGate`, `ProviderOperationRegistry`, `ProviderConnectionResolver`, `OperationRunService`, `ProviderNextStepsRegistry`, `ReasonPresenter`, `OperationUxPresenter`, `OperationRunLinks` (216-provider-dispatch-gate)
|
||||
- PostgreSQL via existing `operation_runs`, `provider_connections`, `managed_tenant_onboarding_sessions`, `restore_runs`, and tenant-owned runtime records; no new tables planned (216-provider-dispatch-gate)
|
||||
- Astro 6.0.0 templates + TypeScript 5.9.x + Astro 6, Tailwind CSS v4, local Astro layout/section primitives, Astro content collections, Playwright browser smoke tests (217-homepage-structure)
|
||||
- Static filesystem content, Astro content collections, and assets under `apps/website/src` and `apps/website/public`; no database (217-homepage-structure)
|
||||
- Astro 6.0.0 templates + TypeScript 5.9.x + Astro 6, Tailwind CSS v4, existing Astro content modules and section primitives, Playwright browser smoke tests (218-homepage-hero)
|
||||
- Static filesystem content and assets under `apps/website/src` and `apps/website/public`; no database (218-homepage-hero)
|
||||
- PHP 8.4.15 / Laravel 12 + Filament v5, Livewire v4.0+, Pest v4, Tailwind CSS v4 (219-finding-ownership-semantics)
|
||||
- PostgreSQL via Sail; existing `findings.owner_user_id`, `findings.assignee_user_id`, and `finding_exceptions.owner_user_id` fields; no schema changes planned (219-finding-ownership-semantics)
|
||||
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade + Filament v5, Livewire v4, Pest v4, Laravel Sail, `TenantlessOperationRunViewer`, `OperationRunResource`, `ArtifactTruthPresenter`, `OperatorExplanationBuilder`, `ReasonPresenter`, `OperationUxPresenter`, `SummaryCountsNormalizer`, and the existing enterprise-detail builders (220-governance-run-summaries)
|
||||
- PostgreSQL via existing `operation_runs` plus related `baseline_snapshots`, `evidence_snapshots`, `tenant_reviews`, and `review_packs`; no schema changes planned (220-governance-run-summaries)
|
||||
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade + Filament admin panel pages, `Finding`, `FindingResource`, `WorkspaceOverviewBuilder`, `WorkspaceContext`, `WorkspaceCapabilityResolver`, `CapabilityResolver`, `CanonicalAdminTenantFilterState`, and `CanonicalNavigationContext` (221-findings-operator-inbox)
|
||||
- PostgreSQL via existing `findings`, `tenants`, `tenant_memberships`, and workspace context session state; no schema changes planned (221-findings-operator-inbox)
|
||||
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade + Filament admin pages/tables/actions/notifications, `Finding`, `FindingResource`, `FindingWorkflowService`, `FindingPolicy`, `CapabilityResolver`, `CanonicalAdminTenantFilterState`, `CanonicalNavigationContext`, `WorkspaceContext`, and `UiEnforcement` (222-findings-intake-team-queue)
|
||||
- PostgreSQL via existing `findings`, `tenants`, `tenant_memberships`, `audit_logs`, and workspace session context; no schema changes planned (222-findings-intake-team-queue)
|
||||
- TypeScript 5.9, Astro 6, Node.js 20+ + Astro, astro-icon, Tailwind CSS v4, Playwright 1.59 (223-astrodeck-website-rebuild)
|
||||
- File-based route files, Astro content collections under `src/content`, public assets, and planning documents under `specs/223-astrodeck-website-rebuild`; no database (223-astrodeck-website-rebuild)
|
||||
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade + Laravel notifications (`database` channel), Filament database notifications, `Finding`, `FindingWorkflowService`, `FindingSlaPolicy`, `AlertRule`, `AlertDelivery`, `AlertDispatchService`, `EvaluateAlertsJob`, `CapabilityResolver`, `WorkspaceContext`, `TenantMembership`, `FindingResource` (224-findings-notifications-escalation)
|
||||
- PostgreSQL via existing `findings`, `alert_rules`, `alert_deliveries`, `notifications`, `tenant_memberships`, and `audit_logs`; no schema changes planned (224-findings-notifications-escalation)
|
||||
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade + `Finding`, `FindingResource`, `MyFindingsInbox`, `FindingsIntakeQueue`, `WorkspaceOverviewBuilder`, `EnsureFilamentTenantSelected`, `FindingWorkflowService`, `AuditLog`, `TenantMembership`, Filament page and table primitives (225-assignment-hygiene)
|
||||
- PostgreSQL via existing `findings`, `audit_logs`, `tenant_memberships`, and `users`; no schema changes planned (225-assignment-hygiene)
|
||||
- Markdown artifacts + Astro 6.0.0 + TypeScript 5.9 context for source discovery + Repository spec workflow (`.specify`), Astro website source tree under `apps/website/src`, existing component taxonomy (`primitives`, `content`, `sections`, `layout`) (226-astrodeck-inventory-planning)
|
||||
- Filesystem only (`specs/226-astrodeck-inventory-planning/*`) (226-astrodeck-inventory-planning)
|
||||
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade + `App\Models\Finding`, `App\Filament\Resources\FindingResource`, `App\Services\Findings\FindingWorkflowService`, `App\Services\Baselines\BaselineAutoCloseService`, `App\Services\EntraAdminRoles\EntraAdminRolesFindingGenerator`, `App\Services\PermissionPosture\PermissionPostureFindingGenerator`, `App\Jobs\CompareBaselineToTenantJob`, `App\Filament\Pages\Reviews\ReviewRegister`, `App\Filament\Resources\TenantReviewResource`, `BadgeCatalog`, `BadgeRenderer`, `AuditLog` metadata via `AuditLogger` (231-finding-outcome-taxonomy)
|
||||
- PostgreSQL via existing `findings`, `finding_exceptions`, `tenant_reviews`, `stored_reports`, and audit-log tables; no schema changes planned (231-finding-outcome-taxonomy)
|
||||
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4 + Filament Resources/Pages/Widgets, Pest v4, `App\Support\OperationRunLinks`, `App\Support\System\SystemOperationRunLinks`, `App\Support\Navigation\CanonicalNavigationContext`, `App\Support\Navigation\RelatedNavigationResolver`, existing workspace and tenant authorization helpers (232-operation-run-link-contract)
|
||||
- PostgreSQL-backed existing `operation_runs`, `tenants`, and `workspaces` records plus current session-backed canonical navigation state; no new persistence (232-operation-run-link-contract)
|
||||
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4 + Filament widgets/resources/pages, Pest v4, `App\Models\OperationRun`, `App\Support\Operations\OperationRunFreshnessState`, `App\Services\Operations\OperationLifecycleReconciler`, `App\Support\OpsUx\OperationUxPresenter`, `App\Support\OpsUx\ActiveRuns`, `App\Support\Badges\BadgeCatalog` / `BadgeRenderer`, `App\Support\Workspaces\WorkspaceOverviewBuilder`, `App\Support\OperationRunLinks` (233-stale-run-visibility)
|
||||
- Existing PostgreSQL `operation_runs` records and current session/query-backed monitoring navigation state; no new persistence (233-stale-run-visibility)
|
||||
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4 + `App\Models\BaselineProfile`, `App\Support\Baselines\BaselineProfileStatus`, `App\Support\Badges\BadgeCatalog`, `App\Support\Badges\BadgeDomain`, `Database\Factories\TenantFactory`, `App\Console\Commands\SeedBackupHealthBrowserFixture`, existing tenant-truth and baseline-profile Pest tests (234-dead-transitional-residue)
|
||||
- Existing PostgreSQL `baseline_profiles` and `tenants` tables; no new persistence and no schema migration in this slice (234-dead-transitional-residue)
|
||||
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4 + `BaselineCaptureService`, `CaptureBaselineSnapshotJob`, `BaselineReasonCodes`, `BaselineCompareStats`, `ReasonTranslator`, `GovernanceRunDiagnosticSummaryBuilder`, `OperationRunService`, `BaselineProfile`, `BaselineSnapshot`, `OperationRunOutcome`, existing Filament capture/compare surfaces (235-baseline-capture-truth)
|
||||
- Existing PostgreSQL tables only; no new table or schema migration is planned in the mainline slice (235-baseline-capture-truth)
|
||||
- PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, existing governance domain models and builders, existing Evidence Snapshot and Tenant Review infrastructure (236-canonical-control-catalog-foundation)
|
||||
- PostgreSQL for existing downstream governance artifacts plus a product-seeded in-repo canonical control registry; no new DB-backed control authoring table in the first slice (236-canonical-control-catalog-foundation)
|
||||
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4 + existing provider seams under `App\Services\Providers` and `App\Services\Graph`, especially `ProviderGateway`, `ProviderIdentityResolver`, `ProviderIdentityResolution`, `PlatformProviderIdentityResolver`, `ProviderConnectionResolver`, `ProviderConnectionResolution`, `MicrosoftGraphOptionsResolver`, `ProviderOperationRegistry`, `ProviderOperationStartGate`, `GraphClientInterface`, Pest v4 (237-provider-boundary-hardening)
|
||||
- Existing PostgreSQL tables such as `provider_connections` and `operation_runs`; one new in-repo config catalog for provider-boundary ownership; no new database tables (237-provider-boundary-hardening)
|
||||
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4 + `ProviderConnectionResource`, `ManagedTenantOnboardingWizard`, `ProviderConnection`, `ProviderConnectionResolver`, `ProviderConnectionResolution`, `ProviderConnectionMutationService`, `ProviderConnectionStateProjector`, `ProviderIdentityResolver`, `ProviderIdentityResolution`, `PlatformProviderIdentityResolver`, `BadgeRenderer`, Pest v4 (238-provider-identity-target-scope)
|
||||
- Existing PostgreSQL tables such as `provider_connections`, `provider_credentials`, and existing audit tables; no new database tables planned (238-provider-identity-target-scope)
|
||||
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4 + existing `App\Support\OperationCatalog`, `App\Support\OperationRunType`, `App\Services\OperationRunService`, `App\Services\Providers\ProviderOperationRegistry`, `App\Services\Providers\ProviderOperationStartGate`, `App\Filament\Resources\OperationRunResource`, `App\Filament\Pages\Workspaces\ManagedTenantOnboardingWizard`, `App\Support\Filament\FilterOptionCatalog`, `App\Support\OpsUx\OperationUxPresenter`, `App\Support\References\Resolvers\OperationRunReferenceResolver`, `App\Services\Audit\AuditEventBuilder`, Pest v4 (239-canonical-operation-type-source-of-truth)
|
||||
- PostgreSQL via existing `operation_runs.type` and `managed_tenant_onboarding_sessions.state->bootstrap_operation_types`, plus config-backed `tenantpilot.operations.lifecycle.covered_types` and `tenantpilot.platform_vocabulary`; no new tables (239-canonical-operation-type-source-of-truth)
|
||||
- PHP 8.4 (Laravel 12) + Laravel 12 + Filament v5 + Livewire v4 + Pest; existing onboarding services (`OnboardingLifecycleService`, `OnboardingDraftStageResolver`), provider connection summary, verification assist, and Ops-UX helpers (240-tenant-onboarding-readiness)
|
||||
- PostgreSQL via existing `managed_tenant_onboarding_sessions`, `provider_connections`, `operation_runs`, and stored permission-posture data; no new persistence planned (240-tenant-onboarding-readiness)
|
||||
- PHP 8.4 (Laravel 12) + Laravel 12 + Filament v5 + Livewire v4 + Pest; existing `OperationRunLinks`, `GovernanceRunDiagnosticSummaryBuilder`, `ProviderReasonTranslator`, `RelatedNavigationResolver`, `RedactionIntegrity`, `WorkspaceAuditLogger` (241-support-diagnostic-pack)
|
||||
- PostgreSQL via existing `operation_runs`, `provider_connections`, `findings`, `stored_reports`, `tenant_reviews`, `review_packs`, and `audit_logs`; no new persistence planned (241-support-diagnostic-pack)
|
||||
- PHP 8.4, Laravel 12 + Filament v5, Livewire v4, Pest v4, existing review/evidence/review-pack/audit/RBAC support services (249-customer-review-workspace)
|
||||
- PostgreSQL via existing `tenant_reviews`, `review_packs`, `evidence_snapshots`, findings / finding-exception truth, workspace memberships, and `audit_logs`; no new persistence planned (249-customer-review-workspace)
|
||||
- PHP 8.4 (Laravel 12) + Filament v5 + Livewire v4, existing workspace settings stack (`SettingsRegistry`, `SettingsResolver`, `SettingsWriter`), `WorkspaceEntitlementResolver`, `ReviewPackService`, system directory detail page (251-commercial-entitlements-billing-state)
|
||||
- PostgreSQL via existing `workspace_settings` rows plus existing audit log records; no new table or billing/account model (251-commercial-entitlements-billing-state)
|
||||
- PHP 8.4 (Laravel 12) + Laravel 12 + Filament v5 + Livewire v4 + Pest; existing `UiEnforcement`, `OperationUxPresenter`, `OperationRunService`, `OperationCatalog`, `SystemOperationRunLinks`, `OperationRunLinks`, `AuditRecorder`, `WorkspaceAuditLogger`, and `PlatformCapabilities` (253-remove-findings-backfill-runtime-surfaces)
|
||||
- PostgreSQL existing `findings`, `operation_runs`, `audit_logs`, and related runtime tables only; no new persistence, migration, or data backfill is planned (253-remove-findings-backfill-runtime-surfaces)
|
||||
|
||||
- PHP 8.4.15 (feat/005-bulk-operations)
|
||||
|
||||
@ -300,20 +207,8 @@ ## Code Style
|
||||
PHP 8.4.15: Follow standard conventions
|
||||
|
||||
## Recent Changes
|
||||
- 253-remove-findings-backfill-runtime-surfaces: Added PHP 8.4 (Laravel 12) + Laravel 12 + Filament v5 + Livewire v4 + Pest; existing `UiEnforcement`, `OperationUxPresenter`, `OperationRunService`, `OperationCatalog`, `SystemOperationRunLinks`, `OperationRunLinks`, `AuditRecorder`, `WorkspaceAuditLogger`, and `PlatformCapabilities`
|
||||
- 251-commercial-entitlements-billing-state: Added PHP 8.4 (Laravel 12) + Filament v5 + Livewire v4, existing workspace settings stack (`SettingsRegistry`, `SettingsResolver`, `SettingsWriter`), `WorkspaceEntitlementResolver`, `ReviewPackService`, system directory detail page
|
||||
- 249-customer-review-workspace: Added PHP 8.4, Laravel 12 + Filament v5, Livewire v4, Pest v4, existing review/evidence/review-pack/audit/RBAC support services
|
||||
- 193-monitoring-action-hierarchy: Added PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `OperateHubShell`, `CanonicalNavigationContext`, `CanonicalAdminTenantFilterState`, `UiEnforcement`, `ActionSurfaceValidator`, and Filament page or resource action builders
|
||||
- 192-record-header-discipline: Added PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `UiEnforcement`, `RelatedNavigationResolver`, `ActionSurfaceValidator`, and page-local Filament action builders
|
||||
- 191-baseline-compare-operator-mode: Added PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `BaselineCompareMatrixBuilder`, `BadgeCatalog`, `CanonicalNavigationContext`, and `UiEnforcement` patterns
|
||||
<!-- MANUAL ADDITIONS START -->
|
||||
|
||||
### Pre-production compatibility check
|
||||
|
||||
Before adding aliases, fallback readers, dual-write logic, migration shims, or legacy fixtures, verify all of the following:
|
||||
|
||||
1. Do live production data exist?
|
||||
2. Is shared staging migration-relevant?
|
||||
3. Does an external contract depend on the old shape?
|
||||
4. Does the spec explicitly require compatibility behavior?
|
||||
|
||||
If all answers are no, replace the old shape and remove the compatibility path.
|
||||
|
||||
<!-- MANUAL ADDITIONS END -->
|
||||
|
||||
51
.github/agents/speckit.git.commit.agent.md
vendored
51
.github/agents/speckit.git.commit.agent.md
vendored
@ -1,51 +0,0 @@
|
||||
---
|
||||
description: Auto-commit changes after a Spec Kit command completes
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: git -->
|
||||
<!-- Config: .specify/extensions/git/ -->
|
||||
# Auto-Commit Changes
|
||||
|
||||
Automatically stage and commit all changes after a Spec Kit command completes.
|
||||
|
||||
## Behavior
|
||||
|
||||
This command is invoked as a hook after (or before) core commands. It:
|
||||
|
||||
1. Determines the event name from the hook context (e.g., if invoked as an `after_specify` hook, the event is `after_specify`; if `before_plan`, the event is `before_plan`)
|
||||
2. Checks `.specify/extensions/git/git-config.yml` for the `auto_commit` section
|
||||
3. Looks up the specific event key to see if auto-commit is enabled
|
||||
4. Falls back to `auto_commit.default` if no event-specific key exists
|
||||
5. Uses the per-command `message` if configured, otherwise a default message
|
||||
6. If enabled and there are uncommitted changes, runs `git add .` + `git commit`
|
||||
|
||||
## Execution
|
||||
|
||||
Determine the event name from the hook that triggered this command, then run the script:
|
||||
|
||||
- **Bash**: `.specify/extensions/git/scripts/bash/auto-commit.sh <event_name>`
|
||||
- **PowerShell**: `.specify/extensions/git/scripts/powershell/auto-commit.ps1 <event_name>`
|
||||
|
||||
Replace `<event_name>` with the actual hook event (e.g., `after_specify`, `before_plan`, `after_implement`).
|
||||
|
||||
## Configuration
|
||||
|
||||
In `.specify/extensions/git/git-config.yml`:
|
||||
|
||||
```yaml
|
||||
auto_commit:
|
||||
default: false # Global toggle — set true to enable for all commands
|
||||
after_specify:
|
||||
enabled: true # Override per-command
|
||||
message: "[Spec Kit] Add specification"
|
||||
after_plan:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Add implementation plan"
|
||||
```
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
- If Git is not available or the current directory is not a repository: skips with a warning
|
||||
- If no config file exists: skips (disabled by default)
|
||||
- If no changes to commit: skips with a message
|
||||
70
.github/agents/speckit.git.feature.agent.md
vendored
70
.github/agents/speckit.git.feature.agent.md
vendored
@ -1,70 +0,0 @@
|
||||
---
|
||||
description: Create a feature branch with sequential or timestamp numbering
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: git -->
|
||||
<!-- Config: .specify/extensions/git/ -->
|
||||
# Create Feature Branch
|
||||
|
||||
Create and switch to a new git feature branch for the given specification. This command handles **branch creation only** — the spec directory and files are created by the core `/speckit.specify` workflow.
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Environment Variable Override
|
||||
|
||||
If the user explicitly provided `GIT_BRANCH_NAME` (e.g., via environment variable, argument, or in their request), pass it through to the script by setting the `GIT_BRANCH_NAME` environment variable before invoking the script. When `GIT_BRANCH_NAME` is set:
|
||||
- The script uses the exact value as the branch name, bypassing all prefix/suffix generation
|
||||
- `--short-name`, `--number`, and `--timestamp` flags are ignored
|
||||
- `FEATURE_NUM` is extracted from the name if it starts with a numeric prefix, otherwise set to the full branch name
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Verify Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null`
|
||||
- If Git is not available, warn the user and skip branch creation
|
||||
|
||||
## Branch Numbering Mode
|
||||
|
||||
Determine the branch numbering strategy by checking configuration in this order:
|
||||
|
||||
1. Check `.specify/extensions/git/git-config.yml` for `branch_numbering` value
|
||||
2. Check `.specify/init-options.json` for `branch_numbering` value (backward compatibility)
|
||||
3. Default to `sequential` if neither exists
|
||||
|
||||
## Execution
|
||||
|
||||
Generate a concise short name (2-4 words) for the branch:
|
||||
- Analyze the feature description and extract the most meaningful keywords
|
||||
- Use action-noun format when possible (e.g., "add-user-auth", "fix-payment-bug")
|
||||
- Preserve technical terms and acronyms (OAuth2, API, JWT, etc.)
|
||||
|
||||
Run the appropriate script based on your platform:
|
||||
|
||||
- **Bash**: `.specify/extensions/git/scripts/bash/create-new-feature.sh --json --short-name "<short-name>" "<feature description>"`
|
||||
- **Bash (timestamp)**: `.specify/extensions/git/scripts/bash/create-new-feature.sh --json --timestamp --short-name "<short-name>" "<feature description>"`
|
||||
- **PowerShell**: `.specify/extensions/git/scripts/powershell/create-new-feature.ps1 -Json -ShortName "<short-name>" "<feature description>"`
|
||||
- **PowerShell (timestamp)**: `.specify/extensions/git/scripts/powershell/create-new-feature.ps1 -Json -Timestamp -ShortName "<short-name>" "<feature description>"`
|
||||
|
||||
**IMPORTANT**:
|
||||
- Do NOT pass `--number` — the script determines the correct next number automatically
|
||||
- Always include the JSON flag (`--json` for Bash, `-Json` for PowerShell) so the output can be parsed reliably
|
||||
- You must only ever run this script once per feature
|
||||
- The JSON output will contain `BRANCH_NAME` and `FEATURE_NUM`
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If Git is not installed or the current directory is not a Git repository:
|
||||
- Branch creation is skipped with a warning: `[specify] Warning: Git repository not detected; skipped branch creation`
|
||||
- The script still outputs `BRANCH_NAME` and `FEATURE_NUM` so the caller can reference them
|
||||
|
||||
## Output
|
||||
|
||||
The script outputs JSON with:
|
||||
- `BRANCH_NAME`: The branch name (e.g., `003-user-auth` or `20260319-143022-user-auth`)
|
||||
- `FEATURE_NUM`: The numeric or timestamp prefix used
|
||||
52
.github/agents/speckit.git.initialize.agent.md
vendored
52
.github/agents/speckit.git.initialize.agent.md
vendored
@ -1,52 +0,0 @@
|
||||
---
|
||||
description: Initialize a Git repository with an initial commit
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: git -->
|
||||
<!-- Config: .specify/extensions/git/ -->
|
||||
# Initialize Git Repository
|
||||
|
||||
Initialize a Git repository in the current project directory if one does not already exist.
|
||||
|
||||
## Execution
|
||||
|
||||
Run the appropriate script from the project root:
|
||||
|
||||
- **Bash**: `.specify/extensions/git/scripts/bash/initialize-repo.sh`
|
||||
- **PowerShell**: `.specify/extensions/git/scripts/powershell/initialize-repo.ps1`
|
||||
|
||||
If the extension scripts are not found, fall back to:
|
||||
- **Bash**: `git init && git add . && git commit -m "Initial commit from Specify template"`
|
||||
- **PowerShell**: `git init; git add .; git commit -m "Initial commit from Specify template"`
|
||||
|
||||
The script handles all checks internally:
|
||||
- Skips if Git is not available
|
||||
- Skips if already inside a Git repository
|
||||
- Runs `git init`, `git add .`, and `git commit` with an initial commit message
|
||||
|
||||
## Customization
|
||||
|
||||
Replace the script to add project-specific Git initialization steps:
|
||||
- Custom `.gitignore` templates
|
||||
- Default branch naming (`git config init.defaultBranch`)
|
||||
- Git LFS setup
|
||||
- Git hooks installation
|
||||
- Commit signing configuration
|
||||
- Git Flow initialization
|
||||
|
||||
## Output
|
||||
|
||||
On success:
|
||||
- `✓ Git repository initialized`
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If Git is not installed:
|
||||
- Warn the user
|
||||
- Skip repository initialization
|
||||
- The project continues to function without Git (specs can still be created under `specs/`)
|
||||
|
||||
If Git is installed but `git init`, `git add .`, or `git commit` fails:
|
||||
- Surface the error to the user
|
||||
- Stop this command rather than continuing with a partially initialized repository
|
||||
48
.github/agents/speckit.git.remote.agent.md
vendored
48
.github/agents/speckit.git.remote.agent.md
vendored
@ -1,48 +0,0 @@
|
||||
---
|
||||
description: Detect Git remote URL for GitHub integration
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: git -->
|
||||
<!-- Config: .specify/extensions/git/ -->
|
||||
# Detect Git Remote URL
|
||||
|
||||
Detect the Git remote URL for integration with GitHub services (e.g., issue creation).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Check if Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null`
|
||||
- If Git is not available, output a warning and return empty:
|
||||
```
|
||||
[specify] Warning: Git repository not detected; cannot determine remote URL
|
||||
```
|
||||
|
||||
## Execution
|
||||
|
||||
Run the following command to get the remote URL:
|
||||
|
||||
```bash
|
||||
git config --get remote.origin.url
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
Parse the remote URL and determine:
|
||||
|
||||
1. **Repository owner**: Extract from the URL (e.g., `github` from `https://github.com/github/spec-kit.git`)
|
||||
2. **Repository name**: Extract from the URL (e.g., `spec-kit` from `https://github.com/github/spec-kit.git`)
|
||||
3. **Is GitHub**: Whether the remote points to a GitHub repository
|
||||
|
||||
Supported URL formats:
|
||||
- HTTPS: `https://github.com/<owner>/<repo>.git`
|
||||
- SSH: `git@github.com:<owner>/<repo>.git`
|
||||
|
||||
> [!CAUTION]
|
||||
> ONLY report a GitHub repository if the remote URL actually points to github.com.
|
||||
> Do NOT assume the remote is GitHub if the URL format doesn't match.
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If Git is not installed, the directory is not a Git repository, or no remote is configured:
|
||||
- Return an empty result
|
||||
- Do NOT error — other workflows should continue without Git remote information
|
||||
52
.github/agents/speckit.git.validate.agent.md
vendored
52
.github/agents/speckit.git.validate.agent.md
vendored
@ -1,52 +0,0 @@
|
||||
---
|
||||
description: Validate current branch follows feature branch naming conventions
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: git -->
|
||||
<!-- Config: .specify/extensions/git/ -->
|
||||
# Validate Feature Branch
|
||||
|
||||
Validate that the current Git branch follows the expected feature branch naming conventions.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Check if Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null`
|
||||
- If Git is not available, output a warning and skip validation:
|
||||
```
|
||||
[specify] Warning: Git repository not detected; skipped branch validation
|
||||
```
|
||||
|
||||
## Validation Rules
|
||||
|
||||
Get the current branch name:
|
||||
|
||||
```bash
|
||||
git rev-parse --abbrev-ref HEAD
|
||||
```
|
||||
|
||||
The branch name must match one of these patterns:
|
||||
|
||||
1. **Sequential**: `^[0-9]{3,}-` (e.g., `001-feature-name`, `042-fix-bug`, `1000-big-feature`)
|
||||
2. **Timestamp**: `^[0-9]{8}-[0-9]{6}-` (e.g., `20260319-143022-feature-name`)
|
||||
|
||||
## Execution
|
||||
|
||||
If on a feature branch (matches either pattern):
|
||||
- Output: `✓ On feature branch: <branch-name>`
|
||||
- Check if the corresponding spec directory exists under `specs/`:
|
||||
- For sequential branches, look for `specs/<prefix>-*` where prefix matches the numeric portion
|
||||
- For timestamp branches, look for `specs/<prefix>-*` where prefix matches the `YYYYMMDD-HHMMSS` portion
|
||||
- If spec directory exists: `✓ Spec directory found: <path>`
|
||||
- If spec directory missing: `⚠ No spec directory found for prefix <prefix>`
|
||||
|
||||
If NOT on a feature branch:
|
||||
- Output: `✗ Not on a feature branch. Current branch: <branch-name>`
|
||||
- Output: `Feature branches should be named like: 001-feature-name or 20260319-143022-feature-name`
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If Git is not installed or the directory is not a Git repository:
|
||||
- Check the `SPECIFY_FEATURE` environment variable as a fallback
|
||||
- If set, validate that value against the naming patterns
|
||||
- If not set, skip validation with a warning
|
||||
6
.github/copilot-instructions.md
vendored
6
.github/copilot-instructions.md
vendored
@ -293,7 +293,6 @@ ## Application Structure & Architecture
|
||||
|
||||
## Workspace Commands
|
||||
- Repo-root JavaScript orchestration now uses `corepack pnpm install`, `corepack pnpm dev:platform`, `corepack pnpm dev:website`, `corepack pnpm dev`, `corepack pnpm build:website`, and `corepack pnpm build:platform`.
|
||||
- `corepack pnpm dev:platform` starts the platform Sail stack and the Laravel panel Vite watcher. `corepack pnpm dev` starts that platform watcher plus the website dev server.
|
||||
- `apps/website` is a standalone Astro app, not a second Laravel runtime, so Boost MCP remains platform-only.
|
||||
|
||||
## Frontend Bundling
|
||||
@ -673,8 +672,3 @@ ### Replaced Utilities
|
||||
| decoration-slice | box-decoration-slice |
|
||||
| decoration-clone | box-decoration-clone |
|
||||
</laravel-boost-guidelines>
|
||||
|
||||
<!-- SPECKIT START -->
|
||||
For additional context about technologies to be used, project structure,
|
||||
shell commands, and other important information, read the current plan
|
||||
<!-- SPECKIT END -->
|
||||
|
||||
3
.github/prompts/speckit.git.commit.prompt.md
vendored
3
.github/prompts/speckit.git.commit.prompt.md
vendored
@ -1,3 +0,0 @@
|
||||
---
|
||||
agent: speckit.git.commit
|
||||
---
|
||||
@ -1,3 +0,0 @@
|
||||
---
|
||||
agent: speckit.git.feature
|
||||
---
|
||||
@ -1,3 +0,0 @@
|
||||
---
|
||||
agent: speckit.git.initialize
|
||||
---
|
||||
3
.github/prompts/speckit.git.remote.prompt.md
vendored
3
.github/prompts/speckit.git.remote.prompt.md
vendored
@ -1,3 +0,0 @@
|
||||
---
|
||||
agent: speckit.git.remote
|
||||
---
|
||||
@ -1,3 +0,0 @@
|
||||
---
|
||||
agent: speckit.git.validate
|
||||
---
|
||||
295
.github/skills/browsertest/SKILL.md
vendored
295
.github/skills/browsertest/SKILL.md
vendored
@ -1,295 +0,0 @@
|
||||
---
|
||||
name: browsertest
|
||||
description: Führe einen vollständigen Smoke-Browser-Test im Integrated Browser für das aktuelle Feature aus, inklusive Happy Path, zentraler Regressionen, Kontext-Prüfung und belastbarer Ergebniszusammenfassung.
|
||||
license: MIT
|
||||
metadata:
|
||||
author: GitHub Copilot
|
||||
---
|
||||
|
||||
# Browser Smoke Test
|
||||
|
||||
## What This Skill Does
|
||||
|
||||
Use this skill to validate the current feature end-to-end in the integrated browser.
|
||||
|
||||
This is a focused smoke test, not a full exploratory test session. The goal is to prove that the primary operator flow:
|
||||
|
||||
- loads in the correct auth, workspace, and tenant context
|
||||
- exposes the expected controls and decision points
|
||||
- completes the main happy path without blocking issues
|
||||
- lands in the expected end state or canonical drilldown
|
||||
- does not show obvious regressions such as broken navigation, missing data, or conflicting actions
|
||||
|
||||
The skill should produce a concrete pass or fail result with actionable evidence.
|
||||
|
||||
## When To Apply
|
||||
|
||||
Activate this skill when:
|
||||
|
||||
- the user asks to smoke test the current feature in the browser
|
||||
- a new Filament page, dashboard signal, report, wizard, or detail flow was just added
|
||||
- a UI regression fix needs confirmation in a real browser context
|
||||
- the primary question is whether the feature works from an operator perspective
|
||||
- you need a quick integration-level check without writing a full browser test suite first
|
||||
|
||||
## What Success Looks Like
|
||||
|
||||
A successful smoke test confirms all of the following:
|
||||
|
||||
- the target route opens successfully
|
||||
- the visible context is correct
|
||||
- the main flow is usable
|
||||
- the expected result appears after interaction
|
||||
- the route or drilldown destination is correct
|
||||
- the surface does not obviously violate its intended interaction model
|
||||
|
||||
If the test cannot be completed, the output must clearly state whether the blocker is:
|
||||
|
||||
- authentication
|
||||
- missing data or fixture state
|
||||
- routing
|
||||
- UI interaction failure
|
||||
- server error
|
||||
- an unclear expected behavior contract
|
||||
|
||||
Do not guess. If the route or state is blocked, report the blocker explicitly.
|
||||
|
||||
## Preconditions
|
||||
|
||||
Before running the browser smoke test, make sure you know:
|
||||
|
||||
- the canonical route or entry point for the feature
|
||||
- the primary operator action or happy path
|
||||
- the expected success state
|
||||
- whether the feature depends on a specific tenant, workspace, or seeded record
|
||||
|
||||
When available, use the feature spec, quickstart, tasks, or current browser page as the source of truth.
|
||||
|
||||
## Standard Workflow
|
||||
|
||||
### 1. Define the smoke-test scope
|
||||
|
||||
Identify:
|
||||
|
||||
- the route to open
|
||||
- the primary action to perform
|
||||
- the expected end state
|
||||
- one or two critical regressions that must not break
|
||||
|
||||
The smoke test should stay narrow. Prefer one complete happy path plus one critical boundary over broad exploratory clicking.
|
||||
|
||||
### 2. Establish the browser state
|
||||
|
||||
- Reuse the current browser page if it already matches the target feature.
|
||||
- Otherwise open the canonical route.
|
||||
- Confirm the current auth and scope context before interacting.
|
||||
|
||||
For this repo, that usually means checking whether the page is on:
|
||||
|
||||
- `/admin/...` for workspace-context surfaces
|
||||
- `/admin/t/{tenant}/...` for tenant-context surfaces
|
||||
|
||||
### 3. Inspect before acting
|
||||
|
||||
- Use `read_page` before interacting so you understand the live controls, refs, headings, and route context.
|
||||
- Prefer `read_page` over screenshots for actual interaction planning.
|
||||
- Use screenshots only for visual evidence or when the user asks for them.
|
||||
|
||||
### 4. Execute the primary happy path
|
||||
|
||||
Run the smallest meaningful flow that proves the feature works.
|
||||
|
||||
Typical steps include:
|
||||
|
||||
- open the page
|
||||
- verify heading or key summary text
|
||||
- click the primary CTA or row
|
||||
- fill the minimum required form fields
|
||||
- confirm modal or dialog text when relevant
|
||||
- submit or navigate
|
||||
- verify the expected destination or changed state
|
||||
|
||||
After each meaningful action, re-read the page so the next step is based on current DOM state.
|
||||
|
||||
### 5. Validate the outcome
|
||||
|
||||
Check the exact result that matters for the feature.
|
||||
|
||||
Examples:
|
||||
|
||||
- a new row appears
|
||||
- a status changes
|
||||
- a success message appears
|
||||
- a report filter changes the result set
|
||||
- a row click lands on the canonical detail page
|
||||
- a dashboard signal links to the correct report page
|
||||
|
||||
### 6. Check for obvious regressions
|
||||
|
||||
Even in a smoke test, verify a few core non-negotiables:
|
||||
|
||||
- the page is not blank or half-rendered
|
||||
- the main action is present and usable
|
||||
- the visible context is correct
|
||||
- the drilldown destination is canonical
|
||||
- no obviously duplicated primary actions exist
|
||||
- no stuck modal, spinner, or blocked interaction remains onscreen
|
||||
|
||||
### 7. Capture evidence and summarize clearly
|
||||
|
||||
Your result should state:
|
||||
|
||||
- route tested
|
||||
- context used
|
||||
- steps executed
|
||||
- pass or fail
|
||||
- exact blocker or discrepancy if failed
|
||||
|
||||
Include a screenshot only when it adds value.
|
||||
|
||||
## Tool Usage Guidance
|
||||
|
||||
Use the browser tools in this order by default:
|
||||
|
||||
1. `read_page`
|
||||
2. `click_element`
|
||||
3. `type_in_page`
|
||||
4. `handle_dialog` when needed
|
||||
5. `navigate_page` or `open_browser_page` only when route changes are required
|
||||
6. `run_playwright_code` only if the normal browser tools are insufficient
|
||||
7. `screenshot_page` for evidence, not for primary navigation logic
|
||||
|
||||
## Repo-Specific Guidance For TenantPilot
|
||||
|
||||
### Workspace surfaces
|
||||
|
||||
For `/admin` pages and similar workspace-context surfaces:
|
||||
|
||||
- verify the page is reachable without forcing tenant-route assumptions
|
||||
- confirm any summary signal or CTA lands on the canonical destination
|
||||
- verify calm-state versus attention-state behavior when the feature defines both
|
||||
|
||||
### Tenant surfaces
|
||||
|
||||
For `/admin/t/{tenant}/...` pages:
|
||||
|
||||
- verify the tenant context is explicit and correct
|
||||
- verify drilldowns stay in the intended tenant scope
|
||||
- treat cross-tenant leakage or silent scope changes as failures
|
||||
|
||||
### Filament list or report surfaces
|
||||
|
||||
For Filament tables, reports, or registry-style pages:
|
||||
|
||||
- verify the heading and table shell render
|
||||
- verify fixed filters or summary controls exist when the spec requires them
|
||||
- verify row click or the primary inspect affordance behaves as designed
|
||||
- verify empty-state messaging is specific rather than generic when the feature defines custom behavior
|
||||
|
||||
### Filament detail pages
|
||||
|
||||
For detail or view surfaces:
|
||||
|
||||
- verify the canonical record loads
|
||||
- verify expected sections or summary content are present
|
||||
- verify critical actions or drillbacks are usable
|
||||
|
||||
## Result Format
|
||||
|
||||
Use a compact result format like this:
|
||||
|
||||
```text
|
||||
Browser smoke result: PASS
|
||||
Route: /admin/findings/hygiene
|
||||
Context: workspace member with visible hygiene issues
|
||||
Steps: opened report -> verified filters -> clicked finding row -> landed on canonical finding detail
|
||||
Verified: report rendered, primary interaction worked, drilldown route was correct
|
||||
```
|
||||
|
||||
If the test fails:
|
||||
|
||||
```text
|
||||
Browser smoke result: FAIL
|
||||
Route: /admin/findings/hygiene
|
||||
Context: authenticated workspace member
|
||||
Failed step: clicking the summary CTA
|
||||
Expected: navigate to /admin/findings/hygiene
|
||||
Actual: remained on /admin with no route change
|
||||
Blocker: CTA appears rendered but is not interactive
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Smoke test a new report page
|
||||
|
||||
Use this when the feature adds a new read-only report.
|
||||
|
||||
Steps:
|
||||
|
||||
- open the canonical report route
|
||||
- verify the page heading and main controls
|
||||
- confirm the table or defined empty state is visible
|
||||
- click one row or primary inspect affordance
|
||||
- verify navigation lands on the canonical detail route
|
||||
|
||||
Pass criteria:
|
||||
|
||||
- report loads
|
||||
- intended controls exist
|
||||
- primary inspect path works
|
||||
|
||||
### Example 2: Smoke test a dashboard signal
|
||||
|
||||
Use this when the feature adds a summary signal on `/admin`.
|
||||
|
||||
Steps:
|
||||
|
||||
- open `/admin`
|
||||
- find the signal
|
||||
- verify the visible count or summary text
|
||||
- click the CTA
|
||||
- confirm navigation lands on the canonical downstream surface
|
||||
|
||||
Pass criteria:
|
||||
|
||||
- signal is visible in the correct state
|
||||
- CTA text is present
|
||||
- CTA opens the correct route
|
||||
|
||||
### Example 3: Smoke test a tenant detail follow-up
|
||||
|
||||
Use this when a workspace-level surface should drill into a tenant-level detail page.
|
||||
|
||||
Steps:
|
||||
|
||||
- open the workspace-level surface
|
||||
- trigger the drilldown
|
||||
- verify the target route includes the correct tenant and record
|
||||
- confirm the target page actually loads the expected detail content
|
||||
|
||||
Pass criteria:
|
||||
|
||||
- drilldown route is canonical
|
||||
- tenant context is correct
|
||||
- destination content matches the selected record
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- Clicking before reading the page state and refs
|
||||
- Treating a blocked auth session as a feature failure
|
||||
- Confusing workspace-context routes with tenant-context routes
|
||||
- Reporting visual impressions without validating the actual interaction result
|
||||
- Forgetting to re-read the page after a modal opens or a route changes
|
||||
- Claiming success without verifying the final destination or changed state
|
||||
|
||||
## Non-Goals
|
||||
|
||||
This skill does not replace:
|
||||
|
||||
- full exploratory QA
|
||||
- formal Pest browser coverage
|
||||
- accessibility review
|
||||
- visual regression approval
|
||||
- backend correctness tests
|
||||
|
||||
It is a fast, real-browser confidence pass for the current feature.
|
||||
8
.github/skills/giteaflow/SKILL.md
vendored
8
.github/skills/giteaflow/SKILL.md
vendored
@ -1,8 +0,0 @@
|
||||
---
|
||||
name: giteaflow
|
||||
description: Describe what this skill does and when to use it. Include keywords that help agents identify relevant tasks.
|
||||
---
|
||||
|
||||
<!-- Tip: Use /create-skill in chat to generate content with agent assistance -->
|
||||
|
||||
comit all changes, push to remote, and create a pull request against dev with gitea mcp
|
||||
625
.github/skills/platform-feature-finish/SKILL.md
vendored
625
.github/skills/platform-feature-finish/SKILL.md
vendored
@ -1,625 +0,0 @@
|
||||
|
||||
|
||||
---
|
||||
name: platform-feature-finish
|
||||
description: Commit, push, create a Gitea PR from a TenantPilot platform feature branch into platform-dev, and optionally refresh the platform-dev to dev integration PR by rebase.
|
||||
---
|
||||
|
||||
# Skill: platform-feature-finish
|
||||
|
||||
## Purpose
|
||||
|
||||
Automate the TenantPilot platform feature completion workflow.
|
||||
|
||||
Trigger this skill when the user says something like:
|
||||
|
||||
- "alles committen pushen und PR gegen platform-dev"
|
||||
- "feature fertig, bitte PR erstellen"
|
||||
- "platform feature abschließen"
|
||||
- "commit push PR mit Gitea MCP"
|
||||
- "mach PR gegen platform-dev"
|
||||
- "finish platform feature"
|
||||
- "platform-dev nach dev vorbereiten"
|
||||
- "platform-dev PR aktualisieren"
|
||||
- "out-of-date mit dev beheben"
|
||||
- "integration PR refresh"
|
||||
- "platform-dev auf dev rebasen"
|
||||
|
||||
This skill handles:
|
||||
|
||||
1. Validate current Git branch
|
||||
2. Commit all feature changes
|
||||
3. Push current feature branch
|
||||
4. Create a Gitea pull request into `platform-dev`
|
||||
5. Refresh the `platform-dev` → `dev` integration PR when explicitly requested
|
||||
6. Report the PR link and next integration step
|
||||
|
||||
---
|
||||
|
||||
## Branch Model
|
||||
|
||||
TenantPilot uses area branches:
|
||||
|
||||
```text
|
||||
dev = shared integration branch
|
||||
platform-dev = platform/application area integration branch
|
||||
website-dev = website/marketing area integration branch
|
||||
```
|
||||
|
||||
For platform features:
|
||||
|
||||
```text
|
||||
platform-dev
|
||||
↓
|
||||
feature branch
|
||||
↓
|
||||
PR back to platform-dev
|
||||
↓
|
||||
platform-dev → dev integration PR
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Platform feature branches MUST target `platform-dev`.
|
||||
- Do NOT target `dev` directly unless the user explicitly asks.
|
||||
- Do NOT use `website-dev` for platform features.
|
||||
- `platform-dev` is the default PR base for TenantPilot platform/application work.
|
||||
- `dev` is the shared integration branch.
|
||||
|
||||
### Solo Workflow Rule
|
||||
|
||||
The user works alone on `platform-dev`.
|
||||
|
||||
For refreshing the integration branch before opening or updating the PR `platform-dev` → `dev`, prefer rebase over merge.
|
||||
|
||||
Do not repeatedly merge `origin/dev` into `platform-dev` for refresh.
|
||||
|
||||
Avoid creating repeated merge commits like:
|
||||
|
||||
```text
|
||||
Merge remote-tracking branch 'origin/dev' into platform-dev
|
||||
```
|
||||
|
||||
Use `--force-with-lease`, never plain `--force`.
|
||||
|
||||
If rebase conflicts occur, stop and report the conflict files.
|
||||
|
||||
---
|
||||
|
||||
## Preconditions
|
||||
|
||||
Before committing:
|
||||
|
||||
1. Confirm repository root.
|
||||
2. Confirm current branch is not protected.
|
||||
|
||||
Protected branches:
|
||||
|
||||
```text
|
||||
dev
|
||||
platform-dev
|
||||
website-dev
|
||||
main
|
||||
master
|
||||
```
|
||||
|
||||
If the current branch is protected, STOP and report:
|
||||
|
||||
```text
|
||||
Ich bin auf einem geschützten Branch. Bitte zuerst einen Feature-Branch auschecken.
|
||||
```
|
||||
|
||||
3. Confirm remote exists.
|
||||
4. Confirm there are local changes, untracked files, or unpushed commits.
|
||||
5. Confirm there are no unresolved conflicts.
|
||||
|
||||
Do not ask for confirmation unless:
|
||||
|
||||
- The current branch is protected.
|
||||
- Git status indicates unresolved conflicts.
|
||||
- There is no remote configured.
|
||||
- `.env` or other local secret/config files would be committed.
|
||||
- Commit fails.
|
||||
- Push fails.
|
||||
- Gitea MCP PR creation fails.
|
||||
|
||||
---
|
||||
|
||||
## Required Tools
|
||||
|
||||
Use terminal for Git operations.
|
||||
|
||||
Use Gitea MCP for pull request creation.
|
||||
|
||||
Preferred Gitea MCP operation:
|
||||
|
||||
```text
|
||||
create_pull_request
|
||||
```
|
||||
|
||||
Required PR parameters:
|
||||
|
||||
```json
|
||||
{
|
||||
"owner": "ahmido",
|
||||
"repo": "TenantAtlas",
|
||||
"head": "<current-feature-branch>",
|
||||
"base": "platform-dev",
|
||||
"title": "<generated-title>",
|
||||
"body": "<generated-body>"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1 — Inspect Git state
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git rev-parse --show-toplevel
|
||||
git rev-parse --abbrev-ref HEAD
|
||||
git status --porcelain
|
||||
git status -sb
|
||||
git config --get remote.origin.url
|
||||
git log --oneline --max-count=5
|
||||
```
|
||||
|
||||
Determine:
|
||||
|
||||
- repository root
|
||||
- current branch
|
||||
- changed files
|
||||
- untracked files
|
||||
- remote URL
|
||||
- whether there are unpushed commits
|
||||
- whether unresolved conflicts exist
|
||||
|
||||
If the current branch is protected, stop.
|
||||
|
||||
If unresolved conflicts exist, stop.
|
||||
|
||||
If no remote exists, stop.
|
||||
|
||||
---
|
||||
|
||||
### Step 2 — Check for local environment files
|
||||
|
||||
Before `git add -A`, check whether local environment/config files are modified or untracked:
|
||||
|
||||
```bash
|
||||
git status --porcelain | grep -E '(^.. \.env$|^.. apps/platform/\.env$|^.. .*\.env$)' || true
|
||||
```
|
||||
|
||||
If `.env` or another environment file is included, STOP and report:
|
||||
|
||||
```text
|
||||
Achtung: Eine .env-/Environment-Datei ist geändert oder untracked. Ich committe das nicht automatisch. Bitte prüfen oder aus dem Commit entfernen.
|
||||
```
|
||||
|
||||
Do not commit secrets or local runtime configuration.
|
||||
|
||||
---
|
||||
|
||||
### Step 3 — Build commit message
|
||||
|
||||
Use the current branch name.
|
||||
|
||||
If branch starts with a spec number, for example:
|
||||
|
||||
```text
|
||||
256-external-support-desk-handoff
|
||||
```
|
||||
|
||||
Generate:
|
||||
|
||||
```text
|
||||
feat(specs/256): external support desk handoff
|
||||
```
|
||||
|
||||
If branch does not contain a spec number, generate:
|
||||
|
||||
```text
|
||||
feat(platform): complete <branch-name>
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Use lowercase subject.
|
||||
- Use feature-style subject.
|
||||
- Do not include `WIP`.
|
||||
- Do not include `final`.
|
||||
- Do not include overly generic `updates`.
|
||||
|
||||
Examples:
|
||||
|
||||
```text
|
||||
feat(specs/256): external support desk handoff
|
||||
feat(specs/252): platform localization v1
|
||||
feat(platform): improve tenant review workspace
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 4 — Commit all changes
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "<commit-message>"
|
||||
```
|
||||
|
||||
If there are no local changes to commit, continue only if the branch has unpushed commits.
|
||||
|
||||
Check unpushed commits with:
|
||||
|
||||
```bash
|
||||
git status -sb
|
||||
git log --oneline origin/<current-branch>..HEAD
|
||||
```
|
||||
|
||||
If there are no local changes and no unpushed commits, report:
|
||||
|
||||
```text
|
||||
Es gibt keine lokalen Änderungen und keine unpushed commits. Ich erstelle keinen leeren Commit.
|
||||
```
|
||||
|
||||
Then continue to PR creation only if the branch already exists remotely or can be pushed.
|
||||
|
||||
---
|
||||
|
||||
### Step 5 — Push branch
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git push --set-upstream origin <current-branch>
|
||||
```
|
||||
|
||||
If the upstream already exists, this is acceptable.
|
||||
|
||||
Never force-push unless the user explicitly requests it.
|
||||
|
||||
---
|
||||
|
||||
### Step 6 — Create PR into platform-dev via Gitea MCP
|
||||
|
||||
Use Gitea MCP to create a pull request:
|
||||
|
||||
```json
|
||||
{
|
||||
"owner": "ahmido",
|
||||
"repo": "TenantAtlas",
|
||||
"head": "<current-feature-branch>",
|
||||
"base": "platform-dev",
|
||||
"title": "<commit-message>",
|
||||
"body": "Implements platform feature branch `<current-feature-branch>`.\n\nTarget branch: `platform-dev`.\n\nFollow-up integration path after merge:\n\n`platform-dev` → `dev`."
|
||||
}
|
||||
```
|
||||
|
||||
If a PR already exists for the same branch and base, do not create a duplicate.
|
||||
|
||||
Report the existing PR if available.
|
||||
|
||||
---
|
||||
|
||||
## Optional Step — Check platform-dev to dev PR
|
||||
|
||||
After creating the feature PR, check whether an open integration PR exists:
|
||||
|
||||
```text
|
||||
platform-dev → dev
|
||||
```
|
||||
|
||||
If a Gitea MCP list/search pull request function is available, use it.
|
||||
|
||||
If one exists, report:
|
||||
|
||||
```text
|
||||
Der Folge-PR `platform-dev` → `dev` existiert bereits: <url>
|
||||
```
|
||||
|
||||
If none exists, report:
|
||||
|
||||
```text
|
||||
Nach dem Merge dieses Feature-PRs sollte der Integrations-PR `platform-dev` → `dev` erstellt oder aktualisiert werden.
|
||||
```
|
||||
|
||||
Do not automatically create the `platform-dev` → `dev` PR unless the user explicitly asks for it.
|
||||
|
||||
Reason: before the feature PR is merged into `platform-dev`, the integration PR may not include the new feature yet.
|
||||
|
||||
---
|
||||
|
||||
## Integration Refresh Mode
|
||||
|
||||
Use this mode when the user explicitly says one of the following:
|
||||
|
||||
- "platform-dev nach dev vorbereiten"
|
||||
- "platform-dev PR aktualisieren"
|
||||
- "out-of-date mit dev beheben"
|
||||
- "integration PR refresh"
|
||||
- "platform-dev auf dev rebasen"
|
||||
- "auch platform-dev nach dev"
|
||||
- "und danach platform-dev nach dev"
|
||||
- "full integration"
|
||||
- "kompletten platform-dev zu dev PR machen"
|
||||
- "folge-pr erstellen"
|
||||
|
||||
This mode prepares or updates the integration PR:
|
||||
|
||||
```text
|
||||
platform-dev → dev
|
||||
```
|
||||
|
||||
Because the user works alone on `platform-dev`, prefer rebase over merge.
|
||||
|
||||
### Integration Refresh Preconditions
|
||||
|
||||
Before running this mode:
|
||||
|
||||
1. Ensure the working tree is clean.
|
||||
2. Ensure there are no unresolved conflicts.
|
||||
3. Fetch remote branches.
|
||||
4. Ensure `origin/platform-dev` exists.
|
||||
5. Ensure `origin/dev` exists.
|
||||
|
||||
If the working tree is dirty, STOP and report:
|
||||
|
||||
```text
|
||||
Der Working Tree ist nicht sauber. Bitte erst Änderungen committen, stashen oder verwerfen, bevor `platform-dev` auf `dev` rebased wird.
|
||||
```
|
||||
|
||||
If unresolved conflicts exist, STOP and report the conflict files.
|
||||
|
||||
### Integration Refresh Workflow
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
git checkout platform-dev
|
||||
git reset --hard origin/platform-dev
|
||||
git rebase origin/dev
|
||||
git push --force-with-lease origin platform-dev
|
||||
```
|
||||
|
||||
After pushing, verify that `origin/dev` is now an ancestor of `origin/platform-dev`:
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
git merge-base --is-ancestor origin/dev origin/platform-dev \
|
||||
&& echo "OK: platform-dev contains dev" \
|
||||
|| echo "OUTDATED: platform-dev does not contain dev"
|
||||
```
|
||||
|
||||
If the verification prints `OUTDATED`, stop and report it. Do not claim the PR is up-to-date.
|
||||
|
||||
Rules:
|
||||
|
||||
- Do not merge `origin/dev` into `platform-dev` for this refresh.
|
||||
- Do not create repeated merge commits from `origin/dev` into `platform-dev`.
|
||||
- Use `git push --force-with-lease origin platform-dev` after a successful rebase.
|
||||
- Never use plain `git push --force`.
|
||||
- If `git rebase origin/dev` reports conflicts, stop immediately.
|
||||
- Do not continue to PR creation while a rebase is unresolved.
|
||||
- Do not auto-merge the PR.
|
||||
- Do not claim Gitea will remove the out-of-date warning unless the ancestor check succeeds.
|
||||
|
||||
If rebase conflicts occur, report:
|
||||
|
||||
```text
|
||||
Rebase-Konflikte erkannt. Ich habe gestoppt.
|
||||
|
||||
Konfliktdateien:
|
||||
<files>
|
||||
|
||||
Bitte Konflikte lösen, dann `git rebase --continue` ausführen oder den Rebase mit `git rebase --abort` abbrechen.
|
||||
```
|
||||
|
||||
### Create or Report Integration PR
|
||||
|
||||
After the rebase, push, and ancestor verification succeeded, use Gitea MCP to create or report the integration PR:
|
||||
|
||||
```json
|
||||
{
|
||||
"owner": "ahmido",
|
||||
"repo": "TenantAtlas",
|
||||
"head": "platform-dev",
|
||||
"base": "dev",
|
||||
"title": "chore(platform): merge platform-dev into dev",
|
||||
"body": "Integrates latest TenantPilot platform changes from `platform-dev` into `dev`.\n\nThis PR was created by agent on user request; do not merge automatically."
|
||||
}
|
||||
```
|
||||
|
||||
If an open PR already exists for `platform-dev` → `dev`, do not create a duplicate. Report the existing PR.
|
||||
|
||||
### Integration Refresh Reporting Format
|
||||
|
||||
Final response for this mode must include:
|
||||
|
||||
```text
|
||||
Fertig.
|
||||
|
||||
- Branch aktualisiert: platform-dev
|
||||
- Refresh-Methode: rebase auf origin/dev
|
||||
- Ancestor-Check: origin/dev ist Ancestor von origin/platform-dev
|
||||
- Push: --force-with-lease origin/platform-dev
|
||||
- Integration PR: <url>
|
||||
- Base: dev
|
||||
- Hinweis: PR wurde nicht automatisch gemerged.
|
||||
```
|
||||
|
||||
Do not claim tests passed unless they were actually executed.
|
||||
|
||||
---
|
||||
|
||||
## Reporting Format
|
||||
|
||||
Final response must be concise and include:
|
||||
|
||||
```text
|
||||
Fertig.
|
||||
|
||||
- Branch: <branch>
|
||||
- Commit: <commit-sha or "keine neuen Änderungen">
|
||||
- Push: origin/<branch>
|
||||
- PR: <url>
|
||||
- Base: platform-dev
|
||||
- Nächster Schritt: Nach Merge `platform-dev` → `dev` PR aktualisieren/erstellen
|
||||
```
|
||||
|
||||
If tests were not run, say:
|
||||
|
||||
```text
|
||||
Tests wurden in diesem Skill nicht automatisch ausgeführt.
|
||||
```
|
||||
|
||||
Do not claim tests passed unless the tool actually ran them.
|
||||
|
||||
---
|
||||
|
||||
## Safety Rules
|
||||
|
||||
- Never commit directly to `dev`, `platform-dev`, `website-dev`, `main`, or `master`.
|
||||
- Never force-push unless explicitly requested.
|
||||
- For Integration Refresh Mode only, `git push --force-with-lease origin platform-dev` is allowed because the user works alone on `platform-dev`; never use plain `--force`.
|
||||
- Never auto-merge PRs unless explicitly requested.
|
||||
- Never target `dev` directly for platform feature PRs unless explicitly requested.
|
||||
- Never delete branches unless explicitly requested.
|
||||
- Never claim tests were run unless the tool actually ran them.
|
||||
- Never commit `.env`, secrets, local tokens, local mock-server configuration, or temporary runtime-only changes.
|
||||
- If migrations were created, mention that the target environment needs migration execution after deployment.
|
||||
- If unresolved conflicts exist, stop.
|
||||
|
||||
---
|
||||
|
||||
## Useful Commands
|
||||
|
||||
Inspect:
|
||||
|
||||
```bash
|
||||
git rev-parse --show-toplevel
|
||||
git rev-parse --abbrev-ref HEAD
|
||||
git status --porcelain
|
||||
git status -sb
|
||||
git config --get remote.origin.url
|
||||
```
|
||||
|
||||
Detect protected branch:
|
||||
|
||||
```bash
|
||||
branch="$(git rev-parse --abbrev-ref HEAD)"
|
||||
case "$branch" in
|
||||
dev|platform-dev|website-dev|main|master)
|
||||
echo "PROTECTED_BRANCH:$branch"
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
```
|
||||
|
||||
Detect unresolved conflicts:
|
||||
|
||||
```bash
|
||||
git diff --name-only --diff-filter=U
|
||||
```
|
||||
|
||||
Detect `.env` changes:
|
||||
|
||||
```bash
|
||||
git status --porcelain | grep -E '(^.. \.env$|^.. apps/platform/\.env$|^.. .*\.env$)' || true
|
||||
```
|
||||
|
||||
Commit:
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "<message>"
|
||||
```
|
||||
|
||||
Push:
|
||||
|
||||
```bash
|
||||
git push --set-upstream origin "$(git rev-parse --abbrev-ref HEAD)"
|
||||
```
|
||||
|
||||
Latest commit:
|
||||
|
||||
```bash
|
||||
git rev-parse --short HEAD
|
||||
git log -1 --pretty=%s
|
||||
```
|
||||
|
||||
Integration refresh:
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
git checkout platform-dev
|
||||
git reset --hard origin/platform-dev
|
||||
git rebase origin/dev
|
||||
git push --force-with-lease origin platform-dev
|
||||
```
|
||||
|
||||
Verify integration refresh:
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
git merge-base --is-ancestor origin/dev origin/platform-dev \
|
||||
&& echo "OK: platform-dev contains dev" \
|
||||
|| echo "OUTDATED: platform-dev does not contain dev"
|
||||
```
|
||||
|
||||
Check rebase conflicts:
|
||||
|
||||
```bash
|
||||
git diff --name-only --diff-filter=U
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example User Request
|
||||
|
||||
User:
|
||||
|
||||
```text
|
||||
alles committen pushen und pr gegen platform-dev mit gitea mcp
|
||||
```
|
||||
|
||||
Assistant should:
|
||||
|
||||
1. Check current branch.
|
||||
2. Stop if branch is protected.
|
||||
3. Stop if `.env` or secrets would be committed.
|
||||
4. Commit all changes.
|
||||
5. Push current branch.
|
||||
6. Create PR into `platform-dev` with Gitea MCP.
|
||||
7. Report result.
|
||||
|
||||
Do not ask unnecessary follow-up questions.
|
||||
|
||||
---
|
||||
|
||||
## Example Integration Refresh Request
|
||||
|
||||
User:
|
||||
|
||||
```text
|
||||
platform-dev PR aktualisieren
|
||||
```
|
||||
|
||||
Assistant should:
|
||||
|
||||
1. Ensure the working tree is clean.
|
||||
2. Fetch origin.
|
||||
3. Checkout `platform-dev`.
|
||||
4. Reset local `platform-dev` to `origin/platform-dev`.
|
||||
5. Rebase `platform-dev` onto `origin/dev`.
|
||||
6. Push with `--force-with-lease`.
|
||||
7. Verify `origin/dev` is an ancestor of `origin/platform-dev`.
|
||||
8. Create or report the PR `platform-dev` → `dev`.
|
||||
9. Report result.
|
||||
|
||||
Do not merge the PR automatically.
|
||||
447
.github/skills/spec-kit-implementation-loop/SKILL.md
vendored
447
.github/skills/spec-kit-implementation-loop/SKILL.md
vendored
@ -1,447 +0,0 @@
|
||||
---
|
||||
name: spec-kit-implementation-loop
|
||||
description: Implement an existing TenantPilot/TenantAtlas Spec Kit feature, run tests, browser smoke checks where applicable, post-implementation analysis, fix all confirmed in-scope findings when safe and bounded, and repeat until no in-scope findings remain or a stop condition is reached.
|
||||
---
|
||||
|
||||
# Skill: Spec Kit Implementation Loop
|
||||
|
||||
## Purpose
|
||||
|
||||
Use this skill to implement an already prepared TenantPilot/TenantAtlas Spec Kit feature and verify it with a bounded implementation loop.
|
||||
|
||||
This skill assumes `spec.md`, `plan.md`, and `tasks.md` already exist and have passed preparation readiness or have been explicitly accepted by the user.
|
||||
|
||||
The intended workflow is:
|
||||
|
||||
```text
|
||||
active or explicitly named spec
|
||||
→ inspect repo truth, constitution, spec, plan, tasks, and relevant code/tests
|
||||
→ evaluate implementation gates
|
||||
→ implement strictly task-by-task
|
||||
→ run relevant tests/checks
|
||||
→ run browser smoke test when UI/user-facing flows are affected
|
||||
→ run strict post-implementation analysis
|
||||
→ fix confirmed in-scope findings
|
||||
→ repeat test + browser smoke + analysis + fix loop until clean or bounded stop condition is reached
|
||||
→ final implementation report
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
Use this skill when the user asks to:
|
||||
|
||||
- implement an active or explicitly named Spec Kit feature
|
||||
- run Spec Kit implement
|
||||
- analyze after implementation
|
||||
- fix implementation findings
|
||||
- repeat implementation verification until no confirmed in-scope findings remain
|
||||
- run tests and browser smoke checks after implementation
|
||||
|
||||
Typical user prompts:
|
||||
|
||||
```text
|
||||
Implementiere die aktive Spec und analysiere danach, ob alles passt.
|
||||
```
|
||||
|
||||
```text
|
||||
Implementiere specs/243-product-usage-adoption-telemetry streng nach tasks.md.
|
||||
```
|
||||
|
||||
```text
|
||||
Mach Spec Kit implement und danach analyse. Behebe alle Abweichungen und wiederhole bis sauber.
|
||||
```
|
||||
|
||||
```text
|
||||
Implementiere die vorbereitete Spec. Danach Tests, Browser Smoke Test falls UI betroffen ist, Analyse und Fix-Loop bis keine In-Scope Findings mehr offen sind.
|
||||
```
|
||||
|
||||
## Hard Rules
|
||||
|
||||
- Work strictly repo-based.
|
||||
- Implement only the active or explicitly named Spec Kit feature.
|
||||
- Do not choose a new candidate.
|
||||
- Do not create a new spec.
|
||||
- Do not expand scope beyond `spec.md`, `plan.md`, and `tasks.md`.
|
||||
- Do not silently add roadmap features, adjacent UX rewrites, speculative architecture, or unrelated refactors.
|
||||
- Follow the repository constitution and existing Spec Kit conventions.
|
||||
- Preserve TenantPilot/TenantAtlas terminology.
|
||||
- Prefer small, reviewable patches over broad rewrites.
|
||||
- Treat repository truth as authoritative over assumptions.
|
||||
- If repository truth conflicts with implementation scope, stop and report the conflict unless there is an obvious minimal correction inside active spec scope.
|
||||
- Fix only confirmed findings from tests, static checks, browser smoke checks, or post-implementation analysis.
|
||||
- Fix all confirmed in-scope findings, regardless of severity, when they are safe and bounded.
|
||||
- Do not leave Medium/Low findings open silently. If they are not fixed, document exactly why.
|
||||
- Never hide failing tests, weaken assertions, delete meaningful coverage, or mark tasks complete without implementation evidence.
|
||||
- Do not run destructive commands.
|
||||
- Do not force checkout, reset, stash, rebase, merge, or delete branches.
|
||||
- Do not perform database-destructive actions unless the repository test workflow explicitly requires isolated test database resets.
|
||||
- Do not continue analysis/fix loops indefinitely.
|
||||
- Do not move from implementation to final status unless the Test Gate, Browser Smoke Test Gate where applicable, and Post-Implementation Analysis Gate have been evaluated.
|
||||
- Do not claim merge-readiness unless the Merge Readiness Gate passes.
|
||||
|
||||
## Required Inputs
|
||||
|
||||
The user should provide at least one of:
|
||||
|
||||
- explicit spec directory such as `specs/<number>-<slug>/`
|
||||
- instruction to use the current active Spec Kit feature
|
||||
- instruction to implement the prepared/current spec
|
||||
|
||||
If the active spec cannot be determined safely, inspect the repository Spec Kit context first. If it is still ambiguous, stop and ask for the specific spec directory.
|
||||
|
||||
## Required Repository Checks
|
||||
|
||||
Always check:
|
||||
|
||||
1. active Spec Kit context / current branch
|
||||
2. git status
|
||||
3. `.specify/memory/constitution.md`
|
||||
4. the active spec directory
|
||||
5. `spec.md`
|
||||
6. `plan.md`
|
||||
7. `tasks.md`
|
||||
8. relevant templates or conventions under `.specify/templates/`
|
||||
9. nearby existing specs with related terminology or scope
|
||||
10. application code surfaces referenced by the active spec
|
||||
11. existing tests related to the changed behavior
|
||||
|
||||
## Git and Branch Safety
|
||||
|
||||
Before making implementation changes:
|
||||
|
||||
1. Check the current branch.
|
||||
2. Check whether the working tree is clean.
|
||||
3. If there are unrelated uncommitted changes, stop and report them. Do not continue.
|
||||
4. If the working tree only contains user-intended changes for this operation, continue cautiously.
|
||||
5. Do not force checkout, reset, stash, rebase, merge, or delete branches.
|
||||
6. Do not overwrite unrelated work.
|
||||
|
||||
## Quality Gates
|
||||
|
||||
### Gate 1: Spec Readiness Gate
|
||||
|
||||
Required before implementation starts.
|
||||
|
||||
Pass criteria:
|
||||
|
||||
- `spec.md`, `plan.md`, and `tasks.md` exist.
|
||||
- The spec has clear problem statement, user value, functional requirements, out-of-scope boundaries, acceptance criteria, assumptions, and risks.
|
||||
- The plan identifies likely affected repo surfaces and does not contradict repository architecture.
|
||||
- The tasks are small, ordered, verifiable, and include test/validation tasks.
|
||||
- RBAC, workspace/tenant isolation, auditability, OperationRun semantics, evidence/result-truth, and UX requirements are addressed where relevant.
|
||||
- No open question blocks safe implementation.
|
||||
- The scope is small enough for a bounded implementation loop.
|
||||
|
||||
Fail behavior:
|
||||
|
||||
- Stop before implementation.
|
||||
- Report readiness gaps.
|
||||
- Do not compensate for an unclear spec by inventing implementation scope.
|
||||
|
||||
### Gate 2: Implementation Scope Gate
|
||||
|
||||
Required before changing application code.
|
||||
|
||||
Pass criteria:
|
||||
|
||||
- The active spec directory is known.
|
||||
- The implementation target is traceable to specific tasks in `tasks.md`.
|
||||
- The affected files/surfaces are consistent with `plan.md` or clearly justified by repository truth.
|
||||
- No required change would introduce unrelated product behavior.
|
||||
- No required change conflicts with constitution, existing architecture, RBAC/isolation boundaries, or source-of-truth semantics.
|
||||
|
||||
Fail behavior:
|
||||
|
||||
- Stop before code changes and report the conflict or ambiguity.
|
||||
- Suggest a minimal spec/plan/tasks correction if the issue is in the artifacts rather than the codebase.
|
||||
|
||||
### Gate 3: Test Gate
|
||||
|
||||
Required after implementation and after each fix iteration.
|
||||
|
||||
Pass criteria:
|
||||
|
||||
- Targeted tests for changed behavior pass.
|
||||
- Relevant existing tests pass or failures are proven unrelated and documented.
|
||||
- Static analysis, linting, formatting, or type checks used by the repository pass when applicable.
|
||||
- Security/governance-relevant changes have backend, policy, or domain coverage; UI-only verification is not enough.
|
||||
- Regression coverage exists for each fixed Blocker or High finding where practical.
|
||||
|
||||
Fail behavior:
|
||||
|
||||
- Fix in-scope failures before post-implementation analysis.
|
||||
- If failures are unrelated or pre-existing, document evidence and continue only if they do not invalidate the active spec.
|
||||
- Do not weaken tests to pass the gate.
|
||||
|
||||
### Gate 4: Browser Smoke Test Gate
|
||||
|
||||
Required before claiming implementation is ready for manual review/merge when the change affects Filament UI, Livewire interactions, navigation, forms, tables, actions, modals, dashboards, operation drilldowns, tenant/workspace context, or any user-facing flow.
|
||||
|
||||
Not required for backend-only, domain-only, enum-only, contract-only, or test-only changes unless those changes alter a user-facing flow.
|
||||
|
||||
Pass criteria:
|
||||
|
||||
- The relevant page or flow loads in a real browser or the repository's browser-testing harness.
|
||||
- The primary action introduced or changed by the spec can be executed successfully.
|
||||
- Expected UI states, labels, badges, actions, empty states, tables, forms, modals, and navigation are visible where relevant.
|
||||
- Workspace/tenant context is preserved across the tested flow where relevant.
|
||||
- RBAC/capability-dependent visibility behaves as expected where practical to verify.
|
||||
- Livewire interactions complete without visible runtime errors.
|
||||
- No relevant browser console errors occur.
|
||||
- No failed network requests occur for the tested flow, except known unrelated development noise that is explicitly documented.
|
||||
- OperationRun, audit, evidence, result, or support-diagnostic drilldowns work where relevant.
|
||||
- The smoke-tested path is documented in the final response.
|
||||
|
||||
Fail behavior:
|
||||
|
||||
- Fix in-scope browser, UX, Livewire, navigation, or runtime failures before claiming merge-readiness.
|
||||
- If a browser issue is unrelated existing debt, document evidence and residual risk.
|
||||
- Do not treat a passing browser smoke test as a substitute for backend, policy, domain, security, feature, or integration tests.
|
||||
- Do not expand the smoke test into a full E2E suite unless the user explicitly asks for that.
|
||||
|
||||
### Gate 5: Post-Implementation Analysis Gate
|
||||
|
||||
Required after implementation and after each fix iteration.
|
||||
|
||||
Pass criteria:
|
||||
|
||||
- The implementation has been checked against `spec.md`, `plan.md`, `tasks.md`, and constitution.
|
||||
- All completed tasks have implementation evidence.
|
||||
- No confirmed in-scope findings remain.
|
||||
- Medium/Low findings are fixed when they are inside active spec scope, clearly bounded, and safe.
|
||||
- Medium/Low findings that remain open are explicitly documented with one of these reasons:
|
||||
- out of scope
|
||||
- requires separate spec
|
||||
- risky refactor
|
||||
- existing unrelated debt
|
||||
- not reproducible
|
||||
- blocked by unclear product/architecture decision
|
||||
- No scope expansion was introduced during fixes.
|
||||
|
||||
Fail behavior:
|
||||
|
||||
- Fix confirmed in-scope findings, regardless of severity, when the fix is safe and bounded.
|
||||
- Stop instead of fixing when remediation would expand scope, contradict repo architecture, introduce risky refactors, or repeat the same failed fix twice.
|
||||
|
||||
### Gate 6: Merge Readiness Gate
|
||||
|
||||
Required before claiming the implementation is ready for manual review/merge.
|
||||
|
||||
Pass criteria:
|
||||
|
||||
- Spec Readiness Gate passed.
|
||||
- Implementation Scope Gate passed.
|
||||
- Test Gate passed.
|
||||
- Browser Smoke Test Gate passed when applicable, or was explicitly marked not applicable with a reason.
|
||||
- Post-Implementation Analysis Gate passed.
|
||||
- `tasks.md` reflects actual completion status.
|
||||
- No confirmed in-scope findings remain.
|
||||
- All remaining findings are documented as out-of-scope, follow-up candidates, unrelated existing debt, or explicit residual risks.
|
||||
- Final response includes changed files, tests/checks run, browser smoke result, iterations performed, residual risks, and follow-up candidates.
|
||||
|
||||
Fail behavior:
|
||||
|
||||
- Do not claim merge-readiness.
|
||||
- Report the failed gate, remaining risks, and the smallest recommended next action.
|
||||
|
||||
## Implementation Loop
|
||||
|
||||
Execute the loop in bounded phases:
|
||||
|
||||
1. Evaluate the Spec Readiness Gate.
|
||||
2. Evaluate the Implementation Scope Gate before changing application code.
|
||||
3. Implement the active Spec Kit feature scope task-by-task.
|
||||
4. Run targeted tests and relevant static/dynamic checks.
|
||||
5. Evaluate the Test Gate.
|
||||
6. Run a Browser Smoke Test when the change affects UI/user-facing flows.
|
||||
7. Evaluate the Browser Smoke Test Gate as passed, failed, or not applicable with a reason.
|
||||
8. Run strict post-implementation analysis against spec, plan, tasks, constitution, changed code, changed tests, browser smoke results where applicable, and relevant existing patterns.
|
||||
9. Evaluate the Post-Implementation Analysis Gate.
|
||||
10. Identify confirmed findings by severity: Blocker, High, Medium, Low.
|
||||
11. Fix all confirmed in-scope findings regardless of severity when safe and bounded.
|
||||
12. Do not fix findings that require scope expansion, risky unrelated refactors, or architectural/product decisions outside the active spec; document them as follow-up/residual risks with reasons.
|
||||
13. Re-run relevant tests and browser smoke checks where applicable after fixes.
|
||||
14. Repeat test + browser smoke + analysis + fix loop until no confirmed in-scope findings remain or a stop condition is reached.
|
||||
15. Evaluate the Merge Readiness Gate.
|
||||
16. Report final implementation status, changed files, tests, browser smoke result, residual risks, failed/passed gates, and manual review prompt.
|
||||
|
||||
## Stop Conditions
|
||||
|
||||
Stop the implementation loop when any of the following is true:
|
||||
|
||||
- No confirmed in-scope findings remain.
|
||||
- The same finding appears twice after attempted fixes.
|
||||
- A required fix conflicts with the spec, plan, constitution, or repository architecture.
|
||||
- A required fix would expand scope beyond the active spec.
|
||||
- A required fix would require a risky unrelated refactor.
|
||||
- A required fix depends on an unresolved product or architecture decision.
|
||||
- Tests reveal an unrelated pre-existing failure that cannot be safely fixed inside the active spec.
|
||||
- Browser smoke testing reveals an unrelated pre-existing UI/runtime failure that cannot be safely fixed inside the active spec.
|
||||
- Three analysis/fix iterations have already been completed.
|
||||
- The repository state is ambiguous enough that continuing would risk damaging architecture or data semantics.
|
||||
|
||||
When stopping before full cleanliness, report exactly why the loop stopped and what remains.
|
||||
|
||||
## Post-Implementation Analysis Prompt
|
||||
|
||||
Use this prompt internally after implementation and after each fix iteration:
|
||||
|
||||
```markdown
|
||||
Du bist ein Senior Staff Software Engineer, Software Architect und Enterprise SaaS Reviewer.
|
||||
|
||||
Analysiere die Implementierung der aktiven Spec streng repo-basiert.
|
||||
|
||||
Ziel:
|
||||
Prüfe, ob die Umsetzung vollständig, konsistent, getestet und constitution-konform ist.
|
||||
|
||||
Prüfe gegen:
|
||||
- spec.md
|
||||
- plan.md
|
||||
- tasks.md
|
||||
- .specify/memory/constitution.md
|
||||
- geänderte Anwendungscodes
|
||||
- geänderte Tests
|
||||
- Browser-Smoke-Test-Ergebnis, falls UI/user-facing Flows betroffen sind
|
||||
- bestehende Repository-Patterns
|
||||
|
||||
Wichtig:
|
||||
- Keine Spekulation ohne Repo-Beleg.
|
||||
- Keine Scope-Erweiterung.
|
||||
- Keine neuen Produktideen als Pflicht-Fixes.
|
||||
- Findings nach Blocker, High, Medium, Low gruppieren.
|
||||
- Für jedes Finding konkrete Datei-/Code-Belege nennen.
|
||||
- Für jedes Finding eine minimale Remediation nennen.
|
||||
- Separat ausweisen, welche Findings innerhalb der aktiven Spec behoben werden müssen.
|
||||
- Medium/Low Findings innerhalb der aktiven Spec ebenfalls zur Behebung markieren, wenn sie sicher und bounded sind.
|
||||
- Bei UI-/Filament-/Livewire-Änderungen prüfen, ob ein Browser Smoke Test durchgeführt wurde und ob der getestete Operator-Flow wirklich funktioniert.
|
||||
- Findings, die nicht behoben werden sollen, nur als Follow-up/Residual Risk ausweisen, wenn sie out of scope, risky refactor, unrelated existing debt, not reproducible oder durch eine offene Produkt-/Architekturentscheidung blockiert sind.
|
||||
- Wenn keine bestätigten In-Scope Findings verbleiben, klare Implementierungsfreigabe geben.
|
||||
```
|
||||
|
||||
## Task Completion Rules
|
||||
|
||||
- Keep `tasks.md` aligned with actual implementation status.
|
||||
- Check off tasks only after the implementation and test evidence exists.
|
||||
- If a task is obsolete because repository truth proves a different path, update the task note with the reason instead of silently deleting it.
|
||||
- If a task cannot be completed inside scope, leave it unchecked and report why.
|
||||
|
||||
## Testing Rules
|
||||
|
||||
- Add or update tests for all changed business behavior.
|
||||
- Include RBAC and workspace/tenant isolation tests where relevant.
|
||||
- Include OperationRun, audit, evidence, or result-truth tests where relevant.
|
||||
- Prefer regression tests for every fixed Blocker or High finding.
|
||||
- Add regression tests for Medium/Low findings when the behavior is important and testable without excessive churn.
|
||||
- Do not weaken tests to pass the suite.
|
||||
- Do not treat a green UI path as sufficient without backend or policy coverage when the behavior is security- or governance-relevant.
|
||||
|
||||
## Browser Smoke Test Rules
|
||||
|
||||
Apply these rules when the active spec changes Filament UI, Livewire interactions, navigation, forms, tables, actions, modals, dashboards, operation drilldowns, tenant/workspace context, or any user-facing flow.
|
||||
|
||||
The browser smoke test should be narrow and focused. It is not a full E2E suite unless explicitly requested.
|
||||
|
||||
Minimum smoke path:
|
||||
|
||||
1. Open the relevant page or entry point.
|
||||
2. Confirm the expected workspace/tenant context where relevant.
|
||||
3. Confirm the changed or newly introduced UI element is visible.
|
||||
4. Execute the primary action or interaction changed by the spec.
|
||||
5. Confirm the expected result state, notification, redirect, table update, modal state, operation link, or drilldown.
|
||||
6. Check for relevant console errors.
|
||||
7. Check for failed network requests related to the tested flow.
|
||||
8. Document the tested path in the final response.
|
||||
|
||||
For TenantPilot/TenantAtlas, pay special attention to:
|
||||
|
||||
- Filament actions and header actions
|
||||
- Livewire polling, modals, validation, and actions
|
||||
- workspace/tenant context preservation
|
||||
- RBAC/capability-dependent action visibility
|
||||
- OperationRun links and drilldown continuity
|
||||
- audit/evidence/result/support-diagnostic drilldowns where relevant
|
||||
- empty states, badges, labels, and decision guidance where relevant
|
||||
|
||||
Browser smoke testing is required for UI/user-facing changes and optional for backend-only changes.
|
||||
|
||||
Do not treat browser smoke success as proof that backend security, policies, domain logic, auditability, or workspace/tenant isolation are correct. Those still require automated tests or repo-based verification.
|
||||
|
||||
## Failure Handling
|
||||
|
||||
If an implementation step, test phase, browser smoke phase, or post-implementation analysis fails:
|
||||
|
||||
1. Stop at the relevant gate or stop condition.
|
||||
2. Report the failing command or phase.
|
||||
3. Summarize the error.
|
||||
4. Do not attempt unrelated implementation as a workaround.
|
||||
5. Suggest the smallest safe next action.
|
||||
|
||||
If the branch or working tree state is unsafe:
|
||||
|
||||
1. Stop before implementation changes.
|
||||
2. Report the current branch and relevant uncommitted files.
|
||||
3. Ask the user to commit, stash, or move to a clean worktree.
|
||||
|
||||
## Final Response Requirements
|
||||
|
||||
Respond with:
|
||||
|
||||
1. Active spec directory
|
||||
2. Summary of implemented changes
|
||||
3. Tests/checks run and their results
|
||||
4. Browser smoke test result, tested path, or not-applicable reason
|
||||
5. Quality gates passed/failed and number of analysis/fix iterations performed
|
||||
6. Remaining in-scope findings, if any
|
||||
7. Residual risks and follow-up candidates, if relevant
|
||||
8. Files changed
|
||||
9. Explicit statement whether the Merge Readiness Gate passed and whether the implementation is ready for manual review/merge
|
||||
|
||||
Keep the final response concise, but include enough detail for the user to continue immediately.
|
||||
|
||||
## Manual Review Prompt
|
||||
|
||||
Provide a ready-to-copy prompt like this, adapted to the active spec number and slug:
|
||||
|
||||
```markdown
|
||||
Du bist ein Senior Staff Software Architect und Enterprise SaaS Reviewer.
|
||||
|
||||
Führe eine finale manuelle Review der implementierten Spec `<spec-number>-<slug>` streng repo-basiert durch.
|
||||
|
||||
Ziel:
|
||||
Prüfe, ob die Implementierung nach dem Agenten-Loop wirklich merge-ready ist.
|
||||
|
||||
Wichtig:
|
||||
- Keine Implementierung.
|
||||
- Keine Codeänderungen.
|
||||
- Keine Scope-Erweiterung.
|
||||
- Prüfe gegen spec.md, plan.md, tasks.md und constitution.md.
|
||||
- Prüfe die geänderten Dateien, Tests, Browser-Smoke-Test-Ergebnis, RBAC, Workspace-/Tenant-Isolation, Auditability, UX und OperationRun-Semantik, soweit relevant.
|
||||
- Benenne nur konkrete Findings mit Repo-Beleg.
|
||||
- Gib am Ende eine klare Entscheidung: Merge-ready, merge-ready with notes, oder not merge-ready.
|
||||
```
|
||||
|
||||
## Example Invocation
|
||||
|
||||
User:
|
||||
|
||||
```text
|
||||
Nutze den Skill spec-kit-implementation-loop.
|
||||
Implementiere die aktive Spec.
|
||||
Danach Tests ausführen, Browser Smoke Test falls UI/user-facing betroffen ist, Post-Implementation Analyse durchführen und alle bestätigten In-Scope Findings unabhängig von Severity beheben, wenn safe und bounded.
|
||||
Wiederhole test + browser smoke + analysis + fix bis keine In-Scope Findings mehr offen sind oder eine Stop Condition greift.
|
||||
```
|
||||
|
||||
Expected behavior:
|
||||
|
||||
1. Inspect active Spec Kit context, constitution, spec, plan, tasks, relevant code, and relevant tests.
|
||||
2. Evaluate the Spec Readiness Gate and Implementation Scope Gate.
|
||||
3. Implement only the active spec scope.
|
||||
4. Run targeted tests and relevant checks.
|
||||
5. Evaluate the Test Gate.
|
||||
6. Run and evaluate Browser Smoke Test when UI/user-facing flows are affected.
|
||||
7. Run post-implementation analysis.
|
||||
8. Fix all confirmed in-scope findings regardless of severity when safe and bounded.
|
||||
9. Repeat test + browser smoke + analysis + fix loop up to the stop conditions.
|
||||
10. Evaluate the Merge Readiness Gate.
|
||||
11. Report final status, changed files, tests, browser smoke result, residual risks, gates, and manual review prompt.
|
||||
```
|
||||
562
.github/skills/spec-kit-next-best-prep/SKILL.md
vendored
562
.github/skills/spec-kit-next-best-prep/SKILL.md
vendored
@ -1,562 +0,0 @@
|
||||
---
|
||||
name: spec-kit-next-best-prep
|
||||
description: Select the next suitable TenantPilot/TenantAtlas spec candidate from roadmap/spec-candidates, run the repository's Spec Kit preparation flow, create or update spec.md/plan.md/tasks.md, run preparation analysis, fix preparation-artifact issues only, and stop before application implementation.
|
||||
---
|
||||
|
||||
# Skill: Spec Kit Next-Best Preparation
|
||||
|
||||
## Purpose
|
||||
|
||||
Use this skill to prepare the next implementation-ready Spec Kit package for TenantPilot/TenantAtlas without implementing application code.
|
||||
|
||||
This skill supports preparation only:
|
||||
|
||||
1. Select or scope the next suitable feature from roadmap/spec-candidates.
|
||||
2. Run the repository's real Spec Kit preparation workflow where available.
|
||||
3. Create or update `spec.md`, `plan.md`, and `tasks.md`.
|
||||
4. Run preparation `analyze` when supported.
|
||||
5. Fix preparation-artifact issues only.
|
||||
6. Evaluate preparation quality gates.
|
||||
7. Stop before application implementation.
|
||||
|
||||
The intended workflow is:
|
||||
|
||||
```text
|
||||
roadmap / spec-candidates / feature idea
|
||||
→ inspect repo truth, constitution, roadmap, spec candidates, existing specs, and relevant code
|
||||
→ select the next suitable candidate or scope the provided idea
|
||||
→ run Spec Kit specify/plan/tasks/analyze where available
|
||||
→ create or update spec.md + plan.md + tasks.md
|
||||
→ fix preparation-artifact issues only
|
||||
→ evaluate Candidate Selection Gate and Spec Readiness Gate
|
||||
→ final preparation report
|
||||
→ explicit implementation step later
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
Use this skill when the user asks to:
|
||||
|
||||
- select the next best spec candidate from `docs/product/spec-candidates.md` and roadmap sources
|
||||
- turn a feature idea, roadmap item, or candidate into `spec.md`, `plan.md`, and `tasks.md`
|
||||
- prepare Spec Kit artifacts in one pass
|
||||
- run specify/plan/tasks/analyze without implementation
|
||||
- fix preparation analysis issues in Spec Kit artifacts only
|
||||
- prepare a feature package for a later implementation skill
|
||||
|
||||
Typical user prompts:
|
||||
|
||||
```text
|
||||
Nimm den nächsten sinnvollen Spec Candidate aus Roadmap/spec-candidates und mach spec, plan und tasks.
|
||||
```
|
||||
|
||||
```text
|
||||
Mach daraus spec, plan und tasks in einem Rutsch, aber noch nicht implementieren.
|
||||
```
|
||||
|
||||
```text
|
||||
Wähle aus roadmap.md und spec-candidates.md die nächste sinnvollste Spec und führe specify, plan, tasks und analyze aus.
|
||||
```
|
||||
|
||||
```text
|
||||
Behebe alle analyze-Issues in den Spec-Kit-Artefakten. Keine Application-Implementierung.
|
||||
```
|
||||
|
||||
## Hard Rules
|
||||
|
||||
- Work strictly repo-based.
|
||||
- This is a preparation-only skill.
|
||||
- Do not implement application code.
|
||||
- Do not modify production code.
|
||||
- Do not modify migrations, models, services, jobs, Filament resources, Livewire components, policies, commands, routes, views, tests, or runtime behavior.
|
||||
- Use the repository's actual Spec Kit workflow, scripts, templates, branch naming rules, and generated paths when available.
|
||||
- Do not manually invent spec numbers, branch names, or spec paths if Spec Kit provides a script or command for that.
|
||||
- Do not bypass Spec Kit branch mechanics.
|
||||
- Create or update only Spec Kit preparation artifacts unless repository conventions require additional documentation artifacts.
|
||||
- Do not expand scope beyond the selected feature, `spec.md`, `plan.md`, and `tasks.md`.
|
||||
- Do not silently add roadmap features, adjacent UX rewrites, speculative architecture, or unrelated refactors.
|
||||
- Follow the repository constitution and existing Spec Kit conventions.
|
||||
- Preserve TenantPilot/TenantAtlas terminology.
|
||||
- Prefer small, reviewable, implementation-ready specs over broad rewrites.
|
||||
- Treat repository truth as authoritative over assumptions.
|
||||
- If repository truth conflicts with the user-provided draft or candidate wording, keep repository truth and document the deviation.
|
||||
- Fix only confirmed preparation-artifact findings from Spec Kit preparation analysis.
|
||||
- Do not leave preparation findings open silently. If they are not fixed, document exactly why.
|
||||
- Do not run destructive commands.
|
||||
- Do not force checkout, reset, stash, rebase, merge, or delete branches.
|
||||
- Do not overwrite existing specs.
|
||||
- Do not move from preparation to an implementation step inside this skill.
|
||||
|
||||
## Required Inputs
|
||||
|
||||
The user should provide at least one of:
|
||||
|
||||
- feature title and short goal
|
||||
- full spec candidate
|
||||
- roadmap item
|
||||
- rough problem statement
|
||||
- UX or architecture improvement idea
|
||||
- instruction to choose the next best candidate from roadmap/spec-candidates
|
||||
|
||||
If the input is incomplete, proceed with the smallest reasonable interpretation and document assumptions.
|
||||
|
||||
If no suitable candidate can be selected safely, stop and report why.
|
||||
|
||||
## Required Repository Checks
|
||||
|
||||
Always check:
|
||||
|
||||
1. `.specify/memory/constitution.md`
|
||||
2. `.specify/templates/`
|
||||
3. `.specify/scripts/`
|
||||
4. existing Spec Kit command usage or repository instructions, if present
|
||||
5. current branch and git status
|
||||
6. `specs/`
|
||||
7. `docs/product/spec-candidates.md`
|
||||
8. relevant roadmap documents under `docs/product/`, especially `roadmap.md` if present
|
||||
9. nearby existing specs with related terminology or scope
|
||||
10. application code only as needed to avoid wrong naming, wrong architecture, duplicate concepts, impossible tasks, duplicated specs, or already-completed candidates
|
||||
|
||||
Do not edit application code.
|
||||
|
||||
## Git and Branch Safety
|
||||
|
||||
Before running any Spec Kit command:
|
||||
|
||||
1. Check the current branch.
|
||||
2. Check whether the working tree is clean.
|
||||
3. If there are unrelated uncommitted changes, stop and report them. Do not continue.
|
||||
4. If the working tree only contains user-intended planning edits for this operation, continue cautiously.
|
||||
5. Let Spec Kit create or switch to the correct feature branch when that is how the repository workflow works.
|
||||
6. Do not force checkout, reset, stash, rebase, merge, or delete branches.
|
||||
7. Do not overwrite existing specs.
|
||||
|
||||
If the repo requires an explicit branch creation script for `specify`, use that script rather than manually creating the branch.
|
||||
|
||||
## Quality Gates
|
||||
|
||||
### Gate 1: Candidate Selection Gate
|
||||
|
||||
Required before creating a new spec from roadmap/spec-candidates.
|
||||
|
||||
Pass criteria:
|
||||
|
||||
- The selected candidate exists in roadmap/spec-candidate material or is directly provided by the user.
|
||||
- The selected candidate is not already covered by an existing active or completed spec.
|
||||
- The selected candidate aligns with current roadmap priorities or explicitly documented product direction.
|
||||
- The candidate can be scoped as a small, reviewable, implementation-ready slice.
|
||||
- Major adjacent concerns are listed as follow-up candidates instead of being hidden inside the primary scope.
|
||||
|
||||
Fail behavior:
|
||||
|
||||
- If no candidate satisfies the gate, stop and report the top candidates plus the reason none is ready.
|
||||
- Do not invent a new roadmap direction to force progress.
|
||||
|
||||
### Gate 2: Spec Readiness Gate
|
||||
|
||||
Required before reporting that the package is ready for implementation.
|
||||
|
||||
Pass criteria:
|
||||
|
||||
- `spec.md`, `plan.md`, and `tasks.md` exist.
|
||||
- The spec has clear problem statement, user value, functional requirements, out-of-scope boundaries, acceptance criteria, assumptions, and risks.
|
||||
- The plan identifies likely affected repo surfaces and does not contradict repository architecture.
|
||||
- The tasks are small, ordered, verifiable, and include test/validation tasks.
|
||||
- RBAC, workspace/tenant isolation, auditability, OperationRun semantics, evidence/result-truth, and UX requirements are addressed where relevant.
|
||||
- No open question blocks safe implementation.
|
||||
- The scope is small enough for a bounded implementation loop in a later implementation skill.
|
||||
- Required checklist artifacts exist when the constitution requires them.
|
||||
|
||||
Fail behavior:
|
||||
|
||||
- Fix preparation-artifact issues when they are safe and bounded.
|
||||
- If readiness cannot be achieved without implementation or unresolved product decisions, stop and report the gap.
|
||||
- Do not compensate for an unclear spec by inventing implementation scope.
|
||||
|
||||
## Candidate Selection Rules
|
||||
|
||||
When the user asks for the next best spec from roadmap/spec-candidates:
|
||||
|
||||
- Read `docs/product/spec-candidates.md`.
|
||||
- Read relevant roadmap documents under `docs/product/`, especially `roadmap.md` if present.
|
||||
- Check existing specs to avoid duplicates.
|
||||
- Prefer candidates that align with current roadmap priorities, platform foundations, enterprise UX, RBAC/isolation, auditability, observability, and governance workflow maturity.
|
||||
- Prefer candidates that unlock roadmap progress, reduce architectural drift, harden foundations, or remove known blockers.
|
||||
- Prefer small, implementation-ready slices over broad platform rewrites.
|
||||
- If multiple candidates are plausible, choose one primary candidate and document why it was selected.
|
||||
- Add non-selected relevant candidates as follow-up spec candidates, not hidden scope.
|
||||
- Do not invent a candidate if existing roadmap/spec-candidate material provides a suitable one.
|
||||
- Do not pick a spec only because it is listed first.
|
||||
- Evaluate the Candidate Selection Gate before creating the spec directory.
|
||||
|
||||
Evaluate candidates using these criteria:
|
||||
|
||||
1. **Roadmap Fit**: Does it support the current roadmap sequence or unlock the next roadmap layer?
|
||||
2. **Foundation Value**: Does it strengthen reusable platform foundations such as RBAC, isolation, auditability, evidence, OperationRun observability, provider boundaries, vocabulary, baseline/control/finding semantics, or enterprise UX patterns?
|
||||
3. **Dependency Unblocking**: Does it make future specs smaller, safer, or more consistent?
|
||||
4. **Scope Size**: Can it be implemented as a narrow, testable slice?
|
||||
5. **Repo Readiness**: Does the repo already have enough structure to implement the next slice safely?
|
||||
6. **Risk Reduction**: Does it reduce current architectural or product risk?
|
||||
7. **User/Product Value**: Does it produce visible operator value or make the platform more sellable without heavy scope?
|
||||
|
||||
## Required Selection Output Before Spec Kit Execution
|
||||
|
||||
Before running the Spec Kit flow, identify:
|
||||
|
||||
- selected candidate title
|
||||
- source location in roadmap/spec-candidates
|
||||
- why it was selected
|
||||
- why close alternatives were deferred
|
||||
- roadmap relationship
|
||||
- smallest viable implementation slice
|
||||
- proposed concise feature description to feed into `specify`
|
||||
|
||||
The feature description must be product- and behavior-oriented. It should not be a low-level implementation plan.
|
||||
|
||||
## Spec Kit Preparation Flow
|
||||
|
||||
### Step 1: Determine the repository's Spec Kit command pattern
|
||||
|
||||
Inspect repository instructions and scripts to identify how this repo expects Spec Kit to be run.
|
||||
|
||||
Common locations to inspect:
|
||||
|
||||
```text
|
||||
.specify/scripts/
|
||||
.specify/templates/
|
||||
.specify/memory/constitution.md
|
||||
.github/prompts/
|
||||
.github/skills/
|
||||
README.md
|
||||
specs/
|
||||
```
|
||||
|
||||
Use the repo-specific mechanism if present.
|
||||
|
||||
### Step 2: Run `specify`
|
||||
|
||||
Run the repository's `specify` flow using the selected candidate and the smallest viable slice.
|
||||
|
||||
The `specify` input should include:
|
||||
|
||||
- selected candidate title
|
||||
- problem statement
|
||||
- operator/user value
|
||||
- roadmap relationship
|
||||
- out-of-scope boundaries
|
||||
- key acceptance criteria
|
||||
- important enterprise constraints
|
||||
|
||||
Let Spec Kit create the correct branch and spec location if that is the repo's configured behavior.
|
||||
|
||||
### Step 3: Run `plan`
|
||||
|
||||
Run the repository's `plan` flow for the generated spec.
|
||||
|
||||
The `plan` input should keep the scope tight and should require repo-based alignment with:
|
||||
|
||||
- constitution
|
||||
- existing architecture
|
||||
- workspace/tenant isolation
|
||||
- RBAC
|
||||
- OperationRun/observability where relevant
|
||||
- evidence/snapshot/truth semantics where relevant
|
||||
- Filament/Livewire conventions where relevant
|
||||
- test strategy
|
||||
|
||||
### Step 4: Run `tasks`
|
||||
|
||||
Run the repository's `tasks` flow for the generated plan.
|
||||
|
||||
The generated tasks must be:
|
||||
|
||||
- ordered
|
||||
- small
|
||||
- testable
|
||||
- grouped by phase
|
||||
- limited to the selected scope
|
||||
- suitable for later implementation or manual analysis before implementation
|
||||
|
||||
### Step 5: Run preparation `analyze`
|
||||
|
||||
Run the repository's `analyze` flow against the generated Spec Kit artifacts when the repository supports it.
|
||||
|
||||
Analyze must check:
|
||||
|
||||
- consistency between `spec.md`, `plan.md`, and `tasks.md`
|
||||
- constitution alignment
|
||||
- roadmap alignment
|
||||
- whether the selected candidate was narrowed safely
|
||||
- whether tasks are complete enough for implementation
|
||||
- whether tasks accidentally require scope not described in the spec
|
||||
- whether plan details conflict with repository architecture or terminology
|
||||
- whether implementation risks are documented instead of silently ignored
|
||||
|
||||
Do not use analyze as a trigger to implement application code.
|
||||
|
||||
### Step 6: Fix preparation-artifact issues only
|
||||
|
||||
If preparation analyze finds issues, fix only Spec Kit preparation artifacts such as:
|
||||
|
||||
- `spec.md`
|
||||
- `plan.md`
|
||||
- `tasks.md`
|
||||
- `checklists/requirements.md` or other generated Spec Kit metadata files, if the repository uses them
|
||||
|
||||
Allowed fixes include:
|
||||
|
||||
- clarify requirements
|
||||
- tighten scope
|
||||
- move out-of-scope work into follow-up candidates
|
||||
- correct terminology
|
||||
- add missing tasks
|
||||
- remove tasks not backed by the spec
|
||||
- align plan language with repository architecture
|
||||
- add missing acceptance criteria or validation tasks
|
||||
- add missing checklist artifacts required by the constitution
|
||||
|
||||
Forbidden fixes include:
|
||||
|
||||
- modifying application code
|
||||
- creating migrations
|
||||
- editing models, services, jobs, policies, Filament resources, Livewire components, tests, commands, routes, or views
|
||||
- running implementation or test-fix loops
|
||||
- changing runtime behavior
|
||||
|
||||
### Step 7: Evaluate the Spec Readiness Gate
|
||||
|
||||
After preparation analyze has passed or preparation-artifact issues have been fixed, evaluate the Spec Readiness Gate.
|
||||
|
||||
Stop after this gate and do not implement.
|
||||
|
||||
## Spec Directory Rules
|
||||
|
||||
When creating a new spec directory, use the repository's Spec Kit-generated directory or path.
|
||||
|
||||
If the repository does not provide a command for spec setup, use the next valid spec number and a kebab-case slug:
|
||||
|
||||
```text
|
||||
specs/<number>-<slug>/
|
||||
```
|
||||
|
||||
The exact number must be derived from the current repository state and existing numbering conventions.
|
||||
|
||||
Create or update preparation artifacts inside the selected spec directory:
|
||||
|
||||
```text
|
||||
specs/<number>-<slug>/spec.md
|
||||
specs/<number>-<slug>/plan.md
|
||||
specs/<number>-<slug>/tasks.md
|
||||
```
|
||||
|
||||
If the repository templates require additional preparation files, create them only when this is consistent with existing Spec Kit conventions.
|
||||
|
||||
## `spec.md` Requirements
|
||||
|
||||
The spec must be product- and behavior-oriented. It should avoid premature implementation detail unless needed for correctness.
|
||||
|
||||
Include:
|
||||
|
||||
- Feature title
|
||||
- Problem statement
|
||||
- Business/product value
|
||||
- Primary users/operators
|
||||
- User stories
|
||||
- Functional requirements
|
||||
- Non-functional requirements
|
||||
- UX requirements
|
||||
- RBAC/security requirements
|
||||
- Auditability/observability requirements
|
||||
- Data/truth-source requirements where relevant
|
||||
- Out of scope
|
||||
- Acceptance criteria
|
||||
- Success criteria
|
||||
- Risks
|
||||
- Assumptions
|
||||
- Open questions
|
||||
|
||||
TenantPilot/TenantAtlas specs should preserve enterprise SaaS principles:
|
||||
|
||||
- workspace/tenant isolation
|
||||
- capability-first RBAC
|
||||
- auditability
|
||||
- operation/result truth separation
|
||||
- source-of-truth clarity
|
||||
- calm enterprise operator UX
|
||||
- progressive disclosure where useful
|
||||
- no false positive calmness
|
||||
|
||||
## `plan.md` Requirements
|
||||
|
||||
The plan must be repo-aware and implementation-oriented, but it must not make code changes by itself.
|
||||
|
||||
Include:
|
||||
|
||||
- Technical approach
|
||||
- Existing repository surfaces likely affected
|
||||
- Domain/model implications
|
||||
- UI/Filament implications
|
||||
- Livewire implications where relevant
|
||||
- OperationRun/monitoring implications where relevant
|
||||
- RBAC/policy implications
|
||||
- Audit/logging/evidence implications where relevant
|
||||
- Data/migration implications where relevant
|
||||
- Test strategy
|
||||
- Rollout considerations
|
||||
- Risk controls
|
||||
- Implementation phases
|
||||
|
||||
The plan should clearly distinguish where relevant:
|
||||
|
||||
- execution truth
|
||||
- artifact truth
|
||||
- backup/snapshot truth
|
||||
- recovery/evidence truth
|
||||
- operator next action
|
||||
|
||||
## `tasks.md` Requirements
|
||||
|
||||
Tasks must be ordered, small, and verifiable.
|
||||
|
||||
Include:
|
||||
|
||||
- checkbox tasks
|
||||
- phase grouping
|
||||
- tests before or alongside implementation tasks where practical
|
||||
- final validation tasks
|
||||
- documentation/update tasks if needed
|
||||
- explicit non-goals where useful
|
||||
|
||||
Avoid vague tasks such as:
|
||||
|
||||
```text
|
||||
Clean up code
|
||||
Refactor UI
|
||||
Improve performance
|
||||
Make it enterprise-ready
|
||||
```
|
||||
|
||||
Prefer concrete tasks such as:
|
||||
|
||||
```text
|
||||
- [ ] Add a feature test covering workspace isolation for <specific behavior>.
|
||||
- [ ] Update <specific Filament page/resource> to display <specific state>.
|
||||
- [ ] Add policy coverage for <specific capability>.
|
||||
```
|
||||
|
||||
If exact file names are not known yet, phrase tasks as repo-verification tasks first rather than inventing file paths.
|
||||
|
||||
## Preparation Scope Control
|
||||
|
||||
If the requested feature implies multiple independent concerns, create one primary spec for the smallest valuable slice and add a `Follow-up spec candidates` section.
|
||||
|
||||
Examples of follow-up candidates:
|
||||
|
||||
- assigned findings
|
||||
- pending approvals
|
||||
- personal work queue
|
||||
- notification delivery settings
|
||||
- evidence pack export hardening
|
||||
- operation monitoring refinements
|
||||
- autonomous governance decision surfaces
|
||||
|
||||
Do not force all follow-up candidates into the primary spec.
|
||||
|
||||
## Failure Handling
|
||||
|
||||
If a Spec Kit command or preparation analyze phase fails:
|
||||
|
||||
1. Stop at the relevant gate.
|
||||
2. Report the failing command or phase.
|
||||
3. Summarize the error.
|
||||
4. Do not attempt implementation as a workaround.
|
||||
5. Suggest the smallest safe next action.
|
||||
|
||||
If the branch or working tree state is unsafe:
|
||||
|
||||
1. Stop before running Spec Kit commands.
|
||||
2. Report the current branch and relevant uncommitted files.
|
||||
3. Ask the user to commit, stash, or move to a clean worktree.
|
||||
|
||||
## Final Response Requirements
|
||||
|
||||
Respond with:
|
||||
|
||||
1. Selected candidate and why it was chosen
|
||||
2. Why close alternatives were deferred
|
||||
3. Current branch after Spec Kit execution, if changed
|
||||
4. Generated spec path
|
||||
5. Files created or updated by Spec Kit
|
||||
6. Preparation analyze result summary
|
||||
7. Preparation-artifact fixes applied after analyze
|
||||
8. Assumptions made
|
||||
9. Open questions, if any
|
||||
10. Candidate Selection Gate result
|
||||
11. Spec Readiness Gate result
|
||||
12. Recommended next implementation prompt
|
||||
13. Explicit statement that no application implementation was performed
|
||||
|
||||
Keep the final response concise, but include enough detail for the user to continue immediately.
|
||||
|
||||
## Manual Review and Next-Step Prompts
|
||||
|
||||
Provide a ready-to-copy manual artifact review prompt like this, adapted to the generated spec branch/path:
|
||||
|
||||
```markdown
|
||||
Du bist ein Senior Staff Software Architect und Enterprise SaaS Reviewer.
|
||||
|
||||
Analysiere die neu erstellte Spec `<spec-branch-or-spec-path>` streng repo-basiert.
|
||||
|
||||
Ziel:
|
||||
Prüfe, ob `spec.md`, `plan.md` und `tasks.md` vollständig, konsistent, implementierbar und constitution-konform sind.
|
||||
|
||||
Wichtig:
|
||||
- Keine Implementierung.
|
||||
- Keine Codeänderungen.
|
||||
- Keine Scope-Erweiterung.
|
||||
- Prüfe nur gegen Repo-Wahrheit.
|
||||
- Benenne konkrete Konflikte mit Dateien, Patterns, Datenflüssen oder bestehenden Specs.
|
||||
- Schlage nur minimale Korrekturen an `spec.md`, `plan.md` und `tasks.md` vor.
|
||||
- Wenn alles passt, gib eine klare Implementierungsfreigabe.
|
||||
```
|
||||
|
||||
Also provide a ready-to-copy implementation prompt for the separate implementation skill after analyze has passed or preparation-artifact issues have been fixed:
|
||||
|
||||
```markdown
|
||||
/spec-kit-implementation-loop
|
||||
|
||||
Implementiere die vorbereitete Spec `<spec-branch-or-spec-path>` streng anhand von `tasks.md`.
|
||||
|
||||
Danach Tests ausführen, Browser Smoke Test falls UI/user-facing betroffen ist, Post-Implementation Analyse durchführen und alle bestätigten In-Scope Findings unabhängig von Severity beheben, wenn safe und bounded.
|
||||
|
||||
Wiederhole test + browser smoke + analysis + fix bis keine In-Scope Findings mehr offen sind oder eine Stop Condition greift.
|
||||
```
|
||||
|
||||
## Example Invocation
|
||||
|
||||
User:
|
||||
|
||||
```text
|
||||
Nutze den Skill spec-kit-next-best-prep.
|
||||
Wähle aus roadmap.md und spec-candidates.md die nächste sinnvollste Spec.
|
||||
Führe danach GitHub Spec Kit specify, plan, tasks und analyze in einem Rutsch aus.
|
||||
Behebe alle analyze-Issues in den Spec-Kit-Artefakten.
|
||||
Keine Application-Implementierung.
|
||||
```
|
||||
|
||||
Expected behavior:
|
||||
|
||||
1. Inspect constitution, Spec Kit scripts/templates, specs, roadmap, and spec candidates.
|
||||
2. Check branch and working tree safety.
|
||||
3. Compare candidate suitability.
|
||||
4. Select the next best candidate.
|
||||
5. Evaluate the Candidate Selection Gate.
|
||||
6. Run the repository's real Spec Kit `specify` flow, letting it handle branch/spec setup.
|
||||
7. Run the repository's real Spec Kit `plan` flow.
|
||||
8. Run the repository's real Spec Kit `tasks` flow.
|
||||
9. Run the repository's real Spec Kit preparation `analyze` flow.
|
||||
10. Fix analyze issues only in Spec Kit preparation artifacts.
|
||||
11. Evaluate the Spec Readiness Gate.
|
||||
12. Stop before application implementation.
|
||||
13. Return selection rationale, branch/path summary, artifact summary, analyze summary, fixes applied, gates, and next implementation prompt.
|
||||
```
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@ -19,9 +19,6 @@
|
||||
/apps/website/node_modules
|
||||
/.pnpm-store
|
||||
/apps/website/.astro
|
||||
/apps/website/playwright-report
|
||||
/apps/website/test-results
|
||||
/apps/website/blob-report
|
||||
dist/
|
||||
build/
|
||||
coverage/
|
||||
@ -40,10 +37,6 @@ coverage/
|
||||
/apps/platform/storage/framework
|
||||
/storage/logs
|
||||
/apps/platform/storage/logs
|
||||
/apps/platform/storage/logs/*
|
||||
!/apps/platform/storage/logs/test-lanes/
|
||||
/apps/platform/storage/logs/test-lanes/*
|
||||
!/apps/platform/storage/logs/test-lanes/.gitignore
|
||||
/storage/debugbar
|
||||
/apps/platform/storage/debugbar
|
||||
/vendor
|
||||
|
||||
@ -7,9 +7,6 @@ apps/platform/node_modules/
|
||||
apps/website/node_modules/
|
||||
apps/website/.astro/
|
||||
apps/website/dist/
|
||||
apps/website/playwright-report/
|
||||
apps/website/test-results/
|
||||
apps/website/blob-report/
|
||||
vendor/
|
||||
apps/platform/vendor/
|
||||
*.log
|
||||
|
||||
@ -10,47 +10,5 @@ ## Important
|
||||
- `plan.md`
|
||||
- `tasks.md`
|
||||
- `checklists/requirements.md`
|
||||
- Runtime-changing or test-affecting work MUST carry actual test-purpose classification (`Unit`, `Feature`, `Heavy-Governance`, `Browser`), affected lanes, fixture/default cost risks, heavy-family changes, escalation decisions, and minimal validation commands through the active `spec.md`, `plan.md`, and `tasks.md`.
|
||||
- Review-oriented checklists MUST surface nativity, shared-family boundaries, state-layer ownership, exception spread, proof depth, and close-out targeting before merge.
|
||||
- `.specify/` is the operational workflow. `docs/ui/operator-ux-surface-standards.md` remains a rule/reference document, not a second checklist or review process.
|
||||
|
||||
## Author Entry Point
|
||||
|
||||
Use the active feature's `spec.md`, `plan.md`, and `tasks.md` in that order.
|
||||
|
||||
1. Fill the spec's UI / Surface Guardrail Impact section once. If there is no operator-facing surface change, write a concise `N/A` and do not invent extra prose downstream.
|
||||
2. Turn that classification into plan-level handling modes, repository-signal treatment, required proof depth, and the named active feature PR close-out entry.
|
||||
3. Carry the same terms into tasks so implementation, review, definition-of-done, exception documentation, and smoke coverage all point at the same guardrail decision.
|
||||
|
||||
## Review Entry Point
|
||||
|
||||
Use the active feature's `spec.md`, `plan.md`, and `tasks.md` together with the generated checklist based on `.specify/templates/checklist-template.md`.
|
||||
|
||||
1. Confirm the spec names the guardrail impact once: native/custom classification, shared-family relevance, state-layer ownership, exception need, and any low-impact `N/A` path.
|
||||
2. Confirm the plan turns that into handling modes, repository-signal treatment, required test or smoke depth, and the named active feature PR close-out entry.
|
||||
3. Apply the checklist and end with both one review outcome class (`blocker`, `strong-warning`, `documentation-required-exception`, or `acceptable-special-case`) and one workflow outcome (`keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split`).
|
||||
4. If a guarded surface or exception remains in scope, ensure the active feature PR close-out entry records the final note rather than leaving the decision in scattered review comments.
|
||||
|
||||
## Low-Impact Rule
|
||||
|
||||
- Docs-only or template-only work may answer `N/A` or `none`.
|
||||
- Do not force fake surface, lane, or exception prose when no operator-facing change or runtime impact exists.
|
||||
- Use the low-impact path to stay fast, not to hide a real guarded surface change.
|
||||
|
||||
## Escalation Rule
|
||||
|
||||
- Use `blocker` when fake-native drift, hidden host drift, unresolved state ownership, or missing exception boundaries remain.
|
||||
- Use `strong-warning` when the change can proceed only after the active workflow records the guardrail risk explicitly.
|
||||
- Use `documentation-required-exception` when default rules are intentionally relaxed and the bounded exception record is the remaining requirement.
|
||||
- Use `acceptable-special-case` only when the change already fits the declared guardrail contract.
|
||||
- Use `document-in-feature` for contained cost or drift that belongs in the active feature.
|
||||
- Use `follow-up-spec` only for recurring pain or structural lane or family changes.
|
||||
- Use `reject-or-split` when hidden test cost or wrong-lane scope is still unresolved.
|
||||
|
||||
## Close-Out Rule
|
||||
|
||||
- The named close-out target for guarded work is the active feature PR close-out entry `Guardrail / Exception / Smoke Coverage`.
|
||||
- That entry records the low-impact or representative scenario used, the outcome class, handling mode, workflow outcome, required tests or manual smoke, any exception boundary, duplicate-prompt notes, and any deferred automation.
|
||||
- If the change is genuinely low-impact, record a concise `N/A` note rather than fabricating guardrail spread.
|
||||
|
||||
The files `.specify/spec.md`, `.specify/plan.md`, `.specify/tasks.md` may exist as legacy references only.
|
||||
|
||||
@ -1,148 +0,0 @@
|
||||
installed: []
|
||||
settings:
|
||||
auto_execute_hooks: true
|
||||
hooks:
|
||||
before_constitution:
|
||||
- extension: git
|
||||
command: speckit.git.initialize
|
||||
enabled: true
|
||||
optional: false
|
||||
prompt: Execute speckit.git.initialize?
|
||||
description: Initialize Git repository before constitution setup
|
||||
condition: null
|
||||
before_specify:
|
||||
- extension: git
|
||||
command: speckit.git.feature
|
||||
enabled: true
|
||||
optional: false
|
||||
prompt: Execute speckit.git.feature?
|
||||
description: Create feature branch before specification
|
||||
condition: null
|
||||
before_clarify:
|
||||
- extension: git
|
||||
command: speckit.git.commit
|
||||
enabled: true
|
||||
optional: true
|
||||
prompt: Commit outstanding changes before clarification?
|
||||
description: Auto-commit before spec clarification
|
||||
condition: null
|
||||
before_plan:
|
||||
- extension: git
|
||||
command: speckit.git.commit
|
||||
enabled: true
|
||||
optional: true
|
||||
prompt: Commit outstanding changes before planning?
|
||||
description: Auto-commit before implementation planning
|
||||
condition: null
|
||||
before_tasks:
|
||||
- extension: git
|
||||
command: speckit.git.commit
|
||||
enabled: true
|
||||
optional: true
|
||||
prompt: Commit outstanding changes before task generation?
|
||||
description: Auto-commit before task generation
|
||||
condition: null
|
||||
before_implement:
|
||||
- extension: git
|
||||
command: speckit.git.commit
|
||||
enabled: true
|
||||
optional: true
|
||||
prompt: Commit outstanding changes before implementation?
|
||||
description: Auto-commit before implementation
|
||||
condition: null
|
||||
before_checklist:
|
||||
- extension: git
|
||||
command: speckit.git.commit
|
||||
enabled: true
|
||||
optional: true
|
||||
prompt: Commit outstanding changes before checklist?
|
||||
description: Auto-commit before checklist generation
|
||||
condition: null
|
||||
before_analyze:
|
||||
- extension: git
|
||||
command: speckit.git.commit
|
||||
enabled: true
|
||||
optional: true
|
||||
prompt: Commit outstanding changes before analysis?
|
||||
description: Auto-commit before analysis
|
||||
condition: null
|
||||
before_taskstoissues:
|
||||
- extension: git
|
||||
command: speckit.git.commit
|
||||
enabled: true
|
||||
optional: true
|
||||
prompt: Commit outstanding changes before issue sync?
|
||||
description: Auto-commit before tasks-to-issues conversion
|
||||
condition: null
|
||||
after_constitution:
|
||||
- extension: git
|
||||
command: speckit.git.commit
|
||||
enabled: true
|
||||
optional: true
|
||||
prompt: Commit constitution changes?
|
||||
description: Auto-commit after constitution update
|
||||
condition: null
|
||||
after_specify:
|
||||
- extension: git
|
||||
command: speckit.git.commit
|
||||
enabled: true
|
||||
optional: true
|
||||
prompt: Commit specification changes?
|
||||
description: Auto-commit after specification
|
||||
condition: null
|
||||
after_clarify:
|
||||
- extension: git
|
||||
command: speckit.git.commit
|
||||
enabled: true
|
||||
optional: true
|
||||
prompt: Commit clarification changes?
|
||||
description: Auto-commit after spec clarification
|
||||
condition: null
|
||||
after_plan:
|
||||
- extension: git
|
||||
command: speckit.git.commit
|
||||
enabled: true
|
||||
optional: true
|
||||
prompt: Commit plan changes?
|
||||
description: Auto-commit after implementation planning
|
||||
condition: null
|
||||
after_tasks:
|
||||
- extension: git
|
||||
command: speckit.git.commit
|
||||
enabled: true
|
||||
optional: true
|
||||
prompt: Commit task changes?
|
||||
description: Auto-commit after task generation
|
||||
condition: null
|
||||
after_implement:
|
||||
- extension: git
|
||||
command: speckit.git.commit
|
||||
enabled: true
|
||||
optional: true
|
||||
prompt: Commit implementation changes?
|
||||
description: Auto-commit after implementation
|
||||
condition: null
|
||||
after_checklist:
|
||||
- extension: git
|
||||
command: speckit.git.commit
|
||||
enabled: true
|
||||
optional: true
|
||||
prompt: Commit checklist changes?
|
||||
description: Auto-commit after checklist generation
|
||||
condition: null
|
||||
after_analyze:
|
||||
- extension: git
|
||||
command: speckit.git.commit
|
||||
enabled: true
|
||||
optional: true
|
||||
prompt: Commit analysis results?
|
||||
description: Auto-commit after analysis
|
||||
condition: null
|
||||
after_taskstoissues:
|
||||
- extension: git
|
||||
command: speckit.git.commit
|
||||
enabled: true
|
||||
optional: true
|
||||
prompt: Commit after syncing issues?
|
||||
description: Auto-commit after tasks-to-issues conversion
|
||||
condition: null
|
||||
@ -1,44 +0,0 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"extensions": {
|
||||
"git": {
|
||||
"version": "1.0.0",
|
||||
"source": "local",
|
||||
"manifest_hash": "sha256:9731aa8143a72fbebfdb440f155038ab42642517c2b2bdbbf67c8fdbe076ed79",
|
||||
"enabled": true,
|
||||
"priority": 10,
|
||||
"registered_commands": {
|
||||
"agy": [
|
||||
"speckit.git.feature",
|
||||
"speckit.git.validate",
|
||||
"speckit.git.remote",
|
||||
"speckit.git.initialize",
|
||||
"speckit.git.commit"
|
||||
],
|
||||
"codex": [
|
||||
"speckit.git.feature",
|
||||
"speckit.git.validate",
|
||||
"speckit.git.remote",
|
||||
"speckit.git.initialize",
|
||||
"speckit.git.commit"
|
||||
],
|
||||
"copilot": [
|
||||
"speckit.git.feature",
|
||||
"speckit.git.validate",
|
||||
"speckit.git.remote",
|
||||
"speckit.git.initialize",
|
||||
"speckit.git.commit"
|
||||
],
|
||||
"gemini": [
|
||||
"speckit.git.feature",
|
||||
"speckit.git.validate",
|
||||
"speckit.git.remote",
|
||||
"speckit.git.initialize",
|
||||
"speckit.git.commit"
|
||||
]
|
||||
},
|
||||
"registered_skills": [],
|
||||
"installed_at": "2026-04-22T21:58:03.029565+00:00"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,100 +0,0 @@
|
||||
# Git Branching Workflow Extension
|
||||
|
||||
Git repository initialization, feature branch creation, numbering (sequential/timestamp), validation, remote detection, and auto-commit for Spec Kit.
|
||||
|
||||
## Overview
|
||||
|
||||
This extension provides Git operations as an optional, self-contained module. It manages:
|
||||
|
||||
- **Repository initialization** with configurable commit messages
|
||||
- **Feature branch creation** with sequential (`001-feature-name`) or timestamp (`20260319-143022-feature-name`) numbering
|
||||
- **Branch validation** to ensure branches follow naming conventions
|
||||
- **Git remote detection** for GitHub integration (e.g., issue creation)
|
||||
- **Auto-commit** after core commands (configurable per-command with custom messages)
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `speckit.git.initialize` | Initialize a Git repository with a configurable commit message |
|
||||
| `speckit.git.feature` | Create a feature branch with sequential or timestamp numbering |
|
||||
| `speckit.git.validate` | Validate current branch follows feature branch naming conventions |
|
||||
| `speckit.git.remote` | Detect Git remote URL for GitHub integration |
|
||||
| `speckit.git.commit` | Auto-commit changes (configurable per-command enable/disable and messages) |
|
||||
|
||||
## Hooks
|
||||
|
||||
| Event | Command | Optional | Description |
|
||||
|-------|---------|----------|-------------|
|
||||
| `before_constitution` | `speckit.git.initialize` | No | Init git repo before constitution |
|
||||
| `before_specify` | `speckit.git.feature` | No | Create feature branch before specification |
|
||||
| `before_clarify` | `speckit.git.commit` | Yes | Commit outstanding changes before clarification |
|
||||
| `before_plan` | `speckit.git.commit` | Yes | Commit outstanding changes before planning |
|
||||
| `before_tasks` | `speckit.git.commit` | Yes | Commit outstanding changes before task generation |
|
||||
| `before_implement` | `speckit.git.commit` | Yes | Commit outstanding changes before implementation |
|
||||
| `before_checklist` | `speckit.git.commit` | Yes | Commit outstanding changes before checklist |
|
||||
| `before_analyze` | `speckit.git.commit` | Yes | Commit outstanding changes before analysis |
|
||||
| `before_taskstoissues` | `speckit.git.commit` | Yes | Commit outstanding changes before issue sync |
|
||||
| `after_constitution` | `speckit.git.commit` | Yes | Auto-commit after constitution update |
|
||||
| `after_specify` | `speckit.git.commit` | Yes | Auto-commit after specification |
|
||||
| `after_clarify` | `speckit.git.commit` | Yes | Auto-commit after clarification |
|
||||
| `after_plan` | `speckit.git.commit` | Yes | Auto-commit after planning |
|
||||
| `after_tasks` | `speckit.git.commit` | Yes | Auto-commit after task generation |
|
||||
| `after_implement` | `speckit.git.commit` | Yes | Auto-commit after implementation |
|
||||
| `after_checklist` | `speckit.git.commit` | Yes | Auto-commit after checklist |
|
||||
| `after_analyze` | `speckit.git.commit` | Yes | Auto-commit after analysis |
|
||||
| `after_taskstoissues` | `speckit.git.commit` | Yes | Auto-commit after issue sync |
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration is stored in `.specify/extensions/git/git-config.yml`:
|
||||
|
||||
```yaml
|
||||
# Branch numbering strategy: "sequential" or "timestamp"
|
||||
branch_numbering: sequential
|
||||
|
||||
# Custom commit message for git init
|
||||
init_commit_message: "[Spec Kit] Initial commit"
|
||||
|
||||
# Auto-commit per command (all disabled by default)
|
||||
# Example: enable auto-commit after specify
|
||||
auto_commit:
|
||||
default: false
|
||||
after_specify:
|
||||
enabled: true
|
||||
message: "[Spec Kit] Add specification"
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Install the bundled git extension (no network required)
|
||||
specify extension add git
|
||||
```
|
||||
|
||||
## Disabling
|
||||
|
||||
```bash
|
||||
# Disable the git extension (spec creation continues without branching)
|
||||
specify extension disable git
|
||||
|
||||
# Re-enable it
|
||||
specify extension enable git
|
||||
```
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
When Git is not installed or the directory is not a Git repository:
|
||||
- Spec directories are still created under `specs/`
|
||||
- Branch creation is skipped with a warning
|
||||
- Branch validation is skipped with a warning
|
||||
- Remote detection returns empty results
|
||||
|
||||
## Scripts
|
||||
|
||||
The extension bundles cross-platform scripts:
|
||||
|
||||
- `scripts/bash/create-new-feature.sh` — Bash implementation
|
||||
- `scripts/bash/git-common.sh` — Shared Git utilities (Bash)
|
||||
- `scripts/powershell/create-new-feature.ps1` — PowerShell implementation
|
||||
- `scripts/powershell/git-common.ps1` — Shared Git utilities (PowerShell)
|
||||
@ -1,48 +0,0 @@
|
||||
---
|
||||
description: "Auto-commit changes after a Spec Kit command completes"
|
||||
---
|
||||
|
||||
# Auto-Commit Changes
|
||||
|
||||
Automatically stage and commit all changes after a Spec Kit command completes.
|
||||
|
||||
## Behavior
|
||||
|
||||
This command is invoked as a hook after (or before) core commands. It:
|
||||
|
||||
1. Determines the event name from the hook context (e.g., if invoked as an `after_specify` hook, the event is `after_specify`; if `before_plan`, the event is `before_plan`)
|
||||
2. Checks `.specify/extensions/git/git-config.yml` for the `auto_commit` section
|
||||
3. Looks up the specific event key to see if auto-commit is enabled
|
||||
4. Falls back to `auto_commit.default` if no event-specific key exists
|
||||
5. Uses the per-command `message` if configured, otherwise a default message
|
||||
6. If enabled and there are uncommitted changes, runs `git add .` + `git commit`
|
||||
|
||||
## Execution
|
||||
|
||||
Determine the event name from the hook that triggered this command, then run the script:
|
||||
|
||||
- **Bash**: `.specify/extensions/git/scripts/bash/auto-commit.sh <event_name>`
|
||||
- **PowerShell**: `.specify/extensions/git/scripts/powershell/auto-commit.ps1 <event_name>`
|
||||
|
||||
Replace `<event_name>` with the actual hook event (e.g., `after_specify`, `before_plan`, `after_implement`).
|
||||
|
||||
## Configuration
|
||||
|
||||
In `.specify/extensions/git/git-config.yml`:
|
||||
|
||||
```yaml
|
||||
auto_commit:
|
||||
default: false # Global toggle — set true to enable for all commands
|
||||
after_specify:
|
||||
enabled: true # Override per-command
|
||||
message: "[Spec Kit] Add specification"
|
||||
after_plan:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Add implementation plan"
|
||||
```
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
- If Git is not available or the current directory is not a repository: skips with a warning
|
||||
- If no config file exists: skips (disabled by default)
|
||||
- If no changes to commit: skips with a message
|
||||
@ -1,67 +0,0 @@
|
||||
---
|
||||
description: "Create a feature branch with sequential or timestamp numbering"
|
||||
---
|
||||
|
||||
# Create Feature Branch
|
||||
|
||||
Create and switch to a new git feature branch for the given specification. This command handles **branch creation only** — the spec directory and files are created by the core `/speckit.specify` workflow.
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Environment Variable Override
|
||||
|
||||
If the user explicitly provided `GIT_BRANCH_NAME` (e.g., via environment variable, argument, or in their request), pass it through to the script by setting the `GIT_BRANCH_NAME` environment variable before invoking the script. When `GIT_BRANCH_NAME` is set:
|
||||
- The script uses the exact value as the branch name, bypassing all prefix/suffix generation
|
||||
- `--short-name`, `--number`, and `--timestamp` flags are ignored
|
||||
- `FEATURE_NUM` is extracted from the name if it starts with a numeric prefix, otherwise set to the full branch name
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Verify Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null`
|
||||
- If Git is not available, warn the user and skip branch creation
|
||||
|
||||
## Branch Numbering Mode
|
||||
|
||||
Determine the branch numbering strategy by checking configuration in this order:
|
||||
|
||||
1. Check `.specify/extensions/git/git-config.yml` for `branch_numbering` value
|
||||
2. Check `.specify/init-options.json` for `branch_numbering` value (backward compatibility)
|
||||
3. Default to `sequential` if neither exists
|
||||
|
||||
## Execution
|
||||
|
||||
Generate a concise short name (2-4 words) for the branch:
|
||||
- Analyze the feature description and extract the most meaningful keywords
|
||||
- Use action-noun format when possible (e.g., "add-user-auth", "fix-payment-bug")
|
||||
- Preserve technical terms and acronyms (OAuth2, API, JWT, etc.)
|
||||
|
||||
Run the appropriate script based on your platform:
|
||||
|
||||
- **Bash**: `.specify/extensions/git/scripts/bash/create-new-feature.sh --json --short-name "<short-name>" "<feature description>"`
|
||||
- **Bash (timestamp)**: `.specify/extensions/git/scripts/bash/create-new-feature.sh --json --timestamp --short-name "<short-name>" "<feature description>"`
|
||||
- **PowerShell**: `.specify/extensions/git/scripts/powershell/create-new-feature.ps1 -Json -ShortName "<short-name>" "<feature description>"`
|
||||
- **PowerShell (timestamp)**: `.specify/extensions/git/scripts/powershell/create-new-feature.ps1 -Json -Timestamp -ShortName "<short-name>" "<feature description>"`
|
||||
|
||||
**IMPORTANT**:
|
||||
- Do NOT pass `--number` — the script determines the correct next number automatically
|
||||
- Always include the JSON flag (`--json` for Bash, `-Json` for PowerShell) so the output can be parsed reliably
|
||||
- You must only ever run this script once per feature
|
||||
- The JSON output will contain `BRANCH_NAME` and `FEATURE_NUM`
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If Git is not installed or the current directory is not a Git repository:
|
||||
- Branch creation is skipped with a warning: `[specify] Warning: Git repository not detected; skipped branch creation`
|
||||
- The script still outputs `BRANCH_NAME` and `FEATURE_NUM` so the caller can reference them
|
||||
|
||||
## Output
|
||||
|
||||
The script outputs JSON with:
|
||||
- `BRANCH_NAME`: The branch name (e.g., `003-user-auth` or `20260319-143022-user-auth`)
|
||||
- `FEATURE_NUM`: The numeric or timestamp prefix used
|
||||
@ -1,49 +0,0 @@
|
||||
---
|
||||
description: "Initialize a Git repository with an initial commit"
|
||||
---
|
||||
|
||||
# Initialize Git Repository
|
||||
|
||||
Initialize a Git repository in the current project directory if one does not already exist.
|
||||
|
||||
## Execution
|
||||
|
||||
Run the appropriate script from the project root:
|
||||
|
||||
- **Bash**: `.specify/extensions/git/scripts/bash/initialize-repo.sh`
|
||||
- **PowerShell**: `.specify/extensions/git/scripts/powershell/initialize-repo.ps1`
|
||||
|
||||
If the extension scripts are not found, fall back to:
|
||||
- **Bash**: `git init && git add . && git commit -m "Initial commit from Specify template"`
|
||||
- **PowerShell**: `git init; git add .; git commit -m "Initial commit from Specify template"`
|
||||
|
||||
The script handles all checks internally:
|
||||
- Skips if Git is not available
|
||||
- Skips if already inside a Git repository
|
||||
- Runs `git init`, `git add .`, and `git commit` with an initial commit message
|
||||
|
||||
## Customization
|
||||
|
||||
Replace the script to add project-specific Git initialization steps:
|
||||
- Custom `.gitignore` templates
|
||||
- Default branch naming (`git config init.defaultBranch`)
|
||||
- Git LFS setup
|
||||
- Git hooks installation
|
||||
- Commit signing configuration
|
||||
- Git Flow initialization
|
||||
|
||||
## Output
|
||||
|
||||
On success:
|
||||
- `✓ Git repository initialized`
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If Git is not installed:
|
||||
- Warn the user
|
||||
- Skip repository initialization
|
||||
- The project continues to function without Git (specs can still be created under `specs/`)
|
||||
|
||||
If Git is installed but `git init`, `git add .`, or `git commit` fails:
|
||||
- Surface the error to the user
|
||||
- Stop this command rather than continuing with a partially initialized repository
|
||||
@ -1,45 +0,0 @@
|
||||
---
|
||||
description: "Detect Git remote URL for GitHub integration"
|
||||
---
|
||||
|
||||
# Detect Git Remote URL
|
||||
|
||||
Detect the Git remote URL for integration with GitHub services (e.g., issue creation).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Check if Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null`
|
||||
- If Git is not available, output a warning and return empty:
|
||||
```
|
||||
[specify] Warning: Git repository not detected; cannot determine remote URL
|
||||
```
|
||||
|
||||
## Execution
|
||||
|
||||
Run the following command to get the remote URL:
|
||||
|
||||
```bash
|
||||
git config --get remote.origin.url
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
Parse the remote URL and determine:
|
||||
|
||||
1. **Repository owner**: Extract from the URL (e.g., `github` from `https://github.com/github/spec-kit.git`)
|
||||
2. **Repository name**: Extract from the URL (e.g., `spec-kit` from `https://github.com/github/spec-kit.git`)
|
||||
3. **Is GitHub**: Whether the remote points to a GitHub repository
|
||||
|
||||
Supported URL formats:
|
||||
- HTTPS: `https://github.com/<owner>/<repo>.git`
|
||||
- SSH: `git@github.com:<owner>/<repo>.git`
|
||||
|
||||
> [!CAUTION]
|
||||
> ONLY report a GitHub repository if the remote URL actually points to github.com.
|
||||
> Do NOT assume the remote is GitHub if the URL format doesn't match.
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If Git is not installed, the directory is not a Git repository, or no remote is configured:
|
||||
- Return an empty result
|
||||
- Do NOT error — other workflows should continue without Git remote information
|
||||
@ -1,49 +0,0 @@
|
||||
---
|
||||
description: "Validate current branch follows feature branch naming conventions"
|
||||
---
|
||||
|
||||
# Validate Feature Branch
|
||||
|
||||
Validate that the current Git branch follows the expected feature branch naming conventions.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Check if Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null`
|
||||
- If Git is not available, output a warning and skip validation:
|
||||
```
|
||||
[specify] Warning: Git repository not detected; skipped branch validation
|
||||
```
|
||||
|
||||
## Validation Rules
|
||||
|
||||
Get the current branch name:
|
||||
|
||||
```bash
|
||||
git rev-parse --abbrev-ref HEAD
|
||||
```
|
||||
|
||||
The branch name must match one of these patterns:
|
||||
|
||||
1. **Sequential**: `^[0-9]{3,}-` (e.g., `001-feature-name`, `042-fix-bug`, `1000-big-feature`)
|
||||
2. **Timestamp**: `^[0-9]{8}-[0-9]{6}-` (e.g., `20260319-143022-feature-name`)
|
||||
|
||||
## Execution
|
||||
|
||||
If on a feature branch (matches either pattern):
|
||||
- Output: `✓ On feature branch: <branch-name>`
|
||||
- Check if the corresponding spec directory exists under `specs/`:
|
||||
- For sequential branches, look for `specs/<prefix>-*` where prefix matches the numeric portion
|
||||
- For timestamp branches, look for `specs/<prefix>-*` where prefix matches the `YYYYMMDD-HHMMSS` portion
|
||||
- If spec directory exists: `✓ Spec directory found: <path>`
|
||||
- If spec directory missing: `⚠ No spec directory found for prefix <prefix>`
|
||||
|
||||
If NOT on a feature branch:
|
||||
- Output: `✗ Not on a feature branch. Current branch: <branch-name>`
|
||||
- Output: `Feature branches should be named like: 001-feature-name or 20260319-143022-feature-name`
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If Git is not installed or the directory is not a Git repository:
|
||||
- Check the `SPECIFY_FEATURE` environment variable as a fallback
|
||||
- If set, validate that value against the naming patterns
|
||||
- If not set, skip validation with a warning
|
||||
@ -1,62 +0,0 @@
|
||||
# Git Branching Workflow Extension Configuration
|
||||
# Copied to .specify/extensions/git/git-config.yml on install
|
||||
|
||||
# Branch numbering strategy: "sequential" (001, 002, ...) or "timestamp" (YYYYMMDD-HHMMSS)
|
||||
branch_numbering: sequential
|
||||
|
||||
# Commit message used by `git commit` during repository initialization
|
||||
init_commit_message: "[Spec Kit] Initial commit"
|
||||
|
||||
# Auto-commit before/after core commands.
|
||||
# Set "default" to enable for all commands, then override per-command.
|
||||
# Each key can be true/false. Message is customizable per-command.
|
||||
auto_commit:
|
||||
default: false
|
||||
before_clarify:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Save progress before clarification"
|
||||
before_plan:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Save progress before planning"
|
||||
before_tasks:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Save progress before task generation"
|
||||
before_implement:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Save progress before implementation"
|
||||
before_checklist:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Save progress before checklist"
|
||||
before_analyze:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Save progress before analysis"
|
||||
before_taskstoissues:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Save progress before issue sync"
|
||||
after_constitution:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Add project constitution"
|
||||
after_specify:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Add specification"
|
||||
after_clarify:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Clarify specification"
|
||||
after_plan:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Add implementation plan"
|
||||
after_tasks:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Add tasks"
|
||||
after_implement:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Implementation progress"
|
||||
after_checklist:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Add checklist"
|
||||
after_analyze:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Add analysis report"
|
||||
after_taskstoissues:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Sync tasks to issues"
|
||||
@ -1,140 +0,0 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
extension:
|
||||
id: git
|
||||
name: "Git Branching Workflow"
|
||||
version: "1.0.0"
|
||||
description: "Feature branch creation, numbering (sequential/timestamp), validation, and Git remote detection"
|
||||
author: spec-kit-core
|
||||
repository: https://github.com/github/spec-kit
|
||||
license: MIT
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.2.0"
|
||||
tools:
|
||||
- name: git
|
||||
required: false
|
||||
|
||||
provides:
|
||||
commands:
|
||||
- name: speckit.git.feature
|
||||
file: commands/speckit.git.feature.md
|
||||
description: "Create a feature branch with sequential or timestamp numbering"
|
||||
- name: speckit.git.validate
|
||||
file: commands/speckit.git.validate.md
|
||||
description: "Validate current branch follows feature branch naming conventions"
|
||||
- name: speckit.git.remote
|
||||
file: commands/speckit.git.remote.md
|
||||
description: "Detect Git remote URL for GitHub integration"
|
||||
- name: speckit.git.initialize
|
||||
file: commands/speckit.git.initialize.md
|
||||
description: "Initialize a Git repository with an initial commit"
|
||||
- name: speckit.git.commit
|
||||
file: commands/speckit.git.commit.md
|
||||
description: "Auto-commit changes after a Spec Kit command completes"
|
||||
|
||||
config:
|
||||
- name: "git-config.yml"
|
||||
template: "config-template.yml"
|
||||
description: "Git branching configuration"
|
||||
required: false
|
||||
|
||||
hooks:
|
||||
before_constitution:
|
||||
command: speckit.git.initialize
|
||||
optional: false
|
||||
description: "Initialize Git repository before constitution setup"
|
||||
before_specify:
|
||||
command: speckit.git.feature
|
||||
optional: false
|
||||
description: "Create feature branch before specification"
|
||||
before_clarify:
|
||||
command: speckit.git.commit
|
||||
optional: true
|
||||
prompt: "Commit outstanding changes before clarification?"
|
||||
description: "Auto-commit before spec clarification"
|
||||
before_plan:
|
||||
command: speckit.git.commit
|
||||
optional: true
|
||||
prompt: "Commit outstanding changes before planning?"
|
||||
description: "Auto-commit before implementation planning"
|
||||
before_tasks:
|
||||
command: speckit.git.commit
|
||||
optional: true
|
||||
prompt: "Commit outstanding changes before task generation?"
|
||||
description: "Auto-commit before task generation"
|
||||
before_implement:
|
||||
command: speckit.git.commit
|
||||
optional: true
|
||||
prompt: "Commit outstanding changes before implementation?"
|
||||
description: "Auto-commit before implementation"
|
||||
before_checklist:
|
||||
command: speckit.git.commit
|
||||
optional: true
|
||||
prompt: "Commit outstanding changes before checklist?"
|
||||
description: "Auto-commit before checklist generation"
|
||||
before_analyze:
|
||||
command: speckit.git.commit
|
||||
optional: true
|
||||
prompt: "Commit outstanding changes before analysis?"
|
||||
description: "Auto-commit before analysis"
|
||||
before_taskstoissues:
|
||||
command: speckit.git.commit
|
||||
optional: true
|
||||
prompt: "Commit outstanding changes before issue sync?"
|
||||
description: "Auto-commit before tasks-to-issues conversion"
|
||||
after_constitution:
|
||||
command: speckit.git.commit
|
||||
optional: true
|
||||
prompt: "Commit constitution changes?"
|
||||
description: "Auto-commit after constitution update"
|
||||
after_specify:
|
||||
command: speckit.git.commit
|
||||
optional: true
|
||||
prompt: "Commit specification changes?"
|
||||
description: "Auto-commit after specification"
|
||||
after_clarify:
|
||||
command: speckit.git.commit
|
||||
optional: true
|
||||
prompt: "Commit clarification changes?"
|
||||
description: "Auto-commit after spec clarification"
|
||||
after_plan:
|
||||
command: speckit.git.commit
|
||||
optional: true
|
||||
prompt: "Commit plan changes?"
|
||||
description: "Auto-commit after implementation planning"
|
||||
after_tasks:
|
||||
command: speckit.git.commit
|
||||
optional: true
|
||||
prompt: "Commit task changes?"
|
||||
description: "Auto-commit after task generation"
|
||||
after_implement:
|
||||
command: speckit.git.commit
|
||||
optional: true
|
||||
prompt: "Commit implementation changes?"
|
||||
description: "Auto-commit after implementation"
|
||||
after_checklist:
|
||||
command: speckit.git.commit
|
||||
optional: true
|
||||
prompt: "Commit checklist changes?"
|
||||
description: "Auto-commit after checklist generation"
|
||||
after_analyze:
|
||||
command: speckit.git.commit
|
||||
optional: true
|
||||
prompt: "Commit analysis results?"
|
||||
description: "Auto-commit after analysis"
|
||||
after_taskstoissues:
|
||||
command: speckit.git.commit
|
||||
optional: true
|
||||
prompt: "Commit after syncing issues?"
|
||||
description: "Auto-commit after tasks-to-issues conversion"
|
||||
|
||||
tags:
|
||||
- "git"
|
||||
- "branching"
|
||||
- "workflow"
|
||||
|
||||
config:
|
||||
defaults:
|
||||
branch_numbering: sequential
|
||||
init_commit_message: "[Spec Kit] Initial commit"
|
||||
@ -1,62 +0,0 @@
|
||||
# Git Branching Workflow Extension Configuration
|
||||
# Copied to .specify/extensions/git/git-config.yml on install
|
||||
|
||||
# Branch numbering strategy: "sequential" (001, 002, ...) or "timestamp" (YYYYMMDD-HHMMSS)
|
||||
branch_numbering: sequential
|
||||
|
||||
# Commit message used by `git commit` during repository initialization
|
||||
init_commit_message: "[Spec Kit] Initial commit"
|
||||
|
||||
# Auto-commit before/after core commands.
|
||||
# Set "default" to enable for all commands, then override per-command.
|
||||
# Each key can be true/false. Message is customizable per-command.
|
||||
auto_commit:
|
||||
default: false
|
||||
before_clarify:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Save progress before clarification"
|
||||
before_plan:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Save progress before planning"
|
||||
before_tasks:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Save progress before task generation"
|
||||
before_implement:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Save progress before implementation"
|
||||
before_checklist:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Save progress before checklist"
|
||||
before_analyze:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Save progress before analysis"
|
||||
before_taskstoissues:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Save progress before issue sync"
|
||||
after_constitution:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Add project constitution"
|
||||
after_specify:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Add specification"
|
||||
after_clarify:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Clarify specification"
|
||||
after_plan:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Add implementation plan"
|
||||
after_tasks:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Add tasks"
|
||||
after_implement:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Implementation progress"
|
||||
after_checklist:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Add checklist"
|
||||
after_analyze:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Add analysis report"
|
||||
after_taskstoissues:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Sync tasks to issues"
|
||||
@ -1,140 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Git extension: auto-commit.sh
|
||||
# Automatically commit changes after a Spec Kit command completes.
|
||||
# Checks per-command config keys in git-config.yml before committing.
|
||||
#
|
||||
# Usage: auto-commit.sh <event_name>
|
||||
# e.g.: auto-commit.sh after_specify
|
||||
|
||||
set -e
|
||||
|
||||
EVENT_NAME="${1:-}"
|
||||
if [ -z "$EVENT_NAME" ]; then
|
||||
echo "Usage: $0 <event_name>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
_find_project_root() {
|
||||
local dir="$1"
|
||||
while [ "$dir" != "/" ]; do
|
||||
if [ -d "$dir/.specify" ] || [ -d "$dir/.git" ]; then
|
||||
echo "$dir"
|
||||
return 0
|
||||
fi
|
||||
dir="$(dirname "$dir")"
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
REPO_ROOT=$(_find_project_root "$SCRIPT_DIR") || REPO_ROOT="$(pwd)"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
# Check if git is available
|
||||
if ! command -v git >/dev/null 2>&1; then
|
||||
echo "[specify] Warning: Git not found; skipped auto-commit" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
echo "[specify] Warning: Not a Git repository; skipped auto-commit" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Read per-command config from git-config.yml
|
||||
_config_file="$REPO_ROOT/.specify/extensions/git/git-config.yml"
|
||||
_enabled=false
|
||||
_commit_msg=""
|
||||
|
||||
if [ -f "$_config_file" ]; then
|
||||
# Parse the auto_commit section for this event.
|
||||
# Look for auto_commit.<event_name>.enabled and .message
|
||||
# Also check auto_commit.default as fallback.
|
||||
_in_auto_commit=false
|
||||
_in_event=false
|
||||
_default_enabled=false
|
||||
|
||||
while IFS= read -r _line; do
|
||||
# Detect auto_commit: section
|
||||
if echo "$_line" | grep -q '^auto_commit:'; then
|
||||
_in_auto_commit=true
|
||||
_in_event=false
|
||||
continue
|
||||
fi
|
||||
|
||||
# Exit auto_commit section on next top-level key
|
||||
if $_in_auto_commit && echo "$_line" | grep -Eq '^[a-z]'; then
|
||||
break
|
||||
fi
|
||||
|
||||
if $_in_auto_commit; then
|
||||
# Check default key
|
||||
if echo "$_line" | grep -Eq "^[[:space:]]+default:[[:space:]]"; then
|
||||
_val=$(echo "$_line" | sed 's/^[^:]*:[[:space:]]*//' | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]')
|
||||
[ "$_val" = "true" ] && _default_enabled=true
|
||||
fi
|
||||
|
||||
# Detect our event subsection
|
||||
if echo "$_line" | grep -Eq "^[[:space:]]+${EVENT_NAME}:"; then
|
||||
_in_event=true
|
||||
continue
|
||||
fi
|
||||
|
||||
# Inside our event subsection
|
||||
if $_in_event; then
|
||||
# Exit on next sibling key (same indent level as event name)
|
||||
if echo "$_line" | grep -Eq '^[[:space:]]{2}[a-z]' && ! echo "$_line" | grep -Eq '^[[:space:]]{4}'; then
|
||||
_in_event=false
|
||||
continue
|
||||
fi
|
||||
if echo "$_line" | grep -Eq '[[:space:]]+enabled:'; then
|
||||
_val=$(echo "$_line" | sed 's/^[^:]*:[[:space:]]*//' | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]')
|
||||
[ "$_val" = "true" ] && _enabled=true
|
||||
[ "$_val" = "false" ] && _enabled=false
|
||||
fi
|
||||
if echo "$_line" | grep -Eq '[[:space:]]+message:'; then
|
||||
_commit_msg=$(echo "$_line" | sed 's/^[^:]*:[[:space:]]*//' | sed 's/^["'\'']//' | sed 's/["'\'']*$//')
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done < "$_config_file"
|
||||
|
||||
# If event-specific key not found, use default
|
||||
if [ "$_enabled" = "false" ] && [ "$_default_enabled" = "true" ]; then
|
||||
# Only use default if the event wasn't explicitly set to false
|
||||
# Check if event section existed at all
|
||||
if ! grep -q "^[[:space:]]*${EVENT_NAME}:" "$_config_file" 2>/dev/null; then
|
||||
_enabled=true
|
||||
fi
|
||||
fi
|
||||
else
|
||||
# No config file — auto-commit disabled by default
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$_enabled" != "true" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if there are changes to commit
|
||||
if git diff --quiet HEAD 2>/dev/null && git diff --cached --quiet 2>/dev/null && [ -z "$(git ls-files --others --exclude-standard 2>/dev/null)" ]; then
|
||||
echo "[specify] No changes to commit after $EVENT_NAME" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Derive a human-readable command name from the event
|
||||
# e.g., after_specify -> specify, before_plan -> plan
|
||||
_command_name=$(echo "$EVENT_NAME" | sed 's/^after_//' | sed 's/^before_//')
|
||||
_phase=$(echo "$EVENT_NAME" | grep -q '^before_' && echo 'before' || echo 'after')
|
||||
|
||||
# Use custom message if configured, otherwise default
|
||||
if [ -z "$_commit_msg" ]; then
|
||||
_commit_msg="[Spec Kit] Auto-commit ${_phase} ${_command_name}"
|
||||
fi
|
||||
|
||||
# Stage and commit
|
||||
_git_out=$(git add . 2>&1) || { echo "[specify] Error: git add failed: $_git_out" >&2; exit 1; }
|
||||
_git_out=$(git commit -q -m "$_commit_msg" 2>&1) || { echo "[specify] Error: git commit failed: $_git_out" >&2; exit 1; }
|
||||
|
||||
echo "[OK] Changes committed ${_phase} ${_command_name}" >&2
|
||||
@ -1,453 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Git extension: create-new-feature.sh
|
||||
# Adapted from core scripts/bash/create-new-feature.sh for extension layout.
|
||||
# Sources common.sh from the project's installed scripts, falling back to
|
||||
# git-common.sh for minimal git helpers.
|
||||
|
||||
set -e
|
||||
|
||||
JSON_MODE=false
|
||||
DRY_RUN=false
|
||||
ALLOW_EXISTING=false
|
||||
SHORT_NAME=""
|
||||
BRANCH_NUMBER=""
|
||||
USE_TIMESTAMP=false
|
||||
ARGS=()
|
||||
i=1
|
||||
while [ $i -le $# ]; do
|
||||
arg="${!i}"
|
||||
case "$arg" in
|
||||
--json)
|
||||
JSON_MODE=true
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
;;
|
||||
--allow-existing-branch)
|
||||
ALLOW_EXISTING=true
|
||||
;;
|
||||
--short-name)
|
||||
if [ $((i + 1)) -gt $# ]; then
|
||||
echo 'Error: --short-name requires a value' >&2
|
||||
exit 1
|
||||
fi
|
||||
i=$((i + 1))
|
||||
next_arg="${!i}"
|
||||
if [[ "$next_arg" == --* ]]; then
|
||||
echo 'Error: --short-name requires a value' >&2
|
||||
exit 1
|
||||
fi
|
||||
SHORT_NAME="$next_arg"
|
||||
;;
|
||||
--number)
|
||||
if [ $((i + 1)) -gt $# ]; then
|
||||
echo 'Error: --number requires a value' >&2
|
||||
exit 1
|
||||
fi
|
||||
i=$((i + 1))
|
||||
next_arg="${!i}"
|
||||
if [[ "$next_arg" == --* ]]; then
|
||||
echo 'Error: --number requires a value' >&2
|
||||
exit 1
|
||||
fi
|
||||
BRANCH_NUMBER="$next_arg"
|
||||
if [[ ! "$BRANCH_NUMBER" =~ ^[0-9]+$ ]]; then
|
||||
echo 'Error: --number must be a non-negative integer' >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
--timestamp)
|
||||
USE_TIMESTAMP=true
|
||||
;;
|
||||
--help|-h)
|
||||
echo "Usage: $0 [--json] [--dry-run] [--allow-existing-branch] [--short-name <name>] [--number N] [--timestamp] <feature_description>"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --json Output in JSON format"
|
||||
echo " --dry-run Compute branch name without creating the branch"
|
||||
echo " --allow-existing-branch Switch to branch if it already exists instead of failing"
|
||||
echo " --short-name <name> Provide a custom short name (2-4 words) for the branch"
|
||||
echo " --number N Specify branch number manually (overrides auto-detection)"
|
||||
echo " --timestamp Use timestamp prefix (YYYYMMDD-HHMMSS) instead of sequential numbering"
|
||||
echo " --help, -h Show this help message"
|
||||
echo ""
|
||||
echo "Environment variables:"
|
||||
echo " GIT_BRANCH_NAME Use this exact branch name, bypassing all prefix/suffix generation"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 'Add user authentication system' --short-name 'user-auth'"
|
||||
echo " $0 'Implement OAuth2 integration for API' --number 5"
|
||||
echo " $0 --timestamp --short-name 'user-auth' 'Add user authentication'"
|
||||
echo " GIT_BRANCH_NAME=my-branch $0 'feature description'"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
ARGS+=("$arg")
|
||||
;;
|
||||
esac
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
FEATURE_DESCRIPTION="${ARGS[*]}"
|
||||
if [ -z "$FEATURE_DESCRIPTION" ]; then
|
||||
echo "Usage: $0 [--json] [--dry-run] [--allow-existing-branch] [--short-name <name>] [--number N] [--timestamp] <feature_description>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Trim whitespace and validate description is not empty
|
||||
FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | xargs)
|
||||
if [ -z "$FEATURE_DESCRIPTION" ]; then
|
||||
echo "Error: Feature description cannot be empty or contain only whitespace" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Function to get highest number from specs directory
|
||||
get_highest_from_specs() {
|
||||
local specs_dir="$1"
|
||||
local highest=0
|
||||
|
||||
if [ -d "$specs_dir" ]; then
|
||||
for dir in "$specs_dir"/*; do
|
||||
[ -d "$dir" ] || continue
|
||||
dirname=$(basename "$dir")
|
||||
# Match sequential prefixes (>=3 digits), but skip timestamp dirs.
|
||||
if echo "$dirname" | grep -Eq '^[0-9]{3,}-' && ! echo "$dirname" | grep -Eq '^[0-9]{8}-[0-9]{6}-'; then
|
||||
number=$(echo "$dirname" | grep -Eo '^[0-9]+')
|
||||
number=$((10#$number))
|
||||
if [ "$number" -gt "$highest" ]; then
|
||||
highest=$number
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "$highest"
|
||||
}
|
||||
|
||||
# Function to get highest number from git branches
|
||||
get_highest_from_branches() {
|
||||
git branch -a 2>/dev/null | sed 's/^[* ]*//; s|^remotes/[^/]*/||' | _extract_highest_number
|
||||
}
|
||||
|
||||
# Extract the highest sequential feature number from a list of ref names (one per line).
|
||||
_extract_highest_number() {
|
||||
local highest=0
|
||||
while IFS= read -r name; do
|
||||
[ -z "$name" ] && continue
|
||||
if echo "$name" | grep -Eq '^[0-9]{3,}-' && ! echo "$name" | grep -Eq '^[0-9]{8}-[0-9]{6}-'; then
|
||||
number=$(echo "$name" | grep -Eo '^[0-9]+' || echo "0")
|
||||
number=$((10#$number))
|
||||
if [ "$number" -gt "$highest" ]; then
|
||||
highest=$number
|
||||
fi
|
||||
fi
|
||||
done
|
||||
echo "$highest"
|
||||
}
|
||||
|
||||
# Function to get highest number from remote branches without fetching (side-effect-free)
|
||||
get_highest_from_remote_refs() {
|
||||
local highest=0
|
||||
|
||||
for remote in $(git remote 2>/dev/null); do
|
||||
local remote_highest
|
||||
remote_highest=$(GIT_TERMINAL_PROMPT=0 git ls-remote --heads "$remote" 2>/dev/null | sed 's|.*refs/heads/||' | _extract_highest_number)
|
||||
if [ "$remote_highest" -gt "$highest" ]; then
|
||||
highest=$remote_highest
|
||||
fi
|
||||
done
|
||||
|
||||
echo "$highest"
|
||||
}
|
||||
|
||||
# Function to check existing branches and return next available number.
|
||||
check_existing_branches() {
|
||||
local specs_dir="$1"
|
||||
local skip_fetch="${2:-false}"
|
||||
|
||||
if [ "$skip_fetch" = true ]; then
|
||||
local highest_remote=$(get_highest_from_remote_refs)
|
||||
local highest_branch=$(get_highest_from_branches)
|
||||
if [ "$highest_remote" -gt "$highest_branch" ]; then
|
||||
highest_branch=$highest_remote
|
||||
fi
|
||||
else
|
||||
git fetch --all --prune >/dev/null 2>&1 || true
|
||||
local highest_branch=$(get_highest_from_branches)
|
||||
fi
|
||||
|
||||
local highest_spec=$(get_highest_from_specs "$specs_dir")
|
||||
|
||||
local max_num=$highest_branch
|
||||
if [ "$highest_spec" -gt "$max_num" ]; then
|
||||
max_num=$highest_spec
|
||||
fi
|
||||
|
||||
echo $((max_num + 1))
|
||||
}
|
||||
|
||||
# Function to clean and format a branch name
|
||||
clean_branch_name() {
|
||||
local name="$1"
|
||||
echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//'
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Source common.sh for resolve_template, json_escape, get_repo_root, has_git.
|
||||
#
|
||||
# Search locations in priority order:
|
||||
# 1. .specify/scripts/bash/common.sh under the project root (installed project)
|
||||
# 2. scripts/bash/common.sh under the project root (source checkout fallback)
|
||||
# 3. git-common.sh next to this script (minimal fallback — lacks resolve_template)
|
||||
# ---------------------------------------------------------------------------
|
||||
SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Find project root by walking up from the script location
|
||||
_find_project_root() {
|
||||
local dir="$1"
|
||||
while [ "$dir" != "/" ]; do
|
||||
if [ -d "$dir/.specify" ] || [ -d "$dir/.git" ]; then
|
||||
echo "$dir"
|
||||
return 0
|
||||
fi
|
||||
dir="$(dirname "$dir")"
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
_common_loaded=false
|
||||
_PROJECT_ROOT=$(_find_project_root "$SCRIPT_DIR") || true
|
||||
|
||||
if [ -n "$_PROJECT_ROOT" ] && [ -f "$_PROJECT_ROOT/.specify/scripts/bash/common.sh" ]; then
|
||||
source "$_PROJECT_ROOT/.specify/scripts/bash/common.sh"
|
||||
_common_loaded=true
|
||||
elif [ -n "$_PROJECT_ROOT" ] && [ -f "$_PROJECT_ROOT/scripts/bash/common.sh" ]; then
|
||||
source "$_PROJECT_ROOT/scripts/bash/common.sh"
|
||||
_common_loaded=true
|
||||
elif [ -f "$SCRIPT_DIR/git-common.sh" ]; then
|
||||
source "$SCRIPT_DIR/git-common.sh"
|
||||
_common_loaded=true
|
||||
fi
|
||||
|
||||
if [ "$_common_loaded" != "true" ]; then
|
||||
echo "Error: Could not locate common.sh or git-common.sh. Please ensure the Specify core scripts are installed." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Resolve repository root
|
||||
if type get_repo_root >/dev/null 2>&1; then
|
||||
REPO_ROOT=$(get_repo_root)
|
||||
elif git rev-parse --show-toplevel >/dev/null 2>&1; then
|
||||
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||
elif [ -n "$_PROJECT_ROOT" ]; then
|
||||
REPO_ROOT="$_PROJECT_ROOT"
|
||||
else
|
||||
echo "Error: Could not determine repository root." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if git is available at this repo root
|
||||
if type has_git >/dev/null 2>&1; then
|
||||
if has_git "$REPO_ROOT"; then
|
||||
HAS_GIT=true
|
||||
else
|
||||
HAS_GIT=false
|
||||
fi
|
||||
elif git -C "$REPO_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
HAS_GIT=true
|
||||
else
|
||||
HAS_GIT=false
|
||||
fi
|
||||
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
SPECS_DIR="$REPO_ROOT/specs"
|
||||
|
||||
# Function to generate branch name with stop word filtering
|
||||
generate_branch_name() {
|
||||
local description="$1"
|
||||
|
||||
local stop_words="^(i|a|an|the|to|for|of|in|on|at|by|with|from|is|are|was|were|be|been|being|have|has|had|do|does|did|will|would|should|could|can|may|might|must|shall|this|that|these|those|my|your|our|their|want|need|add|get|set)$"
|
||||
|
||||
local clean_name=$(echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g')
|
||||
|
||||
local meaningful_words=()
|
||||
for word in $clean_name; do
|
||||
[ -z "$word" ] && continue
|
||||
if ! echo "$word" | grep -qiE "$stop_words"; then
|
||||
if [ ${#word} -ge 3 ]; then
|
||||
meaningful_words+=("$word")
|
||||
elif echo "$description" | grep -qw -- "${word^^}"; then
|
||||
meaningful_words+=("$word")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#meaningful_words[@]} -gt 0 ]; then
|
||||
local max_words=3
|
||||
if [ ${#meaningful_words[@]} -eq 4 ]; then max_words=4; fi
|
||||
|
||||
local result=""
|
||||
local count=0
|
||||
for word in "${meaningful_words[@]}"; do
|
||||
if [ $count -ge $max_words ]; then break; fi
|
||||
if [ -n "$result" ]; then result="$result-"; fi
|
||||
result="$result$word"
|
||||
count=$((count + 1))
|
||||
done
|
||||
echo "$result"
|
||||
else
|
||||
local cleaned=$(clean_branch_name "$description")
|
||||
echo "$cleaned" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//'
|
||||
fi
|
||||
}
|
||||
|
||||
# Check for GIT_BRANCH_NAME env var override (exact branch name, no prefix/suffix)
|
||||
if [ -n "${GIT_BRANCH_NAME:-}" ]; then
|
||||
BRANCH_NAME="$GIT_BRANCH_NAME"
|
||||
# Extract FEATURE_NUM from the branch name if it starts with a numeric prefix
|
||||
# Check timestamp pattern first (YYYYMMDD-HHMMSS-) since it also matches the simpler ^[0-9]+ pattern
|
||||
if echo "$BRANCH_NAME" | grep -Eq '^[0-9]{8}-[0-9]{6}-'; then
|
||||
FEATURE_NUM=$(echo "$BRANCH_NAME" | grep -Eo '^[0-9]{8}-[0-9]{6}')
|
||||
BRANCH_SUFFIX="${BRANCH_NAME#${FEATURE_NUM}-}"
|
||||
elif echo "$BRANCH_NAME" | grep -Eq '^[0-9]+-'; then
|
||||
FEATURE_NUM=$(echo "$BRANCH_NAME" | grep -Eo '^[0-9]+')
|
||||
BRANCH_SUFFIX="${BRANCH_NAME#${FEATURE_NUM}-}"
|
||||
else
|
||||
FEATURE_NUM="$BRANCH_NAME"
|
||||
BRANCH_SUFFIX="$BRANCH_NAME"
|
||||
fi
|
||||
else
|
||||
# Generate branch name
|
||||
if [ -n "$SHORT_NAME" ]; then
|
||||
BRANCH_SUFFIX=$(clean_branch_name "$SHORT_NAME")
|
||||
else
|
||||
BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION")
|
||||
fi
|
||||
|
||||
# Warn if --number and --timestamp are both specified
|
||||
if [ "$USE_TIMESTAMP" = true ] && [ -n "$BRANCH_NUMBER" ]; then
|
||||
>&2 echo "[specify] Warning: --number is ignored when --timestamp is used"
|
||||
BRANCH_NUMBER=""
|
||||
fi
|
||||
|
||||
# Determine branch prefix
|
||||
if [ "$USE_TIMESTAMP" = true ]; then
|
||||
FEATURE_NUM=$(date +%Y%m%d-%H%M%S)
|
||||
BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}"
|
||||
else
|
||||
if [ -z "$BRANCH_NUMBER" ]; then
|
||||
if [ "$DRY_RUN" = true ] && [ "$HAS_GIT" = true ]; then
|
||||
BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR" true)
|
||||
elif [ "$DRY_RUN" = true ]; then
|
||||
HIGHEST=$(get_highest_from_specs "$SPECS_DIR")
|
||||
BRANCH_NUMBER=$((HIGHEST + 1))
|
||||
elif [ "$HAS_GIT" = true ]; then
|
||||
BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR")
|
||||
else
|
||||
HIGHEST=$(get_highest_from_specs "$SPECS_DIR")
|
||||
BRANCH_NUMBER=$((HIGHEST + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
FEATURE_NUM=$(printf "%03d" "$((10#$BRANCH_NUMBER))")
|
||||
BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# GitHub enforces a 244-byte limit on branch names
|
||||
MAX_BRANCH_LENGTH=244
|
||||
_byte_length() { printf '%s' "$1" | LC_ALL=C wc -c | tr -d ' '; }
|
||||
BRANCH_BYTE_LEN=$(_byte_length "$BRANCH_NAME")
|
||||
if [ -n "${GIT_BRANCH_NAME:-}" ] && [ "$BRANCH_BYTE_LEN" -gt $MAX_BRANCH_LENGTH ]; then
|
||||
>&2 echo "Error: GIT_BRANCH_NAME must be 244 bytes or fewer in UTF-8. Provided value is ${BRANCH_BYTE_LEN} bytes."
|
||||
exit 1
|
||||
elif [ "$BRANCH_BYTE_LEN" -gt $MAX_BRANCH_LENGTH ]; then
|
||||
PREFIX_LENGTH=$(( ${#FEATURE_NUM} + 1 ))
|
||||
MAX_SUFFIX_LENGTH=$((MAX_BRANCH_LENGTH - PREFIX_LENGTH))
|
||||
|
||||
TRUNCATED_SUFFIX=$(echo "$BRANCH_SUFFIX" | cut -c1-$MAX_SUFFIX_LENGTH)
|
||||
TRUNCATED_SUFFIX=$(echo "$TRUNCATED_SUFFIX" | sed 's/-$//')
|
||||
|
||||
ORIGINAL_BRANCH_NAME="$BRANCH_NAME"
|
||||
BRANCH_NAME="${FEATURE_NUM}-${TRUNCATED_SUFFIX}"
|
||||
|
||||
>&2 echo "[specify] Warning: Branch name exceeded GitHub's 244-byte limit"
|
||||
>&2 echo "[specify] Original: $ORIGINAL_BRANCH_NAME (${#ORIGINAL_BRANCH_NAME} bytes)"
|
||||
>&2 echo "[specify] Truncated to: $BRANCH_NAME (${#BRANCH_NAME} bytes)"
|
||||
fi
|
||||
|
||||
if [ "$DRY_RUN" != true ]; then
|
||||
if [ "$HAS_GIT" = true ]; then
|
||||
branch_create_error=""
|
||||
if ! branch_create_error=$(git checkout -q -b "$BRANCH_NAME" 2>&1); then
|
||||
current_branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
||||
if git branch --list "$BRANCH_NAME" | grep -q .; then
|
||||
if [ "$ALLOW_EXISTING" = true ]; then
|
||||
if [ "$current_branch" = "$BRANCH_NAME" ]; then
|
||||
:
|
||||
elif ! switch_branch_error=$(git checkout -q "$BRANCH_NAME" 2>&1); then
|
||||
>&2 echo "Error: Failed to switch to existing branch '$BRANCH_NAME'. Please resolve any local changes or conflicts and try again."
|
||||
if [ -n "$switch_branch_error" ]; then
|
||||
>&2 printf '%s\n' "$switch_branch_error"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
elif [ "$USE_TIMESTAMP" = true ]; then
|
||||
>&2 echo "Error: Branch '$BRANCH_NAME' already exists. Rerun to get a new timestamp or use a different --short-name."
|
||||
exit 1
|
||||
else
|
||||
>&2 echo "Error: Branch '$BRANCH_NAME' already exists. Please use a different feature name or specify a different number with --number."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
>&2 echo "Error: Failed to create git branch '$BRANCH_NAME'."
|
||||
if [ -n "$branch_create_error" ]; then
|
||||
>&2 printf '%s\n' "$branch_create_error"
|
||||
else
|
||||
>&2 echo "Please check your git configuration and try again."
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
>&2 echo "[specify] Warning: Git repository not detected; skipped branch creation for $BRANCH_NAME"
|
||||
fi
|
||||
|
||||
printf '# To persist: export SPECIFY_FEATURE=%q\n' "$BRANCH_NAME" >&2
|
||||
fi
|
||||
|
||||
if $JSON_MODE; then
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
jq -cn \
|
||||
--arg branch_name "$BRANCH_NAME" \
|
||||
--arg feature_num "$FEATURE_NUM" \
|
||||
'{BRANCH_NAME:$branch_name,FEATURE_NUM:$feature_num,DRY_RUN:true}'
|
||||
else
|
||||
jq -cn \
|
||||
--arg branch_name "$BRANCH_NAME" \
|
||||
--arg feature_num "$FEATURE_NUM" \
|
||||
'{BRANCH_NAME:$branch_name,FEATURE_NUM:$feature_num}'
|
||||
fi
|
||||
else
|
||||
if type json_escape >/dev/null 2>&1; then
|
||||
_je_branch=$(json_escape "$BRANCH_NAME")
|
||||
_je_num=$(json_escape "$FEATURE_NUM")
|
||||
else
|
||||
_je_branch="$BRANCH_NAME"
|
||||
_je_num="$FEATURE_NUM"
|
||||
fi
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
printf '{"BRANCH_NAME":"%s","FEATURE_NUM":"%s","DRY_RUN":true}\n' "$_je_branch" "$_je_num"
|
||||
else
|
||||
printf '{"BRANCH_NAME":"%s","FEATURE_NUM":"%s"}\n' "$_je_branch" "$_je_num"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "BRANCH_NAME: $BRANCH_NAME"
|
||||
echo "FEATURE_NUM: $FEATURE_NUM"
|
||||
if [ "$DRY_RUN" != true ]; then
|
||||
printf '# To persist in your shell: export SPECIFY_FEATURE=%q\n' "$BRANCH_NAME"
|
||||
fi
|
||||
fi
|
||||
@ -1,54 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Git-specific common functions for the git extension.
|
||||
# Extracted from scripts/bash/common.sh — contains only git-specific
|
||||
# branch validation and detection logic.
|
||||
|
||||
# Check if we have git available at the repo root
|
||||
has_git() {
|
||||
local repo_root="${1:-$(pwd)}"
|
||||
{ [ -d "$repo_root/.git" ] || [ -f "$repo_root/.git" ]; } && \
|
||||
command -v git >/dev/null 2>&1 && \
|
||||
git -C "$repo_root" rev-parse --is-inside-work-tree >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Strip a single optional path segment (e.g. gitflow "feat/004-name" -> "004-name").
|
||||
# Only when the full name is exactly two slash-free segments; otherwise returns the raw name.
|
||||
spec_kit_effective_branch_name() {
|
||||
local raw="$1"
|
||||
if [[ "$raw" =~ ^([^/]+)/([^/]+)$ ]]; then
|
||||
printf '%s\n' "${BASH_REMATCH[2]}"
|
||||
else
|
||||
printf '%s\n' "$raw"
|
||||
fi
|
||||
}
|
||||
|
||||
# Validate that a branch name matches the expected feature branch pattern.
|
||||
# Accepts sequential (###-* with >=3 digits) or timestamp (YYYYMMDD-HHMMSS-*) formats.
|
||||
# Logic aligned with scripts/bash/common.sh check_feature_branch after effective-name normalization.
|
||||
check_feature_branch() {
|
||||
local raw="$1"
|
||||
local has_git_repo="$2"
|
||||
|
||||
# For non-git repos, we can't enforce branch naming but still provide output
|
||||
if [[ "$has_git_repo" != "true" ]]; then
|
||||
echo "[specify] Warning: Git repository not detected; skipped branch validation" >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
local branch
|
||||
branch=$(spec_kit_effective_branch_name "$raw")
|
||||
|
||||
# Accept sequential prefix (3+ digits) but exclude malformed timestamps
|
||||
# Malformed: 7-or-8 digit date + 6-digit time with no trailing slug (e.g. "2026031-143022" or "20260319-143022")
|
||||
local is_sequential=false
|
||||
if [[ "$branch" =~ ^[0-9]{3,}- ]] && [[ ! "$branch" =~ ^[0-9]{7}-[0-9]{6}- ]] && [[ ! "$branch" =~ ^[0-9]{7,8}-[0-9]{6}$ ]]; then
|
||||
is_sequential=true
|
||||
fi
|
||||
if [[ "$is_sequential" != "true" ]] && [[ ! "$branch" =~ ^[0-9]{8}-[0-9]{6}- ]]; then
|
||||
echo "ERROR: Not on a feature branch. Current branch: $raw" >&2
|
||||
echo "Feature branches should be named like: 001-feature-name, 1234-feature-name, or 20260319-143022-feature-name" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Git extension: initialize-repo.sh
|
||||
# Initialize a Git repository with an initial commit.
|
||||
# Customizable — replace this script to add .gitignore templates,
|
||||
# default branch config, git-flow, LFS, signing, etc.
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Find project root
|
||||
_find_project_root() {
|
||||
local dir="$1"
|
||||
while [ "$dir" != "/" ]; do
|
||||
if [ -d "$dir/.specify" ] || [ -d "$dir/.git" ]; then
|
||||
echo "$dir"
|
||||
return 0
|
||||
fi
|
||||
dir="$(dirname "$dir")"
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
REPO_ROOT=$(_find_project_root "$SCRIPT_DIR") || REPO_ROOT="$(pwd)"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
# Read commit message from extension config, fall back to default
|
||||
COMMIT_MSG="[Spec Kit] Initial commit"
|
||||
_config_file="$REPO_ROOT/.specify/extensions/git/git-config.yml"
|
||||
if [ -f "$_config_file" ]; then
|
||||
_msg=$(grep '^init_commit_message:' "$_config_file" 2>/dev/null | sed 's/^init_commit_message:[[:space:]]*//' | sed 's/^["'\'']//' | sed 's/["'\'']*$//')
|
||||
if [ -n "$_msg" ]; then
|
||||
COMMIT_MSG="$_msg"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if git is available
|
||||
if ! command -v git >/dev/null 2>&1; then
|
||||
echo "[specify] Warning: Git not found; skipped repository initialization" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if already a git repo
|
||||
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
echo "[specify] Git repository already initialized; skipping" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Initialize
|
||||
_git_out=$(git init -q 2>&1) || { echo "[specify] Error: git init failed: $_git_out" >&2; exit 1; }
|
||||
_git_out=$(git add . 2>&1) || { echo "[specify] Error: git add failed: $_git_out" >&2; exit 1; }
|
||||
_git_out=$(git commit --allow-empty -q -m "$COMMIT_MSG" 2>&1) || { echo "[specify] Error: git commit failed: $_git_out" >&2; exit 1; }
|
||||
|
||||
echo "✓ Git repository initialized" >&2
|
||||
@ -1,169 +0,0 @@
|
||||
#!/usr/bin/env pwsh
|
||||
# Git extension: auto-commit.ps1
|
||||
# Automatically commit changes after a Spec Kit command completes.
|
||||
# Checks per-command config keys in git-config.yml before committing.
|
||||
#
|
||||
# Usage: auto-commit.ps1 <event_name>
|
||||
# e.g.: auto-commit.ps1 after_specify
|
||||
param(
|
||||
[Parameter(Position = 0, Mandatory = $true)]
|
||||
[string]$EventName
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Find-ProjectRoot {
|
||||
param([string]$StartDir)
|
||||
$current = Resolve-Path $StartDir
|
||||
while ($true) {
|
||||
foreach ($marker in @('.specify', '.git')) {
|
||||
if (Test-Path (Join-Path $current $marker)) {
|
||||
return $current
|
||||
}
|
||||
}
|
||||
$parent = Split-Path $current -Parent
|
||||
if ($parent -eq $current) { return $null }
|
||||
$current = $parent
|
||||
}
|
||||
}
|
||||
|
||||
$repoRoot = Find-ProjectRoot -StartDir $PSScriptRoot
|
||||
if (-not $repoRoot) { $repoRoot = Get-Location }
|
||||
Set-Location $repoRoot
|
||||
|
||||
# Check if git is available
|
||||
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
|
||||
Write-Warning "[specify] Warning: Git not found; skipped auto-commit"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Temporarily relax ErrorActionPreference so git stderr warnings
|
||||
# (e.g. CRLF notices on Windows) do not become terminating errors.
|
||||
$savedEAP = $ErrorActionPreference
|
||||
$ErrorActionPreference = 'Continue'
|
||||
try {
|
||||
git rev-parse --is-inside-work-tree 2>$null | Out-Null
|
||||
$isRepo = $LASTEXITCODE -eq 0
|
||||
} finally {
|
||||
$ErrorActionPreference = $savedEAP
|
||||
}
|
||||
if (-not $isRepo) {
|
||||
Write-Warning "[specify] Warning: Not a Git repository; skipped auto-commit"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Read per-command config from git-config.yml
|
||||
$configFile = Join-Path $repoRoot ".specify/extensions/git/git-config.yml"
|
||||
$enabled = $false
|
||||
$commitMsg = ""
|
||||
|
||||
if (Test-Path $configFile) {
|
||||
# Parse YAML to find auto_commit section
|
||||
$inAutoCommit = $false
|
||||
$inEvent = $false
|
||||
$defaultEnabled = $false
|
||||
|
||||
foreach ($line in Get-Content $configFile) {
|
||||
# Detect auto_commit: section
|
||||
if ($line -match '^auto_commit:') {
|
||||
$inAutoCommit = $true
|
||||
$inEvent = $false
|
||||
continue
|
||||
}
|
||||
|
||||
# Exit auto_commit section on next top-level key
|
||||
if ($inAutoCommit -and $line -match '^[a-z]') {
|
||||
break
|
||||
}
|
||||
|
||||
if ($inAutoCommit) {
|
||||
# Check default key
|
||||
if ($line -match '^\s+default:\s*(.+)$') {
|
||||
$val = $matches[1].Trim().ToLower()
|
||||
if ($val -eq 'true') { $defaultEnabled = $true }
|
||||
}
|
||||
|
||||
# Detect our event subsection
|
||||
if ($line -match "^\s+${EventName}:") {
|
||||
$inEvent = $true
|
||||
continue
|
||||
}
|
||||
|
||||
# Inside our event subsection
|
||||
if ($inEvent) {
|
||||
# Exit on next sibling key (2-space indent, not 4+)
|
||||
if ($line -match '^\s{2}[a-z]' -and $line -notmatch '^\s{4}') {
|
||||
$inEvent = $false
|
||||
continue
|
||||
}
|
||||
if ($line -match '\s+enabled:\s*(.+)$') {
|
||||
$val = $matches[1].Trim().ToLower()
|
||||
if ($val -eq 'true') { $enabled = $true }
|
||||
if ($val -eq 'false') { $enabled = $false }
|
||||
}
|
||||
if ($line -match '\s+message:\s*(.+)$') {
|
||||
$commitMsg = $matches[1].Trim() -replace '^["'']' -replace '["'']$'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# If event-specific key not found, use default
|
||||
if (-not $enabled -and $defaultEnabled) {
|
||||
$hasEventKey = Select-String -Path $configFile -Pattern "^\s*${EventName}:" -Quiet
|
||||
if (-not $hasEventKey) {
|
||||
$enabled = $true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
# No config file — auto-commit disabled by default
|
||||
exit 0
|
||||
}
|
||||
|
||||
if (-not $enabled) {
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Check if there are changes to commit
|
||||
# Relax ErrorActionPreference so CRLF warnings on stderr do not terminate.
|
||||
$savedEAP = $ErrorActionPreference
|
||||
$ErrorActionPreference = 'Continue'
|
||||
try {
|
||||
git diff --quiet HEAD 2>$null; $d1 = $LASTEXITCODE
|
||||
git diff --cached --quiet 2>$null; $d2 = $LASTEXITCODE
|
||||
$untracked = git ls-files --others --exclude-standard 2>$null
|
||||
} finally {
|
||||
$ErrorActionPreference = $savedEAP
|
||||
}
|
||||
|
||||
if ($d1 -eq 0 -and $d2 -eq 0 -and -not $untracked) {
|
||||
Write-Host "[specify] No changes to commit after $EventName" -ForegroundColor DarkGray
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Derive a human-readable command name from the event
|
||||
$commandName = $EventName -replace '^after_', '' -replace '^before_', ''
|
||||
$phase = if ($EventName -match '^before_') { 'before' } else { 'after' }
|
||||
|
||||
# Use custom message if configured, otherwise default
|
||||
if (-not $commitMsg) {
|
||||
$commitMsg = "[Spec Kit] Auto-commit $phase $commandName"
|
||||
}
|
||||
|
||||
# Stage and commit
|
||||
# Relax ErrorActionPreference so CRLF warnings on stderr do not terminate,
|
||||
# while still allowing redirected error output to be captured for diagnostics.
|
||||
$savedEAP = $ErrorActionPreference
|
||||
$ErrorActionPreference = 'Continue'
|
||||
try {
|
||||
$out = git add . 2>&1 | Out-String
|
||||
if ($LASTEXITCODE -ne 0) { throw "git add failed: $out" }
|
||||
$out = git commit -q -m $commitMsg 2>&1 | Out-String
|
||||
if ($LASTEXITCODE -ne 0) { throw "git commit failed: $out" }
|
||||
} catch {
|
||||
Write-Warning "[specify] Error: $_"
|
||||
exit 1
|
||||
} finally {
|
||||
$ErrorActionPreference = $savedEAP
|
||||
}
|
||||
|
||||
Write-Host "[OK] Changes committed $phase $commandName"
|
||||
@ -1,403 +0,0 @@
|
||||
#!/usr/bin/env pwsh
|
||||
# Git extension: create-new-feature.ps1
|
||||
# Adapted from core scripts/powershell/create-new-feature.ps1 for extension layout.
|
||||
# Sources common.ps1 from the project's installed scripts, falling back to
|
||||
# git-common.ps1 for minimal git helpers.
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$Json,
|
||||
[switch]$AllowExistingBranch,
|
||||
[switch]$DryRun,
|
||||
[string]$ShortName,
|
||||
[Parameter()]
|
||||
[long]$Number = 0,
|
||||
[switch]$Timestamp,
|
||||
[switch]$Help,
|
||||
[Parameter(Position = 0, ValueFromRemainingArguments = $true)]
|
||||
[string[]]$FeatureDescription
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
if ($Help) {
|
||||
Write-Host "Usage: ./create-new-feature.ps1 [-Json] [-DryRun] [-AllowExistingBranch] [-ShortName <name>] [-Number N] [-Timestamp] <feature description>"
|
||||
Write-Host ""
|
||||
Write-Host "Options:"
|
||||
Write-Host " -Json Output in JSON format"
|
||||
Write-Host " -DryRun Compute branch name without creating the branch"
|
||||
Write-Host " -AllowExistingBranch Switch to branch if it already exists instead of failing"
|
||||
Write-Host " -ShortName <name> Provide a custom short name (2-4 words) for the branch"
|
||||
Write-Host " -Number N Specify branch number manually (overrides auto-detection)"
|
||||
Write-Host " -Timestamp Use timestamp prefix (YYYYMMDD-HHMMSS) instead of sequential numbering"
|
||||
Write-Host " -Help Show this help message"
|
||||
Write-Host ""
|
||||
Write-Host "Environment variables:"
|
||||
Write-Host " GIT_BRANCH_NAME Use this exact branch name, bypassing all prefix/suffix generation"
|
||||
Write-Host ""
|
||||
exit 0
|
||||
}
|
||||
|
||||
if (-not $FeatureDescription -or $FeatureDescription.Count -eq 0) {
|
||||
Write-Error "Usage: ./create-new-feature.ps1 [-Json] [-DryRun] [-AllowExistingBranch] [-ShortName <name>] [-Number N] [-Timestamp] <feature description>"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$featureDesc = ($FeatureDescription -join ' ').Trim()
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($featureDesc)) {
|
||||
Write-Error "Error: Feature description cannot be empty or contain only whitespace"
|
||||
exit 1
|
||||
}
|
||||
|
||||
function Get-HighestNumberFromSpecs {
|
||||
param([string]$SpecsDir)
|
||||
|
||||
[long]$highest = 0
|
||||
if (Test-Path $SpecsDir) {
|
||||
Get-ChildItem -Path $SpecsDir -Directory | ForEach-Object {
|
||||
if ($_.Name -match '^(\d{3,})-' -and $_.Name -notmatch '^\d{8}-\d{6}-') {
|
||||
[long]$num = 0
|
||||
if ([long]::TryParse($matches[1], [ref]$num) -and $num -gt $highest) {
|
||||
$highest = $num
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $highest
|
||||
}
|
||||
|
||||
function Get-HighestNumberFromNames {
|
||||
param([string[]]$Names)
|
||||
|
||||
[long]$highest = 0
|
||||
foreach ($name in $Names) {
|
||||
if ($name -match '^(\d{3,})-' -and $name -notmatch '^\d{8}-\d{6}-') {
|
||||
[long]$num = 0
|
||||
if ([long]::TryParse($matches[1], [ref]$num) -and $num -gt $highest) {
|
||||
$highest = $num
|
||||
}
|
||||
}
|
||||
}
|
||||
return $highest
|
||||
}
|
||||
|
||||
function Get-HighestNumberFromBranches {
|
||||
param()
|
||||
|
||||
try {
|
||||
$branches = git branch -a 2>$null
|
||||
if ($LASTEXITCODE -eq 0 -and $branches) {
|
||||
$cleanNames = $branches | ForEach-Object {
|
||||
$_.Trim() -replace '^\*?\s+', '' -replace '^remotes/[^/]+/', ''
|
||||
}
|
||||
return Get-HighestNumberFromNames -Names $cleanNames
|
||||
}
|
||||
} catch {
|
||||
Write-Verbose "Could not check Git branches: $_"
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function Get-HighestNumberFromRemoteRefs {
|
||||
[long]$highest = 0
|
||||
try {
|
||||
$remotes = git remote 2>$null
|
||||
if ($remotes) {
|
||||
foreach ($remote in $remotes) {
|
||||
$env:GIT_TERMINAL_PROMPT = '0'
|
||||
$refs = git ls-remote --heads $remote 2>$null
|
||||
$env:GIT_TERMINAL_PROMPT = $null
|
||||
if ($LASTEXITCODE -eq 0 -and $refs) {
|
||||
$refNames = $refs | ForEach-Object {
|
||||
if ($_ -match 'refs/heads/(.+)$') { $matches[1] }
|
||||
} | Where-Object { $_ }
|
||||
$remoteHighest = Get-HighestNumberFromNames -Names $refNames
|
||||
if ($remoteHighest -gt $highest) { $highest = $remoteHighest }
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Write-Verbose "Could not query remote refs: $_"
|
||||
}
|
||||
return $highest
|
||||
}
|
||||
|
||||
function Get-NextBranchNumber {
|
||||
param(
|
||||
[string]$SpecsDir,
|
||||
[switch]$SkipFetch
|
||||
)
|
||||
|
||||
if ($SkipFetch) {
|
||||
$highestBranch = Get-HighestNumberFromBranches
|
||||
$highestRemote = Get-HighestNumberFromRemoteRefs
|
||||
$highestBranch = [Math]::Max($highestBranch, $highestRemote)
|
||||
} else {
|
||||
try {
|
||||
git fetch --all --prune 2>$null | Out-Null
|
||||
} catch { }
|
||||
$highestBranch = Get-HighestNumberFromBranches
|
||||
}
|
||||
|
||||
$highestSpec = Get-HighestNumberFromSpecs -SpecsDir $SpecsDir
|
||||
$maxNum = [Math]::Max($highestBranch, $highestSpec)
|
||||
return $maxNum + 1
|
||||
}
|
||||
|
||||
function ConvertTo-CleanBranchName {
|
||||
param([string]$Name)
|
||||
return $Name.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', ''
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Source common.ps1 from the project's installed scripts.
|
||||
# Search locations in priority order:
|
||||
# 1. .specify/scripts/powershell/common.ps1 under the project root
|
||||
# 2. scripts/powershell/common.ps1 under the project root (source checkout)
|
||||
# 3. git-common.ps1 next to this script (minimal fallback)
|
||||
# ---------------------------------------------------------------------------
|
||||
function Find-ProjectRoot {
|
||||
param([string]$StartDir)
|
||||
$current = Resolve-Path $StartDir
|
||||
while ($true) {
|
||||
foreach ($marker in @('.specify', '.git')) {
|
||||
if (Test-Path (Join-Path $current $marker)) {
|
||||
return $current
|
||||
}
|
||||
}
|
||||
$parent = Split-Path $current -Parent
|
||||
if ($parent -eq $current) { return $null }
|
||||
$current = $parent
|
||||
}
|
||||
}
|
||||
|
||||
$projectRoot = Find-ProjectRoot -StartDir $PSScriptRoot
|
||||
$commonLoaded = $false
|
||||
|
||||
if ($projectRoot) {
|
||||
$candidates = @(
|
||||
(Join-Path $projectRoot ".specify/scripts/powershell/common.ps1"),
|
||||
(Join-Path $projectRoot "scripts/powershell/common.ps1")
|
||||
)
|
||||
foreach ($candidate in $candidates) {
|
||||
if (Test-Path $candidate) {
|
||||
. $candidate
|
||||
$commonLoaded = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $commonLoaded -and (Test-Path "$PSScriptRoot/git-common.ps1")) {
|
||||
. "$PSScriptRoot/git-common.ps1"
|
||||
$commonLoaded = $true
|
||||
}
|
||||
|
||||
if (-not $commonLoaded) {
|
||||
throw "Unable to locate common script file. Please ensure the Specify core scripts are installed."
|
||||
}
|
||||
|
||||
# Resolve repository root
|
||||
if (Get-Command Get-RepoRoot -ErrorAction SilentlyContinue) {
|
||||
$repoRoot = Get-RepoRoot
|
||||
} elseif ($projectRoot) {
|
||||
$repoRoot = $projectRoot
|
||||
} else {
|
||||
throw "Could not determine repository root."
|
||||
}
|
||||
|
||||
# Check if git is available
|
||||
if (Get-Command Test-HasGit -ErrorAction SilentlyContinue) {
|
||||
# Call without parameters for compatibility with core common.ps1 (no -RepoRoot param)
|
||||
# and git-common.ps1 (has -RepoRoot param with default).
|
||||
$hasGit = Test-HasGit
|
||||
} else {
|
||||
try {
|
||||
git -C $repoRoot rev-parse --is-inside-work-tree 2>$null | Out-Null
|
||||
$hasGit = ($LASTEXITCODE -eq 0)
|
||||
} catch {
|
||||
$hasGit = $false
|
||||
}
|
||||
}
|
||||
|
||||
Set-Location $repoRoot
|
||||
|
||||
$specsDir = Join-Path $repoRoot 'specs'
|
||||
|
||||
function Get-BranchName {
|
||||
param([string]$Description)
|
||||
|
||||
$stopWords = @(
|
||||
'i', 'a', 'an', 'the', 'to', 'for', 'of', 'in', 'on', 'at', 'by', 'with', 'from',
|
||||
'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had',
|
||||
'do', 'does', 'did', 'will', 'would', 'should', 'could', 'can', 'may', 'might', 'must', 'shall',
|
||||
'this', 'that', 'these', 'those', 'my', 'your', 'our', 'their',
|
||||
'want', 'need', 'add', 'get', 'set'
|
||||
)
|
||||
|
||||
$cleanName = $Description.ToLower() -replace '[^a-z0-9\s]', ' '
|
||||
$words = $cleanName -split '\s+' | Where-Object { $_ }
|
||||
|
||||
$meaningfulWords = @()
|
||||
foreach ($word in $words) {
|
||||
if ($stopWords -contains $word) { continue }
|
||||
if ($word.Length -ge 3) {
|
||||
$meaningfulWords += $word
|
||||
} elseif ($Description -match "\b$($word.ToUpper())\b") {
|
||||
$meaningfulWords += $word
|
||||
}
|
||||
}
|
||||
|
||||
if ($meaningfulWords.Count -gt 0) {
|
||||
$maxWords = if ($meaningfulWords.Count -eq 4) { 4 } else { 3 }
|
||||
$result = ($meaningfulWords | Select-Object -First $maxWords) -join '-'
|
||||
return $result
|
||||
} else {
|
||||
$result = ConvertTo-CleanBranchName -Name $Description
|
||||
$fallbackWords = ($result -split '-') | Where-Object { $_ } | Select-Object -First 3
|
||||
return [string]::Join('-', $fallbackWords)
|
||||
}
|
||||
}
|
||||
|
||||
# Check for GIT_BRANCH_NAME env var override (exact branch name, no prefix/suffix)
|
||||
if ($env:GIT_BRANCH_NAME) {
|
||||
$branchName = $env:GIT_BRANCH_NAME
|
||||
# Check 244-byte limit (UTF-8) for override names
|
||||
$branchNameUtf8ByteCount = [System.Text.Encoding]::UTF8.GetByteCount($branchName)
|
||||
if ($branchNameUtf8ByteCount -gt 244) {
|
||||
throw "GIT_BRANCH_NAME must be 244 bytes or fewer in UTF-8. Provided value is $branchNameUtf8ByteCount bytes; please supply a shorter override branch name."
|
||||
}
|
||||
# Extract FEATURE_NUM from the branch name if it starts with a numeric prefix
|
||||
# Check timestamp pattern first (YYYYMMDD-HHMMSS-) since it also matches the simpler ^\d+ pattern
|
||||
if ($branchName -match '^(\d{8}-\d{6})-') {
|
||||
$featureNum = $matches[1]
|
||||
} elseif ($branchName -match '^(\d+)-') {
|
||||
$featureNum = $matches[1]
|
||||
} else {
|
||||
$featureNum = $branchName
|
||||
}
|
||||
} else {
|
||||
if ($ShortName) {
|
||||
$branchSuffix = ConvertTo-CleanBranchName -Name $ShortName
|
||||
} else {
|
||||
$branchSuffix = Get-BranchName -Description $featureDesc
|
||||
}
|
||||
|
||||
if ($Timestamp -and $Number -ne 0) {
|
||||
Write-Warning "[specify] Warning: -Number is ignored when -Timestamp is used"
|
||||
$Number = 0
|
||||
}
|
||||
|
||||
if ($Timestamp) {
|
||||
$featureNum = Get-Date -Format 'yyyyMMdd-HHmmss'
|
||||
$branchName = "$featureNum-$branchSuffix"
|
||||
} else {
|
||||
if ($Number -eq 0) {
|
||||
if ($DryRun -and $hasGit) {
|
||||
$Number = Get-NextBranchNumber -SpecsDir $specsDir -SkipFetch
|
||||
} elseif ($DryRun) {
|
||||
$Number = (Get-HighestNumberFromSpecs -SpecsDir $specsDir) + 1
|
||||
} elseif ($hasGit) {
|
||||
$Number = Get-NextBranchNumber -SpecsDir $specsDir
|
||||
} else {
|
||||
$Number = (Get-HighestNumberFromSpecs -SpecsDir $specsDir) + 1
|
||||
}
|
||||
}
|
||||
|
||||
$featureNum = ('{0:000}' -f $Number)
|
||||
$branchName = "$featureNum-$branchSuffix"
|
||||
}
|
||||
}
|
||||
|
||||
$maxBranchLength = 244
|
||||
if ($branchName.Length -gt $maxBranchLength) {
|
||||
$prefixLength = $featureNum.Length + 1
|
||||
$maxSuffixLength = $maxBranchLength - $prefixLength
|
||||
|
||||
$truncatedSuffix = $branchSuffix.Substring(0, [Math]::Min($branchSuffix.Length, $maxSuffixLength))
|
||||
$truncatedSuffix = $truncatedSuffix -replace '-$', ''
|
||||
|
||||
$originalBranchName = $branchName
|
||||
$branchName = "$featureNum-$truncatedSuffix"
|
||||
|
||||
Write-Warning "[specify] Branch name exceeded GitHub's 244-byte limit"
|
||||
Write-Warning "[specify] Original: $originalBranchName ($($originalBranchName.Length) bytes)"
|
||||
Write-Warning "[specify] Truncated to: $branchName ($($branchName.Length) bytes)"
|
||||
}
|
||||
|
||||
if (-not $DryRun) {
|
||||
if ($hasGit) {
|
||||
$branchCreated = $false
|
||||
$branchCreateError = ''
|
||||
try {
|
||||
$branchCreateError = git checkout -q -b $branchName 2>&1 | Out-String
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
$branchCreated = $true
|
||||
}
|
||||
} catch {
|
||||
$branchCreateError = $_.Exception.Message
|
||||
}
|
||||
|
||||
if (-not $branchCreated) {
|
||||
$currentBranch = ''
|
||||
try { $currentBranch = (git rev-parse --abbrev-ref HEAD 2>$null).Trim() } catch {}
|
||||
$existingBranch = git branch --list $branchName 2>$null
|
||||
if ($existingBranch) {
|
||||
if ($AllowExistingBranch) {
|
||||
if ($currentBranch -eq $branchName) {
|
||||
# Already on the target branch
|
||||
} else {
|
||||
$switchBranchError = git checkout -q $branchName 2>&1 | Out-String
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
if ($switchBranchError) {
|
||||
Write-Error "Error: Branch '$branchName' exists but could not be checked out.`n$($switchBranchError.Trim())"
|
||||
} else {
|
||||
Write-Error "Error: Branch '$branchName' exists but could not be checked out. Resolve any uncommitted changes or conflicts and try again."
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
} elseif ($Timestamp) {
|
||||
Write-Error "Error: Branch '$branchName' already exists. Rerun to get a new timestamp or use a different -ShortName."
|
||||
exit 1
|
||||
} else {
|
||||
Write-Error "Error: Branch '$branchName' already exists. Please use a different feature name or specify a different number with -Number."
|
||||
exit 1
|
||||
}
|
||||
} else {
|
||||
if ($branchCreateError) {
|
||||
Write-Error "Error: Failed to create git branch '$branchName'.`n$($branchCreateError.Trim())"
|
||||
} else {
|
||||
Write-Error "Error: Failed to create git branch '$branchName'. Please check your git configuration and try again."
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($Json) {
|
||||
[Console]::Error.WriteLine("[specify] Warning: Git repository not detected; skipped branch creation for $branchName")
|
||||
} else {
|
||||
Write-Warning "[specify] Warning: Git repository not detected; skipped branch creation for $branchName"
|
||||
}
|
||||
}
|
||||
|
||||
$env:SPECIFY_FEATURE = $branchName
|
||||
}
|
||||
|
||||
if ($Json) {
|
||||
$obj = [PSCustomObject]@{
|
||||
BRANCH_NAME = $branchName
|
||||
FEATURE_NUM = $featureNum
|
||||
HAS_GIT = $hasGit
|
||||
}
|
||||
if ($DryRun) {
|
||||
$obj | Add-Member -NotePropertyName 'DRY_RUN' -NotePropertyValue $true
|
||||
}
|
||||
$obj | ConvertTo-Json -Compress
|
||||
} else {
|
||||
Write-Output "BRANCH_NAME: $branchName"
|
||||
Write-Output "FEATURE_NUM: $featureNum"
|
||||
Write-Output "HAS_GIT: $hasGit"
|
||||
if (-not $DryRun) {
|
||||
Write-Output "SPECIFY_FEATURE environment variable set to: $branchName"
|
||||
}
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
#!/usr/bin/env pwsh
|
||||
# Git-specific common functions for the git extension.
|
||||
# Extracted from scripts/powershell/common.ps1 — contains only git-specific
|
||||
# branch validation and detection logic.
|
||||
|
||||
function Test-HasGit {
|
||||
param([string]$RepoRoot = (Get-Location))
|
||||
try {
|
||||
if (-not (Test-Path (Join-Path $RepoRoot '.git'))) { return $false }
|
||||
if (-not (Get-Command git -ErrorAction SilentlyContinue)) { return $false }
|
||||
git -C $RepoRoot rev-parse --is-inside-work-tree 2>$null | Out-Null
|
||||
return ($LASTEXITCODE -eq 0)
|
||||
} catch {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Get-SpecKitEffectiveBranchName {
|
||||
param([string]$Branch)
|
||||
if ($Branch -match '^([^/]+)/([^/]+)$') {
|
||||
return $Matches[2]
|
||||
}
|
||||
return $Branch
|
||||
}
|
||||
|
||||
function Test-FeatureBranch {
|
||||
param(
|
||||
[string]$Branch,
|
||||
[bool]$HasGit = $true
|
||||
)
|
||||
|
||||
# For non-git repos, we can't enforce branch naming but still provide output
|
||||
if (-not $HasGit) {
|
||||
Write-Warning "[specify] Warning: Git repository not detected; skipped branch validation"
|
||||
return $true
|
||||
}
|
||||
|
||||
$raw = $Branch
|
||||
$Branch = Get-SpecKitEffectiveBranchName $raw
|
||||
|
||||
# Accept sequential prefix (3+ digits) but exclude malformed timestamps
|
||||
# Malformed: 7-or-8 digit date + 6-digit time with no trailing slug (e.g. "2026031-143022" or "20260319-143022")
|
||||
$hasMalformedTimestamp = ($Branch -match '^[0-9]{7}-[0-9]{6}-') -or ($Branch -match '^(?:\d{7}|\d{8})-\d{6}$')
|
||||
$isSequential = ($Branch -match '^[0-9]{3,}-') -and (-not $hasMalformedTimestamp)
|
||||
if (-not $isSequential -and $Branch -notmatch '^\d{8}-\d{6}-') {
|
||||
[Console]::Error.WriteLine("ERROR: Not on a feature branch. Current branch: $raw")
|
||||
[Console]::Error.WriteLine("Feature branches should be named like: 001-feature-name, 1234-feature-name, or 20260319-143022-feature-name")
|
||||
return $false
|
||||
}
|
||||
return $true
|
||||
}
|
||||
@ -1,69 +0,0 @@
|
||||
#!/usr/bin/env pwsh
|
||||
# Git extension: initialize-repo.ps1
|
||||
# Initialize a Git repository with an initial commit.
|
||||
# Customizable — replace this script to add .gitignore templates,
|
||||
# default branch config, git-flow, LFS, signing, etc.
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Find project root
|
||||
function Find-ProjectRoot {
|
||||
param([string]$StartDir)
|
||||
$current = Resolve-Path $StartDir
|
||||
while ($true) {
|
||||
foreach ($marker in @('.specify', '.git')) {
|
||||
if (Test-Path (Join-Path $current $marker)) {
|
||||
return $current
|
||||
}
|
||||
}
|
||||
$parent = Split-Path $current -Parent
|
||||
if ($parent -eq $current) { return $null }
|
||||
$current = $parent
|
||||
}
|
||||
}
|
||||
|
||||
$repoRoot = Find-ProjectRoot -StartDir $PSScriptRoot
|
||||
if (-not $repoRoot) { $repoRoot = Get-Location }
|
||||
Set-Location $repoRoot
|
||||
|
||||
# Read commit message from extension config, fall back to default
|
||||
$commitMsg = "[Spec Kit] Initial commit"
|
||||
$configFile = Join-Path $repoRoot ".specify/extensions/git/git-config.yml"
|
||||
if (Test-Path $configFile) {
|
||||
foreach ($line in Get-Content $configFile) {
|
||||
if ($line -match '^init_commit_message:\s*(.+)$') {
|
||||
$val = $matches[1].Trim() -replace '^["'']' -replace '["'']$'
|
||||
if ($val) { $commitMsg = $val }
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check if git is available
|
||||
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
|
||||
Write-Warning "[specify] Warning: Git not found; skipped repository initialization"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Check if already a git repo
|
||||
try {
|
||||
git rev-parse --is-inside-work-tree 2>$null | Out-Null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Warning "[specify] Git repository already initialized; skipping"
|
||||
exit 0
|
||||
}
|
||||
} catch { }
|
||||
|
||||
# Initialize
|
||||
try {
|
||||
$out = git init -q 2>&1 | Out-String
|
||||
if ($LASTEXITCODE -ne 0) { throw "git init failed: $out" }
|
||||
$out = git add . 2>&1 | Out-String
|
||||
if ($LASTEXITCODE -ne 0) { throw "git add failed: $out" }
|
||||
$out = git commit --allow-empty -q -m $commitMsg 2>&1 | Out-String
|
||||
if ($LASTEXITCODE -ne 0) { throw "git commit failed: $out" }
|
||||
} catch {
|
||||
Write-Warning "[specify] Error: $_"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "✓ Git repository initialized"
|
||||
@ -1,10 +0,0 @@
|
||||
{
|
||||
"ai": "copilot",
|
||||
"branch_numbering": "sequential",
|
||||
"context_file": ".github/copilot-instructions.md",
|
||||
"here": true,
|
||||
"integration": "copilot",
|
||||
"preset": null,
|
||||
"script": "sh",
|
||||
"speckit_version": "0.7.4"
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
{
|
||||
"integration": "copilot",
|
||||
"version": "0.7.4"
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
{
|
||||
"integration": "copilot",
|
||||
"version": "0.7.4",
|
||||
"installed_at": "2026-04-22T21:58:02.962169+00:00",
|
||||
"files": {
|
||||
".github/agents/speckit.analyze.agent.md": "699032fdd49afe31d23c7191f3fe7bcb1d14b081fbc94c2287e6ba3a57574fda",
|
||||
".github/agents/speckit.checklist.agent.md": "d7d691689fe45427c868dcf18ade4df500f0c742a6c91923fefba405d6466dde",
|
||||
".github/agents/speckit.clarify.agent.md": "0cc766dcc5cab233ccdf3bc4cfb5759a6d7d1e13e29f611083046f818f5812bb",
|
||||
".github/agents/speckit.constitution.agent.md": "58d35eb026f56bb7364d91b8b0382d5dd1249ded6c1449a2b69546693afb85f7",
|
||||
".github/agents/speckit.implement.agent.md": "83628415c86ba487b3a083c7a2c0f016c9073abd02c1c7f4a30cff949b6602c0",
|
||||
".github/agents/speckit.plan.agent.md": "2ad128b81ccd8f5bfa78b3b43101f377dfddd8f800fa0856f85bf53b1489b783",
|
||||
".github/agents/speckit.specify.agent.md": "5bbb5270836cc9a3286ce3ed96a500f3d383a54abb06aa11b01a2d2f76dbf39b",
|
||||
".github/agents/speckit.tasks.agent.md": "a58886f29f75e1a14840007772ddd954742aafb3e03d9d1231bee033e6c1626b",
|
||||
".github/agents/speckit.taskstoissues.agent.md": "e84794f7a839126defb364ca815352c5c2b2d20db2d6da399fa53e4ddbb7b3ee",
|
||||
".github/prompts/speckit.analyze.prompt.md": "bb93dbbafa96d07b7cd07fc7061d8adb0c6b26cb772a52d0dce263b1ca2b9b77",
|
||||
".github/prompts/speckit.checklist.prompt.md": "c3aea7526c5cbfd8665acc9508ad5a9a3f71e91a63c36be7bed13a834c3a683c",
|
||||
".github/prompts/speckit.clarify.prompt.md": "ce79b3437ca918d46ac858eb4b8b44d3b0a02c563660c60d94c922a7b5d8d4f4",
|
||||
".github/prompts/speckit.constitution.prompt.md": "38f937279de14387601422ddfda48365debdbaf47b2d513527b8f6d8a27d499d",
|
||||
".github/prompts/speckit.implement.prompt.md": "5053a17fb9238338c63b898ee9c80b2cb4ad1a90c6071fe3748de76864ac6a80",
|
||||
".github/prompts/speckit.plan.prompt.md": "2098dae6bd9277335f31cb150b78bfb1de539c0491798e5cfe382c89ab0bcd0e",
|
||||
".github/prompts/speckit.specify.prompt.md": "7b2cc4dc6462da1c96df46bac4f60e53baba3097f4b24ac3f9b684194458aa98",
|
||||
".github/prompts/speckit.tasks.prompt.md": "88fc57c289f99d5e9d35c255f3e2683f73ecb0a5155dcb4d886f82f52b11841f",
|
||||
".github/prompts/speckit.taskstoissues.prompt.md": "2f9636d4f312a1470f000747cb62677fec0655d8b4e2357fa4fbf238965fa66d"
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
{
|
||||
"integration": "speckit",
|
||||
"version": "0.7.4",
|
||||
"installed_at": "2026-04-22T21:58:02.965809+00:00",
|
||||
"files": {
|
||||
".specify/templates/constitution-template.md": "ce7549540fa45543cca797a150201d868e64495fdff39dc38246fb17bd4024b3"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,5 @@
|
||||
# Research T186 — settings_apply capability verification (LEGACY / DEPRECATED)
|
||||
|
||||
> **Status:** Superseded
|
||||
> **Last reviewed:** 2026-04-30
|
||||
> **Use for:** Historical investigation context only if a later Settings Catalog write-path regression needs provenance
|
||||
> **Do not use for:** Active feature research or current implementation truth
|
||||
|
||||
> DEPRECATED: Do not add new research notes under `.specify/`.
|
||||
> Active feature research should live under `specs/<NNN>-<slug>/`.
|
||||
> Legacy history lives under `spechistory/`.
|
||||
|
||||
@ -40,13 +40,9 @@ mkdir -p "$FEATURE_DIR"
|
||||
TEMPLATE="$REPO_ROOT/.specify/templates/plan-template.md"
|
||||
if [[ -f "$TEMPLATE" ]]; then
|
||||
cp "$TEMPLATE" "$IMPL_PLAN"
|
||||
if ! $JSON_MODE; then
|
||||
echo "Copied plan template to $IMPL_PLAN"
|
||||
fi
|
||||
echo "Copied plan template to $IMPL_PLAN"
|
||||
else
|
||||
if ! $JSON_MODE; then
|
||||
echo "Warning: Plan template not found at $TEMPLATE"
|
||||
fi
|
||||
echo "Warning: Plan template not found at $TEMPLATE"
|
||||
# Create a basic plan file if template doesn't exist
|
||||
touch "$IMPL_PLAN"
|
||||
fi
|
||||
|
||||
@ -5,79 +5,36 @@ # [CHECKLIST TYPE] Checklist: [FEATURE NAME]
|
||||
**Feature**: [Link to spec.md or relevant documentation]
|
||||
|
||||
**Note**: This checklist is generated by the `/speckit.checklist` command based on feature context and requirements.
|
||||
If the checklist covers UI or surface work, use it to reach both one review
|
||||
outcome class (`blocker`, `strong-warning`,
|
||||
`documentation-required-exception`, or `acceptable-special-case`) and one
|
||||
workflow outcome (`keep`, `split`, `document-in-feature`,
|
||||
`follow-up-spec`, or `reject-or-split`). Low-impact docs-only or
|
||||
template-only work may mark runtime-only checks `N/A`, but should still
|
||||
leave one explicit workflow outcome and one note explaining why no
|
||||
guardrail spread exists.
|
||||
|
||||
## Applicability And Low-Impact Gate
|
||||
<!--
|
||||
============================================================================
|
||||
IMPORTANT: The checklist items below are SAMPLE ITEMS for illustration only.
|
||||
|
||||
The /speckit.checklist command MUST replace these with actual items based on:
|
||||
- User's specific checklist request
|
||||
- Feature requirements from spec.md
|
||||
- Technical context from plan.md
|
||||
- Implementation details from tasks.md
|
||||
|
||||
DO NOT keep these sample items in the generated checklist file.
|
||||
============================================================================
|
||||
-->
|
||||
|
||||
- [ ] CHK001 The change explicitly says whether an operator-facing surface or guardrail workflow surface is affected; low-impact `N/A` handling is used once and not contradicted elsewhere.
|
||||
- [ ] CHK002 The spec, plan, and task artifacts carry forward the same native/custom classification, shared-family relevance, state-layer ownership, and exception need without inventing second wording.
|
||||
## [Category 1]
|
||||
|
||||
## Native, Shared-Family, And State Ownership
|
||||
- [ ] CHK001 First checklist item with clear action
|
||||
- [ ] CHK002 Second checklist item
|
||||
- [ ] CHK003 Third checklist item
|
||||
|
||||
- [ ] CHK003 The surface remains native/shared-primitives first; fake-native controls, GET-form page-body interactions, and simple-overview replacements are not treated as harmless customization.
|
||||
- [ ] CHK004 Any shared-detail or shared-family surface keeps one shared contract, and any host variation is either folded back into that contract or explicitly bounded as an exception.
|
||||
- [ ] CHK005 Shell, page, detail, and URL/query state owners are named once and do not collapse into one another.
|
||||
- [ ] CHK006 The likely next operator action and the primary inspect/open model stay coherent with the declared surface class.
|
||||
## [Category 2]
|
||||
|
||||
## Shared Pattern Reuse
|
||||
|
||||
- [ ] CHK007 Any cross-cutting interaction class is explicitly marked, and the existing shared contract/presenter/builder/renderer path is named once.
|
||||
- [ ] CHK008 The change extends the shared path where it is sufficient, or the deviation is explicitly documented with product reason, preserved consistency, ownership cost, and spread-control.
|
||||
- [ ] CHK009 The change does not create a parallel operator-facing UX language for the same interaction class unless a bounded exception is recorded.
|
||||
|
||||
## OperationRun Start UX Contract
|
||||
|
||||
- [ ] CHK019 The change explicitly says whether it creates, queues, deduplicates, resumes, blocks, completes, or deep-links to an `OperationRun`, and the required `OperationRun UX Impact` section exists when applicable.
|
||||
- [ ] CHK020 Queued toast/link/artifact-link/browser-event/dedupe-or-blocked messaging and tenant/workspace-safe operation URL resolution are delegated to the shared OperationRun UX contract instead of local surface code.
|
||||
- [ ] CHK021 Any queued DB notification is explicit opt-in in the active spec or plan, running DB notifications remain absent, and terminal notifications still flow through the central lifecycle mechanism.
|
||||
- [ ] CHK022 Any exception records the explicit spec decision, architecture note, test or guard-test rationale, and temporary migration follow-up decision.
|
||||
|
||||
## Provider Boundary And Vocabulary
|
||||
|
||||
- [ ] CHK010 The change states whether any touched shared seam is provider-owned, platform-core, or mixed, and provider-specific semantics do not silently spread into platform-core contracts, taxonomy, identifiers, compare semantics, or operator vocabulary.
|
||||
- [ ] CHK011 Any retained provider-specific shared boundary is justified as a bounded current-release exception or an explicit follow-up-spec need instead of becoming permanent platform truth by default.
|
||||
|
||||
## Signals, Exceptions, And Test Depth
|
||||
|
||||
- [ ] CHK012 Any triggered repository signal is classified with one handling mode: `hard-stop-candidate`, `review-mandatory`, `exception-required`, or `report-only`.
|
||||
- [ ] CHK013 Any deviation from default rules includes a bounded exception record naming the broken rule, product reason, standardized parts, spread-control rule, and the active feature PR close-out entry.
|
||||
- [ ] CHK014 The required surface test profile is explicit: `shared-detail-family`, `monitoring-state-page`, `global-context-shell`, `exception-coded-surface`, or `standard-native-filament`.
|
||||
- [ ] CHK015 The chosen test family/lane and any manual smoke are the narrowest honest proof for the declared surface class, and `standard-native-filament` relief is used when no special contract exists.
|
||||
|
||||
## Audience-Aware Disclosure And Decision Hierarchy
|
||||
|
||||
- [ ] CHK023 Default-visible content is decision-first and clearly separated from operator diagnostics and support/raw evidence.
|
||||
- [ ] CHK024 Customer/read-only default paths do not expose raw JSON, copied context payloads, fingerprints, internal reason ownership, platform reason families, monitoring detail, or other debug semantics by default.
|
||||
- [ ] CHK025 Exactly one dominant next action is primary; navigation or debug helpers such as `Open operation`, `Technical details`, or `Show JSON` do not compete at equal weight.
|
||||
- [ ] CHK026 Duplicate visible status, blocker, reason, impact, or next-action summaries are removed or explicitly justified as non-duplicative evidence.
|
||||
- [ ] CHK027 Support/raw sections are collapsed, lower-priority, or capability-gated where applicable, and any local Blade/Tailwind surface still preserves Filament visual language, dark mode correctness, progressive disclosure, and accessibility.
|
||||
|
||||
## Review Outcome
|
||||
|
||||
- [ ] CHK016 One review outcome class is chosen: `blocker`, `strong-warning`, `documentation-required-exception`, or `acceptable-special-case`.
|
||||
- [ ] CHK017 One workflow outcome is chosen: `keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split`.
|
||||
- [ ] CHK018 The final note location is explicit: the active feature PR close-out entry for guarded work, or a concise `N/A` note for low-impact changes.
|
||||
- [ ] CHK004 Another category item
|
||||
- [ ] CHK005 Item with specific criteria
|
||||
- [ ] CHK006 Final item in this category
|
||||
|
||||
## Notes
|
||||
|
||||
- `blocker`: the change conflicts with the declared surface contract or guardrail and cannot proceed as proposed.
|
||||
- `strong-warning`: the change may proceed only after the active workflow records the remaining guardrail risk explicitly.
|
||||
- `documentation-required-exception`: the change is valid only once a bounded exception and close-out note exist.
|
||||
- `acceptable-special-case`: the change is legitimate without extra escalation beyond ordinary documentation.
|
||||
- `keep`: the current scope, guardrail handling, and proof depth are justified.
|
||||
- `split`: the intent is valid, but the scope should narrow before merge.
|
||||
- `document-in-feature`: the change is acceptable, but the active feature must record the exception, signal handling, or proof notes explicitly.
|
||||
- `follow-up-spec`: the issue is recurring or structural and needs dedicated governance follow-up. For already-implemented historical drift, prefer a follow-up spec or active feature note instead of retroactively rewriting closed specs.
|
||||
- `reject-or-split`: hidden drift, unresolved exception spread, or wrong proof depth blocks merge as proposed.
|
||||
- Check items off as completed: `[x]`
|
||||
- Add comments or findings inline
|
||||
- Link to relevant resources or documentation
|
||||
- Items are numbered sequentially for easy reference
|
||||
- Reviewer-facing checklists SHOULD stop merge when nativity, shared-family boundaries, state ownership, exception spread, test depth, or escalation handling is unclear.
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
# [PROJECT_NAME] Constitution
|
||||
<!-- Example: Spec Constitution, TaskFlow Constitution, etc. -->
|
||||
|
||||
## Core Principles
|
||||
|
||||
### [PRINCIPLE_1_NAME]
|
||||
<!-- Example: I. Library-First -->
|
||||
[PRINCIPLE_1_DESCRIPTION]
|
||||
<!-- Example: Every feature starts as a standalone library; Libraries must be self-contained, independently testable, documented; Clear purpose required - no organizational-only libraries -->
|
||||
|
||||
### [PRINCIPLE_2_NAME]
|
||||
<!-- Example: II. CLI Interface -->
|
||||
[PRINCIPLE_2_DESCRIPTION]
|
||||
<!-- Example: Every library exposes functionality via CLI; Text in/out protocol: stdin/args → stdout, errors → stderr; Support JSON + human-readable formats -->
|
||||
|
||||
### [PRINCIPLE_3_NAME]
|
||||
<!-- Example: III. Test-First (NON-NEGOTIABLE) -->
|
||||
[PRINCIPLE_3_DESCRIPTION]
|
||||
<!-- Example: TDD mandatory: Tests written → User approved → Tests fail → Then implement; Red-Green-Refactor cycle strictly enforced -->
|
||||
|
||||
### [PRINCIPLE_4_NAME]
|
||||
<!-- Example: IV. Integration Testing -->
|
||||
[PRINCIPLE_4_DESCRIPTION]
|
||||
<!-- Example: Focus areas requiring integration tests: New library contract tests, Contract changes, Inter-service communication, Shared schemas -->
|
||||
|
||||
### [PRINCIPLE_5_NAME]
|
||||
<!-- Example: V. Observability, VI. Versioning & Breaking Changes, VII. Simplicity -->
|
||||
[PRINCIPLE_5_DESCRIPTION]
|
||||
<!-- Example: Text I/O ensures debuggability; Structured logging required; Or: MAJOR.MINOR.BUILD format; Or: Start simple, YAGNI principles -->
|
||||
|
||||
## [SECTION_2_NAME]
|
||||
<!-- Example: Additional Constraints, Security Requirements, Performance Standards, etc. -->
|
||||
|
||||
[SECTION_2_CONTENT]
|
||||
<!-- Example: Technology stack requirements, compliance standards, deployment policies, etc. -->
|
||||
|
||||
## [SECTION_3_NAME]
|
||||
<!-- Example: Development Workflow, Review Process, Quality Gates, etc. -->
|
||||
|
||||
[SECTION_3_CONTENT]
|
||||
<!-- Example: Code review requirements, testing gates, deployment approval process, etc. -->
|
||||
|
||||
## Governance
|
||||
<!-- Example: Constitution supersedes all other practices; Amendments require documentation, approval, migration plan -->
|
||||
|
||||
[GOVERNANCE_RULES]
|
||||
<!-- Example: All PRs/reviews must verify compliance; Complexity must be justified; Use [GUIDANCE_FILE] for runtime development guidance -->
|
||||
|
||||
**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE]
|
||||
<!-- Example: Version: 2.1.1 | Ratified: 2025-06-13 | Last Amended: 2025-07-16 -->
|
||||
@ -21,66 +21,12 @@ ## Technical Context
|
||||
**Primary Dependencies**: [e.g., FastAPI, UIKit, LLVM or NEEDS CLARIFICATION]
|
||||
**Storage**: [if applicable, e.g., PostgreSQL, CoreData, files or N/A]
|
||||
**Testing**: [e.g., pytest, XCTest, cargo test or NEEDS CLARIFICATION]
|
||||
**Validation Lanes**: [e.g., fast-feedback, confidence or NEEDS CLARIFICATION]
|
||||
**Target Platform**: [e.g., Linux server, iOS 15+, WASM or NEEDS CLARIFICATION]
|
||||
**Project Type**: [single/web/mobile - determines source structure]
|
||||
**Performance Goals**: [domain-specific, e.g., 1000 req/s, 10k lines/sec, 60 fps or NEEDS CLARIFICATION]
|
||||
**Constraints**: [domain-specific, e.g., <200ms p95, <100MB memory, offline-capable or NEEDS CLARIFICATION]
|
||||
**Scale/Scope**: [domain-specific, e.g., 10k users, 1M LOC, 50 screens or NEEDS CLARIFICATION]
|
||||
|
||||
## UI / Surface Guardrail Plan
|
||||
|
||||
> **Fill for operator-facing or guardrail-relevant workflow changes. Docs-only or template-only work may use concise `N/A`. Copy the spec classification forward; do not rename or expand it here.**
|
||||
|
||||
- **Guardrail scope**: [no operator-facing surface change / changed surfaces / workflow-only guardrail change]
|
||||
- **Native vs custom classification summary**: [native / custom / mixed / N/A]
|
||||
- **Shared-family relevance**: [none / list affected shared families]
|
||||
- **State layers in scope**: [shell / page / detail / URL-query / none]
|
||||
- **Audience modes in scope**: [customer/read-only / operator-MSP / support-platform / N/A]
|
||||
- **Decision/diagnostic/raw hierarchy plan**: [decision-first / diagnostics-second / support-raw-third / N/A]
|
||||
- **Raw/support gating plan**: [collapsed / capability-gated / role-gated / N/A]
|
||||
- **One-primary-action / duplicate-truth control**: [how one dominant next action is preserved and repeated blockers are removed]
|
||||
- **Handling modes by drift class or surface**: [hard-stop-candidate / review-mandatory / exception-required / report-only / N/A]
|
||||
- **Repository-signal treatment**: [report-only / review-mandatory / exception-required / future hard-stop candidate / N/A]
|
||||
- **Special surface test profiles**: [standard-native-filament / shared-detail-family / monitoring-state-page / global-context-shell / exception-coded-surface / N/A]
|
||||
- **Required tests or manual smoke**: [functional-core / state-contract / exception-fallback / manual-smoke / N/A]
|
||||
- **Exception path and spread control**: [none / describe the named exception boundary]
|
||||
- **Active feature PR close-out entry**: [Guardrail / Exception / Smoke Coverage / N/A]
|
||||
|
||||
## Shared Pattern & System Fit
|
||||
|
||||
> **Fill when the feature touches notifications, status messaging, action links, header actions, dashboard signals/cards, navigation entry points, alerts, evidence/report viewers, or any other shared interaction family. Docs-only or template-only work may use concise `N/A`. Carry the same decision forward from the spec instead of renaming it here.**
|
||||
|
||||
- **Cross-cutting feature marker**: [yes / no / N/A]
|
||||
- **Systems touched**: [List the existing shared systems or `N/A`]
|
||||
- **Shared abstractions reused**: [Named contracts / presenters / builders / renderers / helpers or `N/A`]
|
||||
- **New abstraction introduced? why?**: [none / short explanation]
|
||||
- **Why the existing abstraction was sufficient or insufficient**: [Short explanation tied to current-release truth]
|
||||
- **Bounded deviation / spread control**: [none / describe the exception boundary and containment rule]
|
||||
|
||||
## OperationRun UX Impact
|
||||
|
||||
> **Fill when the feature creates, queues, deduplicates, resumes, blocks, completes, or deep-links to an `OperationRun`. Docs-only or template-only work may use concise `N/A`.**
|
||||
|
||||
- **Touches OperationRun start/completion/link UX?**: [yes / no / N/A]
|
||||
- **Central contract reused**: [shared OperationRun UX layer / `N/A`]
|
||||
- **Delegated UX behaviors**: [queued toast / run link / artifact link / run-enqueued browser event / queued DB-notification decision / dedupe-or-blocked messaging / tenant/workspace-safe URL resolution / `N/A`]
|
||||
- **Surface-owned behavior kept local**: [initiation inputs only / none / short explanation]
|
||||
- **Queued DB-notification policy**: [explicit opt-in / spec override / `N/A`]
|
||||
- **Terminal notification path**: [central lifecycle mechanism / `N/A`]
|
||||
- **Exception path**: [none / spec decision + architecture note + test rationale + temporary migration follow-up]
|
||||
|
||||
## Provider Boundary & Portability Fit
|
||||
|
||||
> **Fill when the feature touches shared provider/platform seams, identity scope, governed-subject taxonomy, compare strategy selection, provider connection descriptors, or operator vocabulary that may leak provider-specific semantics into platform-core truth. Docs-only or template-only work may use concise `N/A`.**
|
||||
|
||||
- **Shared provider/platform boundary touched?**: [yes / no / N/A]
|
||||
- **Provider-owned seams**: [List or `N/A`]
|
||||
- **Platform-core seams**: [List or `N/A`]
|
||||
- **Neutral platform terms / contracts preserved**: [List or `N/A`]
|
||||
- **Retained provider-specific semantics and why**: [none / short explanation]
|
||||
- **Bounded extraction or follow-up path**: [none / document-in-feature / follow-up-spec / N/A]
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
@ -95,46 +41,23 @@ ## Constitution Check
|
||||
- RBAC-UX: global search is tenant-scoped; non-members get no hints; inaccessible results are treated as not found (404 semantics)
|
||||
- Tenant isolation: all reads/writes tenant-scoped; cross-tenant views are explicit and access-checked
|
||||
- Run observability: long-running/remote/queued work creates/reuses `OperationRun`; start surfaces enqueue-only; Monitoring is DB-only; DB-only <2s actions may skip runs but security-relevant ones still audit-log; auth handshake exception OPS-EX-AUTH-001 allows synchronous outbound HTTP on `/auth/*` without `OperationRun`
|
||||
- OperationRun start UX: any feature that creates, queues, deduplicates, resumes, blocks, completes, or links `OperationRun` reuses the central OperationRun Start UX Contract; no local composition of queued toast/link/event/start-state messaging; `OperationRun UX Impact` is present in the active spec or plan
|
||||
- Ops-UX 3-surface feedback: if `OperationRun` is used, default feedback is toast intent-only + progress surfaces + exactly-once terminal `OperationRunCompleted` (initiator-only); queued DB notifications remain explicit opt-in through the shared start UX contract; running DB notifications stay disallowed
|
||||
- Ops-UX 3-surface feedback: if `OperationRun` is used, feedback is exactly toast intent-only + progress surfaces + exactly-once terminal `OperationRunCompleted` (initiator-only); no queued/running DB notifications
|
||||
- Ops-UX lifecycle: `OperationRun.status` / `OperationRun.outcome` transitions are service-owned (only via `OperationRunService`); context-only updates allowed outside
|
||||
- Ops-UX summary counts: `summary_counts` keys come from `OperationSummaryKeys::all()` and values are flat numeric-only
|
||||
- Ops-UX guards: CI has regression guards that fail with actionable output (file + snippet) when these patterns regress
|
||||
- Ops-UX system runs: initiator-null runs emit no terminal DB notification; audit remains via Monitoring; tenant-wide alerting goes through Alerts (not OperationRun notifications)
|
||||
- Automation: queued/scheduled ops use locks + idempotency; handle 429/503 with backoff+jitter
|
||||
- Data minimization: Inventory stores metadata + whitelisted meta; logs contain no secrets/tokens
|
||||
- Test governance (TEST-GOV-001): actual test-purpose classification, affected lanes, fixture/helper/factory/seed/context cost risks, heavy-family visibility, review-stop points, reviewer handoff, and any budget/baseline/trend follow-up are explicit; the narrowest proving lane mix is planned and any structural cost change has an escalation path
|
||||
- Proportionality (PROP-001): any new structure, layer, persisted truth, or semantic machinery is justified by current release truth, current operator workflow, and why a narrower solution is insufficient
|
||||
- No premature abstraction (ABSTR-001): no new factories, registries, resolvers, strategy systems, interfaces, type registries, or orchestration pipelines before at least 2 real concrete cases exist, unless security, tenant isolation, auditability, compliance evidence, or queue correctness require it now
|
||||
- Persisted truth (PERSIST-001): new tables/entities/artifacts represent independent product truth or lifecycle; convenience projections and UI helpers stay derived
|
||||
- Behavioral state (STATE-001): new states/statuses/reason codes change behavior, routing, permissions, lifecycle, audit, retention, or retry handling; presentation-only distinctions stay derived
|
||||
- UI semantics (UI-SEM-001): avoid turning badges, explanation text, trust/confidence labels, or detail summaries into mandatory interpretation frameworks; prefer direct domain-to-UI mapping
|
||||
- Shared pattern first (XCUT-001): cross-cutting interaction classes reuse existing shared contracts/presenters/builders/renderers first; any deviation is explicit, bounded, and justified against current-release truth
|
||||
- Provider boundary (PROV-001): shared provider/platform seams are classified as provider-owned vs platform-core; provider-specific semantics stay out of platform-core contracts, taxonomy, identifiers, compare semantics, and operator vocabulary unless explicitly justified; bounded extraction beats speculative multi-provider frameworks
|
||||
- V1 explicitness / few layers (V1-EXP-001, LAYER-001): prefer direct implementation, local mappings, and small helpers; any new layer replaces an old one or proves the old one cannot serve
|
||||
- Spec discipline / bloat check (SPEC-DISC-001, BLOAT-001): related semantic changes are grouped coherently, and any new enum, DTO/presenter, persisted entity, interface/registry/resolver, or taxonomy includes a proportionality review covering operator problem, insufficiency, narrowness, ownership cost, rejected alternative, and whether it is current-release truth
|
||||
- Badge semantics (BADGE-001): status-like badges use `BadgeCatalog` / `BadgeRenderer`; no ad-hoc mappings; new values include tests
|
||||
- Filament-native UI (UI-FIL-001): admin/operator surfaces use native Filament components or shared primitives first; no ad-hoc status UI, local semantic color/border decisions, or hand-built replacements when native/shared semantics exist; any exception is explicitly justified
|
||||
- Filament-native UI (UI-FIL-001): if local Blade/Tailwind cards are
|
||||
still necessary, they preserve dark mode correctness, spacing
|
||||
consistency, badge semantics, action hierarchy, progressive
|
||||
disclosure, accessibility, and Filament visual language
|
||||
- UI/UX surface taxonomy (UI-CONST-001 / UI-SURF-001): every changed operator-facing surface is classified as exactly one allowed surface type; ad-hoc interaction models are forbidden
|
||||
- Decision-first operating model (DECIDE-001): each changed
|
||||
operator-facing surface is classified as Primary Decision,
|
||||
Secondary Context, or Tertiary Evidence / Diagnostics; primary
|
||||
surfaces justify the human-in-the-loop moment, default-visible info
|
||||
is limited to first-decision needs, deep proof is progressive
|
||||
disclosed, one governance case stays decidable in one context where
|
||||
practical, navigation follows workflows not storage structures, and
|
||||
automation / alerts reduce attention load instead of adding noise
|
||||
- Audience-aware disclosure (DECIDE-AUD-001 / OPSURF-001): detail or
|
||||
status surfaces separate customer-readable decision content,
|
||||
operator diagnostics, and support/raw evidence; customer-readable
|
||||
default paths hide raw JSON, copied context, fingerprints, internal
|
||||
reason ownership, platform reason families, and debug semantics;
|
||||
one dominant next action is explicit; duplicate visible truth is
|
||||
removed
|
||||
- UI/UX inspect model (UI-HARD-001): each list surface has exactly one primary inspect/open model; redundant View beside row click or identifier click is forbidden; edit-as-inspect is limited to Config-lite resources
|
||||
- UI/UX action hierarchy (UI-HARD-001 / UI-EX-001): standard CRUD and Registry rows expose at most one inline safe shortcut; destructive actions are grouped or in the detail header; queue exceptions are catalogued, justified, and tested
|
||||
- UI/UX scope, truth, and naming (UI-HARD-001 / UI-NAMING-001 / OPSURF-001): scope signals are truthful, canonical nouns stay stable across shells, critical operational truth is default-visible, and standard lists remain scanable
|
||||
@ -148,39 +71,7 @@ ## Constitution Check
|
||||
- Operator surfaces (OPSURF-001): each new or materially refactored operator-facing page defines a page contract covering persona, surface type, operator question, default-visible info, diagnostics-only info, status dimensions, mutation scope, primary actions, and dangerous actions
|
||||
- Filament UI Action Surface Contract: for any new/modified Filament Resource/RelationManager/Page, define Header/Row/Bulk/Empty-State actions, ensure every List/Table has a surface-appropriate inspect affordance, remove redundant View when row click or identifier click already opens the same destination, keep standard CRUD/Registry rows to inspect plus at most one inline safe shortcut, group or relocate the rest to “More” or detail header, forbid empty bulk/overflow groups, require confirmations for destructive actions, write audit logs for mutations, enforce RBAC via central helpers (non-member 404, member missing capability 403), and ensure CI blocks merges if the contract is violated or not explicitly exempted
|
||||
- Filament UI UX-001 (Layout & IA): Create/Edit uses Main/Aside (3-col grid, Main=columnSpan(2), Aside=columnSpan(1)); all fields inside Sections/Cards (no naked inputs); View uses Infolists (not disabled edit forms); status badges use BADGE-001; empty states have specific title + explanation + 1 CTA; max 1 primary + 1 secondary header action (see HDR-001); tables provide search/sort/filters for core dimensions; shared layout builders preferred for consistency
|
||||
- Action-surface discipline (ACTSURF-001 / HDR-001): every changed
|
||||
surface declares one broad action-surface class; the spec names the
|
||||
one likely next operator action; navigation is separated from
|
||||
mutation; record/detail/edit pages keep at most one visible primary
|
||||
header action; monitoring/workbench surfaces separate scope/context,
|
||||
selection actions, navigation, and object actions; risky or rare
|
||||
actions are grouped and ordered by meaning/frequency/risk; any special
|
||||
type or workflow-hub exception is explicit and justified
|
||||
- UI review workflow: native/custom classification, shared-family
|
||||
relevance, state-layer ownership, repository-signal treatment,
|
||||
exception path, and the active feature PR close-out entry stay
|
||||
explicit without duplicating the same decision across spec, plan,
|
||||
tasks, checklist, and close-out surfaces
|
||||
|
||||
## Test Governance Check
|
||||
|
||||
> **Fill for any runtime-changing or test-affecting feature. Docs-only or template-only work may state concise `N/A` or `none`.**
|
||||
|
||||
- **Test purpose / classification by changed surface**: [Unit / Feature / Heavy-Governance / Browser / N/A]
|
||||
- **Affected validation lanes**: [fast-feedback / confidence / heavy-governance / browser / profiling / junit / N/A]
|
||||
- **Why this lane mix is the narrowest sufficient proof**: [Why the chosen classification and lanes fit the actual proving purpose]
|
||||
- **Narrowest proving command(s)**: [Exact commands reviewers should run before merge]
|
||||
- **Fixture / helper / factory / seed / context cost risks**: [none / describe]
|
||||
- **Expensive defaults or shared helper growth introduced?**: [no / describe explicit opt-in path]
|
||||
- **Heavy-family additions, promotions, or visibility changes**: [none / describe]
|
||||
- **Surface-class relief / special coverage rule**: [standard-native relief / named special profile / N/A]
|
||||
- **Closing validation and reviewer handoff**: [What must be re-run, what reviewers should verify, and what exact proof command they should rely on]
|
||||
- **Budget / baseline / trend follow-up**: [none / describe]
|
||||
- **Review-stop questions**: [lane fit / breadth / hidden cost / heavy-family risk / escalation]
|
||||
- **Escalation path**: [none / document-in-feature / follow-up-spec / reject-or-split]
|
||||
- **Active feature PR close-out entry**: [Guardrail / Exception / Smoke Coverage / N/A]
|
||||
- **Why no dedicated follow-up spec is needed**: [Routine upkeep stays inside this feature unless recurring pain or structural lane changes justify a separate spec]
|
||||
|
||||
- Header action discipline (HDR-001): record/detail pages expose at most 1 primary visible header action; pure navigation (Open finding, Open tenant, View related run, etc.) is placed at the relevant field/badge/relation, NOT in the header; destructive or governance-changing actions are separated and require friction; rare actions live in Action Groups; every record/detail page passes the 5-second scan rule
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
@ -35,93 +35,22 @@ ## Spec Scope Fields *(mandatory)*
|
||||
- **Default filter behavior when tenant-context is active**: [e.g., prefilter to current tenant]
|
||||
- **Explicit entitlement checks preventing cross-tenant leakage**: [Describe checks]
|
||||
|
||||
## Cross-Cutting / Shared Pattern Reuse *(mandatory when the feature touches notifications, status messaging, action links, header actions, dashboard signals/cards, alerts, navigation entry points, evidence/report viewers, or any other existing shared operator interaction family; otherwise write `N/A - no shared interaction family touched`)*
|
||||
|
||||
- **Cross-cutting feature?**: [yes/no]
|
||||
- **Interaction class(es)**: [notifications / status messaging / header actions / dashboard signals / navigation / reports / etc.]
|
||||
- **Systems touched**: [List shared systems, surfaces, or infrastructure paths]
|
||||
- **Existing pattern(s) to extend**: [Name the existing shared path(s) or write `none`]
|
||||
- **Shared contract / presenter / builder / renderer to reuse**: [Exact class, helper, or surface path, or `none`]
|
||||
- **Why the existing shared path is sufficient or insufficient**: [Short explanation tied to current-release truth]
|
||||
- **Allowed deviation and why**: [none / bounded exception + why]
|
||||
- **Consistency impact**: [What must stay aligned across interaction structure, copy, status semantics, actions, and deep links]
|
||||
- **Review focus**: [What reviewers must verify to prevent parallel local patterns]
|
||||
|
||||
## OperationRun UX Impact *(mandatory when the feature creates, queues, deduplicates, resumes, blocks, completes, or deep-links to an `OperationRun`; otherwise write `N/A - no OperationRun start or link semantics touched`)*
|
||||
|
||||
- **Touches OperationRun start/completion/link UX?**: [yes/no]
|
||||
- **Shared OperationRun UX contract/layer reused**: [Name it or `N/A`]
|
||||
- **Delegated start/completion UX behaviors**: [queued toast / `Open operation` or `View run` link / artifact link / run-enqueued browser event / queued DB-notification decision / dedupe-or-blocked messaging / tenant/workspace-safe URL resolution / `N/A`]
|
||||
- **Local surface-owned behavior that remains**: [initiation inputs only / none / bounded explanation]
|
||||
- **Queued DB-notification policy**: [explicit opt-in / spec override / `N/A`]
|
||||
- **Terminal notification path**: [central lifecycle mechanism / `N/A`]
|
||||
- **Exception required?**: [none / explicit spec decision + architecture note + test or guard-test rationale + temporary migration follow-up]
|
||||
|
||||
## Provider Boundary / Platform Core Check *(mandatory when the feature changes shared provider/platform seams, identity scope, governed-subject taxonomy, compare strategy selection, provider connection descriptors, or operator vocabulary that may leak provider-specific semantics into platform-core truth; otherwise write `N/A - no shared provider/platform boundary touched`)*
|
||||
|
||||
- **Shared provider/platform boundary touched?**: [yes/no]
|
||||
- **Boundary classification**: [provider-owned / platform-core / mixed / N/A]
|
||||
- **Seams affected**: [contracts, models, taxonomies, query keys, labels, filters, compare strategy, etc.]
|
||||
- **Neutral platform terms preserved or introduced**: [List them or `N/A`]
|
||||
- **Provider-specific semantics retained and why**: [none / bounded current-release necessity]
|
||||
- **Why this does not deepen provider coupling accidentally**: [Short explanation]
|
||||
- **Follow-up path**: [none / document-in-feature / follow-up-spec]
|
||||
|
||||
## UI / Surface Guardrail Impact *(mandatory when operator-facing surfaces are changed; otherwise write `N/A`)*
|
||||
|
||||
Use this section to classify UI and surface risk once. If the feature does
|
||||
not change an operator-facing surface, write `N/A - no operator-facing surface
|
||||
change` here and do not invent duplicate prose in the downstream surface tables.
|
||||
|
||||
| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note |
|
||||
|---|---|---|---|---|---|---|
|
||||
| e.g. Tenant policies page | yes | Native Filament + shared primitives | none | page, detail | no | n/a |
|
||||
| e.g. Docs-only change | no | N/A | none | none | no | `N/A - repository workflow only` |
|
||||
|
||||
## Decision-First Surface Role *(mandatory when operator-facing surfaces are changed)*
|
||||
|
||||
If this feature adds or materially changes an operator-facing surface,
|
||||
fill out one row per affected surface. This role is orthogonal to the
|
||||
Action Surface Class / Surface Type below. Reuse the exact surface names
|
||||
and classifications from the UI / Surface Guardrail Impact section above.
|
||||
|
||||
| Surface | Decision Role | Human-in-the-loop Moment | Immediately Visible for First Decision | On-Demand Detail / Evidence | Why This Is Primary or Why Not | Workflow Alignment | Attention-load Reduction |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| e.g. Review inbox | Primary Decision Surface | Review and release queued governance work | Case summary, severity, recommendation, required action | Full evidence, raw payloads, audit trail, provider diagnostics | Primary because it is the queue where operators decide and clear work | Follows pending-decisions workflow, not storage objects | Removes search across runs, findings, and audit pages |
|
||||
|
||||
## Audience-Aware Disclosure *(mandatory when operator-facing surfaces are changed)*
|
||||
|
||||
If this feature adds or materially changes a detail or status surface,
|
||||
fill out one row per affected surface. Reuse the same surface names
|
||||
used above and make the disclosure hierarchy explicit instead of
|
||||
assuming it.
|
||||
|
||||
| Surface | Audience Modes In Scope | Decision-First Default-Visible Content | Operator Diagnostics | Support / Raw Evidence | One Dominant Next Action | Hidden / Gated By Default | Duplicate-Truth Prevention |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| e.g. Review inbox | customer-read-only, operator-MSP, support-platform | Current status, why it matters, impact, recommendation, next action | Review history, lifecycle, related evidence, related runs | Raw payloads, fingerprints, reason ownership, platform reason family | `Review evidence` | Raw/support detail hidden or capability-gated outside support mode | The top summary states the blocker once; later sections add evidence rather than restating it |
|
||||
|
||||
## UI/UX Surface Classification *(mandatory when operator-facing surfaces are changed)*
|
||||
|
||||
If this feature adds or materially changes an operator-facing list, detail, queue, audit, config, or report surface,
|
||||
fill out one row per affected surface. Declare the broad Action Surface
|
||||
Class first, then the detailed Surface Type. Keep this table in sync
|
||||
with the Decision-First Surface Role section above and avoid renaming the
|
||||
same surface a second time.
|
||||
fill out one row per affected surface.
|
||||
|
||||
| Surface | Action Surface Class | Surface Type | Likely Next Operator Action | Primary Inspect/Open Model | Row Click | Secondary Actions Placement | Destructive Actions Placement | Canonical Collection Route | Canonical Detail Route | Scope Signals | Canonical Noun | Critical Truth Visible by Default | Exception Type / Justification |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| e.g. Tenant policies page | List / Table / Bulk | CRUD / List-first Resource | Open policy for review | Full-row click | required | One inline safe shortcut + More | More / detail header | /admin/t/{tenant}/policies | /admin/t/{tenant}/policies/{record} | Tenant chip scopes rows and actions | Policies / Policy | Policy health, drift, assignment coverage | none |
|
||||
| Surface | Surface Type | Primary Inspect/Open Model | Row Click | Secondary Actions Placement | Destructive Actions Placement | Canonical Collection Route | Canonical Detail Route | Scope Signals | Canonical Noun | Critical Truth Visible by Default | Exception Type |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| e.g. Tenant policies page | CRUD / List-first Resource | Full-row click | required | One inline safe shortcut + More | More / detail header | /admin/t/{tenant}/policies | /admin/t/{tenant}/policies/{record} | Tenant chip scopes rows and actions | Policies / Policy | Policy health, drift, assignment coverage | none |
|
||||
|
||||
## Operator Surface Contract *(mandatory when operator-facing surfaces are changed)*
|
||||
|
||||
If this feature adds a new operator-facing page or materially refactors
|
||||
one, fill out one row per affected page/surface. The contract MUST show
|
||||
how one governance case or operator task becomes decidable without
|
||||
unnecessary cross-page reconstruction.
|
||||
If this feature adds a new operator-facing page or materially refactors one, fill out one row per affected page/surface.
|
||||
|
||||
| Surface | Primary Persona | Decision / Operator Action Supported | Surface Type | Primary Operator Question | Default-visible Information | Diagnostics-only Information | Status Dimensions Used | Mutation Scope | Primary Actions | Dangerous Actions |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| e.g. Tenant policies page | Tenant operator | Decide whether policy state needs follow-up | List/detail | What needs action right now? | Policy health, drift, assignment coverage | Raw payloads, provider IDs, low-level API details | lifecycle, data completeness, governance result | TenantPilot only / Microsoft tenant / simulation only | Sync policies, View policy | Restore policy |
|
||||
| Surface | Primary Persona | Surface Type | Primary Operator Question | Default-visible Information | Diagnostics-only Information | Status Dimensions Used | Mutation Scope | Primary Actions | Dangerous Actions |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| e.g. Tenant policies page | Tenant operator | List/detail | What needs action right now? | Policy health, drift, assignment coverage | Raw payloads, provider IDs, low-level API details | lifecycle, data completeness, governance result | TenantPilot only / Microsoft tenant / simulation only | Sync policies, View policy | Restore policy |
|
||||
|
||||
## Proportionality Review *(mandatory when structural complexity is introduced)*
|
||||
|
||||
@ -144,32 +73,6 @@ ## Proportionality Review *(mandatory when structural complexity is introduced)*
|
||||
- **Alternative intentionally rejected**: [What simpler option was considered and why it was not sufficient]
|
||||
- **Release truth**: [Current-release truth or future-release preparation]
|
||||
|
||||
### Compatibility posture
|
||||
|
||||
This feature assumes a pre-production environment.
|
||||
|
||||
Backward compatibility, legacy aliases, migration shims, historical fixtures, and compatibility-specific tests are out of scope unless explicitly required by this spec.
|
||||
|
||||
Canonical replacement is preferred over preservation.
|
||||
|
||||
## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
|
||||
|
||||
For docs-only or template-only changes, state concise `N/A` or `none`. For runtime- or test-affecting work, classification MUST follow the proving purpose of the change rather than the file path or folder name.
|
||||
|
||||
- **Test purpose / classification**: [Unit / Feature / Heavy-Governance / Browser / N/A]
|
||||
- **Validation lane(s)**: [fast-feedback / confidence / heavy-governance / browser / profiling / junit / N/A]
|
||||
- **Why this classification and these lanes are sufficient**: [Why the narrowest listed lane(s) and chosen test type prove the change]
|
||||
- **New or expanded test families**: [none / describe]
|
||||
- **Fixture / helper cost impact**: [none / describe new defaults, factories, seeds, helpers, browser setup, provider setup, workspace or membership context, session state, etc.]
|
||||
- **Heavy-family visibility / justification**: [none / explain any heavy-governance or browser addition and how it remains explicit in naming, lane choice, and review]
|
||||
- **Special surface test profile**: [standard-native-filament / shared-detail-family / monitoring-state-page / global-context-shell / exception-coded-surface / N/A]
|
||||
- **Standard-native relief or required special coverage**: [ordinary feature coverage only / describe required tests or smoke checks]
|
||||
- **Reviewer handoff**: [What reviewers must confirm about lane fit, hidden cost, heavy-family visibility, and the exact proof command]
|
||||
- **Budget / baseline / trend impact**: [none / expected drift + follow-up]
|
||||
- **Escalation needed**: [none / document-in-feature / follow-up-spec / reject-or-split]
|
||||
- **Active feature PR close-out entry**: [Guardrail / Exception / Smoke Coverage / N/A]
|
||||
- **Planned validation commands**: [Exact minimal commands reviewers should run]
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
<!--
|
||||
@ -257,55 +160,13 @@ ## Requirements *(mandatory)*
|
||||
If the feature introduces a new enum/status family, DTO/presenter/envelope, persisted entity/table, interface/contract/registry/resolver,
|
||||
or taxonomy/classification system, the Proportionality Review section above is mandatory.
|
||||
|
||||
**Constitution alignment (XCUT-001):** If this feature touches a cross-cutting interaction class such as notifications, status messaging,
|
||||
action links, header actions, dashboard signals/cards, alerts, navigation entry points, or evidence/report viewers, the spec MUST:
|
||||
- state whether the feature is cross-cutting,
|
||||
- name the existing shared pattern(s) and shared contract/presenter/builder/renderer to extend,
|
||||
- explain why the existing shared path is sufficient or why it is insufficient for current-release truth,
|
||||
- record any allowed deviation, the consistency it must preserve, and its ownership/spread-control cost,
|
||||
- and make the reviewer focus explicit so parallel local UX paths do not appear silently.
|
||||
|
||||
**Constitution alignment (DECIDE-AUD-001 / OPSURF-001):** If this feature changes a detail or status surface, the spec MUST describe:
|
||||
- how the surface separates customer-readable decision content, operator diagnostics, and support/raw evidence,
|
||||
- which audience modes are in scope (`customer/read-only`, `operator/MSP`, `support/platform`),
|
||||
- which content is hidden, collapsed, or capability-gated by default,
|
||||
- how one dominant next action is preserved,
|
||||
- and how duplicate visible truth is prevented.
|
||||
|
||||
**Constitution alignment (PROV-001):** If this feature touches a shared provider/platform seam, the spec MUST:
|
||||
- classify each touched seam as provider-owned or platform-core,
|
||||
- keep provider-specific semantics out of platform-core contracts, taxonomies, identifiers, compare semantics, and operator vocabulary unless explicitly justified,
|
||||
- name the neutral platform terms or shared contracts being preserved,
|
||||
- explain why any retained provider-specific semantics are the narrowest current-release truth,
|
||||
- and state whether the remaining hotspot is resolved in-feature or escalated as a follow-up spec.
|
||||
|
||||
**Constitution alignment (TEST-GOV-001):** If this feature changes runtime behavior or tests, the spec MUST describe:
|
||||
- the actual test-purpose classification (`Unit`, `Feature`, `Heavy-Governance`, or `Browser`) and why that classification matches the real proving purpose,
|
||||
- the affected validation lane(s) and why they are the narrowest sufficient proof,
|
||||
- any new or expanded heavy-governance or browser coverage,
|
||||
- any fixture, helper, factory, seed, provider, workspace, membership, session, or default setup cost added or avoided,
|
||||
- how any heavy family stays explicit rather than becoming accidental default breadth,
|
||||
- the reviewer handoff for lane fit, hidden-cost checks, and the exact minimal validation commands,
|
||||
- any expected budget, baseline, or trend impact,
|
||||
- whether escalation stays inside this feature or resolves as `document-in-feature`, `follow-up-spec`, or `reject-or-split`,
|
||||
- and the exact minimal validation commands reviewers should run.
|
||||
|
||||
**Constitution alignment (OPS-UX):** If this feature creates/reuses an `OperationRun`, the spec MUST:
|
||||
- explicitly state compliance with the default Ops-UX 3-surface feedback contract (toast intent-only, progress surfaces, terminal DB notification) and whether any queued DB notification is explicitly opted into,
|
||||
- explicitly state compliance with the Ops-UX 3-surface feedback contract (toast intent-only, progress surfaces, terminal DB notification),
|
||||
- state that `OperationRun.status` / `OperationRun.outcome` transitions are service-owned (only via `OperationRunService`),
|
||||
- describe how `summary_counts` keys/values comply with `OperationSummaryKeys::all()` and numeric-only rules,
|
||||
- clarify scheduled/system-run behavior (initiator null → no terminal DB notification; audit is via Monitoring),
|
||||
- list which regression guard tests are added/updated to keep these rules enforceable in CI.
|
||||
|
||||
**Constitution alignment (OPS-UX-START-001):** If this feature creates, queues, deduplicates, resumes, blocks, completes, or links to an `OperationRun`, the spec MUST:
|
||||
- include the `OperationRun UX Impact` section,
|
||||
- name the shared OperationRun UX contract/layer being reused,
|
||||
- delegate queued toast/link/artifact-link/browser-event/queued-DB-notification/dedupe-or-blocked messaging/tenant-safe URL resolution to that shared path,
|
||||
- keep local surface code limited to initiation inputs and operation-specific data capture,
|
||||
- keep queued DB notifications explicit opt-in unless the spec intentionally defines a different policy,
|
||||
- route terminal notifications through the central lifecycle mechanism,
|
||||
- and document any exception with an explicit spec decision, architecture note, test or guard-test rationale, and temporary follow-up migration decision.
|
||||
|
||||
**Constitution alignment (RBAC-UX):** If this feature introduces or changes authorization behavior, the spec MUST:
|
||||
- state which authorization plane(s) are involved (tenant/admin `/admin` + tenant-context `/admin/t/{tenant}/...` vs platform `/system`),
|
||||
- ensure any cross-plane access is deny-as-not-found (404),
|
||||
@ -328,7 +189,6 @@ ## Requirements *(mandatory)*
|
||||
- which native Filament components or shared UI primitives are used,
|
||||
- whether any local replacement markup was avoided for badges, alerts, buttons, or status surfaces,
|
||||
- how semantic emphasis is expressed through Filament props or central primitives rather than page-local color/border classes,
|
||||
- how any required local Blade/Tailwind cards still preserve dark mode correctness, spacing consistency, badge semantics, action hierarchy, progressive disclosure, accessibility, and Filament visual language,
|
||||
- and any exception where Filament or a shared primitive was insufficient, including why the exception is necessary and how it avoids introducing a new local status language.
|
||||
|
||||
**Constitution alignment (UI-NAMING-001):** If this feature adds or changes operator-facing buttons, header actions, run titles,
|
||||
@ -339,54 +199,22 @@ ## Requirements *(mandatory)*
|
||||
- how the same domain vocabulary is preserved across button labels, modal titles, run titles, notifications, and audit prose,
|
||||
- and how implementation-first terms are kept out of primary operator-facing labels.
|
||||
|
||||
**Constitution alignment (DECIDE-001):** If this feature adds or changes operator-facing surfaces, the spec MUST describe:
|
||||
- whether each affected surface is a Primary Decision Surface,
|
||||
Secondary Context Surface, or Tertiary Evidence / Diagnostics
|
||||
Surface, and why,
|
||||
- which human-in-the-loop moment each primary surface supports,
|
||||
- what MUST be visible immediately for the first decision,
|
||||
- what is preserved but only revealed on demand,
|
||||
- why any new primary surface cannot live inside an existing decision
|
||||
context,
|
||||
- how navigation follows operator workflows rather than storage
|
||||
structures,
|
||||
- how one governance case remains decidable in one focused context,
|
||||
- how any new automation, notifications, or autonomous governance logic
|
||||
reduce search/review/click load,
|
||||
- and how the resulting default experience is calmer and clearer rather
|
||||
than merely larger.
|
||||
|
||||
**Constitution alignment (UI-CONST-001 / UI-SURF-001 / ACTSURF-001 / UI-HARD-001 / UI-EX-001 / UI-REVIEW-001 / HDR-001):** If this feature adds or changes an operator-facing surface, the spec MUST describe:
|
||||
- the chosen broad action-surface class and why it is the correct classification,
|
||||
- the chosen detailed surface type and why it is the correct refinement,
|
||||
- the one most likely next operator action,
|
||||
**Constitution alignment (UI-CONST-001 / UI-SURF-001 / UI-HARD-001 / UI-EX-001 / UI-REVIEW-001):** If this feature adds or changes an operator-facing surface, the spec MUST describe:
|
||||
- the chosen surface type and why it is the correct classification,
|
||||
- the one and only primary inspect/open model,
|
||||
- whether row click is required, allowed, or forbidden,
|
||||
- whether explicit View or Inspect is present, and why it is present or forbidden,
|
||||
- where pure navigation lives and why it is not competing with mutation,
|
||||
- where secondary actions live,
|
||||
- where destructive actions live,
|
||||
- how grouped actions are ordered by meaning, frequency, and risk,
|
||||
- the canonical collection route and canonical detail route,
|
||||
- the scope signals shown to the operator and what real effect each one has,
|
||||
- the canonical noun used across routes, labels, runs, notifications, and audit prose,
|
||||
- which critical operational truth is visible by default,
|
||||
- and any catalogued exception type, rationale, and dedicated test coverage.
|
||||
|
||||
**Constitution alignment (ACTSURF-001 - action hierarchy):** If this
|
||||
feature adds or materially changes header actions, row actions, bulk
|
||||
actions, or workbench controls, the spec MUST describe:
|
||||
- how navigation, mutation, context signals, selection actions, and
|
||||
dangerous actions are separated,
|
||||
- why any visible secondary action deserves primary-plane placement,
|
||||
- why any ActionGroup is structured rather than a mixed catch-all,
|
||||
- and why any workflow-hub, wizard, system, or other special-type
|
||||
exception is genuine rather than a convenience shortcut.
|
||||
|
||||
**Constitution alignment (OPSURF-001):** If this feature adds or materially refactors an operator-facing surface, the spec MUST describe:
|
||||
- how the default-visible content stays operator-first on `/admin` and avoids raw implementation detail,
|
||||
- which diagnostics are secondary and how they are explicitly revealed,
|
||||
- how the dominant next action stays primary and how duplicate visible truth is avoided,
|
||||
- which status dimensions are shown separately (execution outcome, data completeness, governance result, lifecycle/readiness) and why,
|
||||
- how each mutating action communicates its mutation scope before execution (`TenantPilot only`, `Microsoft tenant`, or `simulation only`),
|
||||
- how dangerous actions follow the safe-execution pattern (configuration, safety checks/simulation, preview, hard confirmation where required, execute),
|
||||
|
||||
@ -9,31 +9,18 @@ # Tasks: [FEATURE NAME]
|
||||
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/
|
||||
|
||||
**Tests**: For runtime behavior changes in this repo, tests are REQUIRED (Pest). Only docs-only changes may omit tests.
|
||||
Runtime-changing features MUST also include tasks to:
|
||||
- classify the actual test purpose (`Unit`, `Feature`, `Heavy-Governance`, `Browser`) and confirm the affected validation lane(s),
|
||||
- keep fast or narrow lanes free of silent discovery, surface, workflow, or browser cost,
|
||||
- keep new helpers, factories, seeds, providers, session state, and support defaults cheap by default or isolate expensive setup behind explicit opt-ins,
|
||||
- make any new heavy-governance or browser family explicit in naming, lane assignment, and review notes,
|
||||
- run the narrowest relevant lane before merge,
|
||||
- record budget, baseline, or trend follow-up when runtime cost shifts materially,
|
||||
- and document whether the change resolves as `document-in-feature`, `follow-up-spec`, or `reject-or-split`.
|
||||
**Operations**: If this feature introduces long-running/remote/queued/scheduled work, include tasks to create/reuse and update a
|
||||
canonical `OperationRun`, and ensure “View run” links route to the canonical Monitoring hub through the shared OperationRun start UX path rather than local surface composition.
|
||||
canonical `OperationRun`, and ensure “View run” links route to the canonical Monitoring hub.
|
||||
If security-relevant DB-only actions skip `OperationRun`, include tasks for `AuditLog` entries (before/after + actor + tenant).
|
||||
Auth handshake exception (OPS-EX-AUTH-001): OIDC/SAML login handshakes may perform synchronous outbound HTTP on `/auth/*` endpoints
|
||||
without an `OperationRun`.
|
||||
If this feature creates/reuses an `OperationRun`, tasks MUST also include:
|
||||
- reusing the central OperationRun Start UX Contract instead of composing local queued toast/link/event/dedupe/blocked/start-failure semantics,
|
||||
- delegating `Open operation` / `View run`, artifact links, run-enqueued browser event, queued DB-notification policy, dedupe / already-available / already-running messaging, blocked / failed-to-start messaging, and tenant/workspace-safe URL resolution to the shared OperationRun UX layer,
|
||||
- enforcing the Ops-UX 3-surface feedback contract (toast intent-only via `OperationUxPresenter`, progress only in widget + run detail, terminal notification is `OperationRunCompleted` exactly-once, initiator-only),
|
||||
- keeping queued DB notifications explicit opt-in in the active spec unless a different policy is intentionally approved, and ensuring running DB notifications do not exist,
|
||||
- routing terminal notifications through the central lifecycle mechanism rather than feature-local notification code,
|
||||
- ensuring no queued/running DB notifications exist anywhere for operations (no `sendToDatabase()` for queued/running/completion/abort in feature code),
|
||||
- ensuring `OperationRun.status` / `OperationRun.outcome` transitions happen only via `OperationRunService`,
|
||||
- ensuring `summary_counts` keys come from `OperationSummaryKeys::all()` and values are flat numeric-only,
|
||||
- adding/updating Ops-UX regression guards (Pest) that fail CI with actionable output (file + snippet) when these patterns regress,
|
||||
- clarifying scheduled/system-run behavior (initiator null → no terminal DB notification; audit via Monitoring; tenant-wide alerting via Alerts system),
|
||||
- documenting any exception with an explicit spec decision, architecture note, test or guard-test rationale, and temporary migration follow-up decision,
|
||||
- and ensuring the active spec or plan contains an `OperationRun UX Impact` section.
|
||||
- clarifying scheduled/system-run behavior (initiator null → no terminal DB notification; audit via Monitoring; tenant-wide alerting via Alerts system).
|
||||
**RBAC**: If this feature introduces or changes authorization, tasks MUST include:
|
||||
- explicit Gate/Policy enforcement for all mutation endpoints/actions,
|
||||
- explicit 404 vs 403 semantics:
|
||||
@ -51,113 +38,41 @@ # Tasks: [FEATURE NAME]
|
||||
- using source/domain terms only where same-screen disambiguation is required,
|
||||
- aligning button labels, modal titles, run titles, notifications, and audit prose to the same domain vocabulary,
|
||||
- removing implementation-first wording from primary operator-facing copy.
|
||||
**Cross-Cutting Shared Pattern Reuse (XCUT-001)**: If this feature touches notifications, status messaging, action links, header actions, dashboard signals/cards, navigation entry points, alerts, evidence/report viewers, or another shared interaction family, tasks MUST include:
|
||||
- identifying the existing shared contract/presenter/builder/renderer before local implementation begins,
|
||||
- extending the shared path when it is sufficient for current-release truth,
|
||||
- or recording a bounded exception task that documents why the shared path is insufficient, what consistency must still be preserved, and how spread is controlled,
|
||||
- and ensuring reviewer proof covers whether the feature converged on the shared path or knowingly introduced a bounded exception.
|
||||
**Provider Boundary / Platform Core (PROV-001)**: If this feature touches shared provider/platform seams, tasks MUST include:
|
||||
- classifying each touched seam as provider-owned or platform-core,
|
||||
- preventing provider-specific semantics from spreading into platform-core contracts, persistence truth, taxonomies, compare semantics, or operator vocabulary unless explicitly justified,
|
||||
- implementing bounded normalization or extraction where a current hotspot is too provider-shaped, rather than introducing speculative multi-provider frameworks,
|
||||
- and recording `document-in-feature` or `follow-up-spec` when a bounded provider-specific hotspot remains.
|
||||
**UI / Surface Guardrails**: If this feature adds or changes operator-facing surfaces or the workflow that governs them, tasks MUST include:
|
||||
- carrying forward the spec's native/custom classification, shared-family relevance, state-layer ownership, and exception need into implementation work without renaming the same decision,
|
||||
- classifying any triggered repository signals with one handling mode (`hard-stop-candidate`, `review-mandatory`, `exception-required`, or `report-only`),
|
||||
- adding explicit review or definition-of-done work when a guarded surface class, repository signal, or exception path is involved,
|
||||
- adding required tests or manual smoke for `shared-detail-family`, `monitoring-state-page`, `global-context-shell`, or `exception-coded-surface`, OR recording `standard-native-filament` relief when no special contract exists,
|
||||
- adding exception documentation and spread-control tasks whenever default surface rules are intentionally relaxed,
|
||||
- recording the active feature PR close-out entry with guardrail class, exception status, required tests/manual smoke, low-impact `N/A` use, and any deferred automation.
|
||||
**Operator Surfaces**: If this feature adds or materially refactors an operator-facing page or flow, tasks MUST include:
|
||||
- classifying each affected surface as Primary Decision, Secondary
|
||||
Context, or Tertiary Evidence / Diagnostics and keeping that role in
|
||||
sync with the governing spec,
|
||||
- defining the human-in-the-loop moment and justifying any new Primary
|
||||
Decision Surface against existing decision contexts,
|
||||
- filling the spec’s UI/UX Surface Classification for every affected surface,
|
||||
- filling the spec’s Operator Surface Contract for every affected page,
|
||||
- keeping default-visible content limited to first-decision needs and
|
||||
moving proof, payloads, and diagnostics into progressive disclosure,
|
||||
- implementing the three-tier disclosure hierarchy where applicable:
|
||||
customer-readable decision content first, operator diagnostics
|
||||
second, support/raw evidence third,
|
||||
- making default-visible content operator-first and moving JSON payloads, raw IDs, internal field names, provider error details, and low-level metadata into explicitly revealed diagnostics surfaces,
|
||||
- ensuring customer/read-only default paths do not expose raw JSON,
|
||||
copied context payloads, fingerprints, internal reason ownership,
|
||||
platform reason families, or debug semantics,
|
||||
- keeping each governance case decidable in one focused context where
|
||||
practical instead of forcing cross-page reconstruction,
|
||||
- keeping exactly one dominant next action primary and demoting
|
||||
navigation/debug helpers such as `Open operation`, `Technical
|
||||
details`, or `Show JSON`,
|
||||
- removing duplicate visible status, blocker, reason, impact, or
|
||||
next-action summaries so later sections add evidence instead of
|
||||
restating the same decision truth,
|
||||
- modeling execution outcome, data completeness, governance result, and lifecycle/readiness as distinct status dimensions when applicable,
|
||||
- making mutation scope legible before execution for every state-changing action (`TenantPilot only`, `Microsoft tenant`, or `simulation only`),
|
||||
- implementing the safe-execution flow for dangerous actions (configuration, safety checks/simulation, preview, hard confirmation where required, execute) or documenting an approved exemption,
|
||||
- keeping canonical nouns stable across routes, buttons, run titles, notifications, and audit prose,
|
||||
- keeping navigation aligned to operator workflows rather than storage
|
||||
structures,
|
||||
- ensuring new automation, alerts, or autonomous flows reduce
|
||||
search/review/click load instead of adding noise, extra lists, or
|
||||
extra detail work,
|
||||
- preserving a calm, prioritized default state that distinguishes
|
||||
actionable work from worth-watching context and reference-only
|
||||
information,
|
||||
- keeping scope signals truthful and ensuring critical operational truth is visible by default,
|
||||
- keeping standard CRUD / Registry rows scanable rather than prose-heavy,
|
||||
- keeping workspace and tenant context explicit in navigation, actions, and page semantics so tenant pages do not silently expose workspace-wide actions.
|
||||
**Filament UI Action Surfaces**: If this feature adds/modifies any Filament Resource / RelationManager / Page, tasks MUST include:
|
||||
- filling the spec’s “UI Action Matrix” for all changed surfaces,
|
||||
- assigning exactly one broad action-surface class to every changed
|
||||
operator-facing surface and keeping the detailed surface type in sync
|
||||
with the spec,
|
||||
- identifying the one likely next operator action for each changed
|
||||
surface and shaping the visible hierarchy around it,
|
||||
- implementing required action surfaces (header/row/bulk/empty-state CTA for lists; header actions for view; consistent save/cancel on create/edit),
|
||||
- ensuring every List/Table has exactly one primary inspect/open model with the correct surface-appropriate affordance,
|
||||
- removing redundant View/Inspect actions when row click or identifier click already opens the same destination,
|
||||
- keeping standard CRUD / Registry rows to inspect/open plus at most one inline safe shortcut,
|
||||
- separating navigation from mutation so pure context changes do not
|
||||
compete visually with state-changing actions,
|
||||
- moving additional secondary actions into More or the detail header,
|
||||
- ordering visible actions and grouped actions by meaning, frequency,
|
||||
and risk rather than append order,
|
||||
- placing destructive actions in More or the detail header for standard lists and using catalogued exceptions only where allowed,
|
||||
- ensuring workbench and monitoring surfaces separate scope/context,
|
||||
selection actions, navigation, and object actions instead of mixing
|
||||
them into one flat header zone,
|
||||
- grouping bulk actions via BulkActionGroup,
|
||||
- preventing empty `ActionGroup` / `BulkActionGroup` placeholders,
|
||||
- adding confirmations for destructive actions (and typed confirmation where required by scale),
|
||||
- adding `AuditLog` entries for relevant mutations,
|
||||
- using native Filament components or shared UI primitives before any local Blade/Tailwind assembly for badges, alerts, buttons, and semantic status surfaces,
|
||||
- avoiding page-local semantic color, border, rounding, or highlight styling when Filament props or shared primitives can express the same state,
|
||||
- documenting any workflow-hub, wizard, utility/system, or other
|
||||
special-type exception in the spec/PR and adding dedicated test
|
||||
coverage,
|
||||
- documenting any catalogued UI exception in the spec/PR and adding dedicated test coverage,
|
||||
- documenting any UI-FIL-001 exception with rationale in the spec/PR,
|
||||
- adding/updated tests that enforce the contract and block merge on violations, OR documenting an explicit exemption with rationale.
|
||||
- For any new or modified customer/operator-facing detail surface,
|
||||
tests MUST prove default-visible status/reason/impact/next-action
|
||||
content, exactly one dominant next action, diagnostics-secondary
|
||||
ordering, hidden raw/support detail by default, capability-gated
|
||||
support/raw sections where applicable, and the absence of duplicate
|
||||
visible decision summaries.
|
||||
**Filament UI UX-001 (Layout & IA)**: If this feature adds/modifies any Filament screen, tasks MUST include:
|
||||
- ensuring Create/Edit pages use Main/Aside layout (3-col grid, Main=columnSpan(2), Aside=columnSpan(1)),
|
||||
- ensuring all form fields are inside Sections/Cards (no naked inputs at root schema level),
|
||||
- ensuring View pages use Infolists (not disabled edit forms); status badges use BADGE-001,
|
||||
- ensuring empty states show a specific title + explanation + exactly 1 CTA; non-empty tables move CTA to header,
|
||||
- enforcing ACTSURF-001 / HDR-001 action discipline: record/detail/edit
|
||||
pages keep at most 1 visible primary header action; pure navigation
|
||||
moves to contextual placement; destructive or governance-changing
|
||||
actions are separated and require friction; monitoring/workbench
|
||||
surfaces use their own layered hierarchy; rare actions live in
|
||||
structured Action Groups; every affected surface passes the few-second
|
||||
scan rule,
|
||||
- capping header actions to max 1 primary + 1 secondary (rest grouped),
|
||||
- enforcing HDR-001 header action discipline: at most 1 primary visible action per record/detail page; pure navigation (Open finding, Open tenant, View related run, etc.) placed at the relevant field/badge/relation, NOT in the header; destructive or governance-changing actions separated and requiring friction; rare actions in Action Groups; every record/detail page passing the 5-second scan rule,
|
||||
- using shared layout builders (e.g., `MainAsideForm`, `MainAsideInfolist`, `StandardTableDefaults`) where available,
|
||||
- OR documenting an explicit exemption with rationale if UX-001 is not fully satisfied.
|
||||
**Badges**: If this feature changes status-like badge semantics, tasks MUST use `BadgeCatalog` / `BadgeRenderer` (BADGE-001),
|
||||
@ -171,18 +86,6 @@ # Tasks: [FEATURE NAME]
|
||||
- and adding tests around business consequences, permissions, lifecycle behavior, isolation, or audit responsibilities rather than thin indirection alone.
|
||||
|
||||
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
|
||||
Runtime behavior or test-surface changes MUST include at least one explicit task for lane validation or runtime-impact review so upkeep stays inside the feature instead of becoming separate cleanup.
|
||||
|
||||
## Test Governance Checklist
|
||||
|
||||
Include this short checklist in generated task lists for runtime-changing or test-affecting work. Docs-only or template-only work may mark the items `N/A`.
|
||||
|
||||
- [ ] Lane assignment is named and is the narrowest sufficient proof for the changed behavior.
|
||||
- [ ] New or changed tests stay in the smallest honest family, and any heavy-governance or browser addition is explicit.
|
||||
- [ ] Shared helpers, factories, seeds, fixtures, and context defaults stay cheap by default; any widening is isolated or documented.
|
||||
- [ ] Planned validation commands cover the change without pulling in unrelated lane cost.
|
||||
- [ ] The declared surface test profile or `standard-native-filament` relief is explicit.
|
||||
- [ ] Any material budget, baseline, trend, or escalation note is recorded in the active spec or PR.
|
||||
|
||||
## Format: `[ID] [P?] [Story] Description`
|
||||
|
||||
@ -328,7 +231,6 @@ ## Phase N: Polish & Cross-Cutting Concerns
|
||||
- [ ] TXXX [P] Additional unit tests (if requested) in tests/unit/
|
||||
- [ ] TXXX Security hardening
|
||||
- [ ] TXXX Proportionality cleanup: remove or collapse superseded layers introduced during implementation
|
||||
- [ ] TXXX Record the active feature PR close-out entry with guardrail class, exception status, proof depth, and deferred automation
|
||||
- [ ] TXXX Run quickstart.md validation
|
||||
|
||||
---
|
||||
|
||||
@ -1,63 +0,0 @@
|
||||
schema_version: "1.0"
|
||||
workflow:
|
||||
id: "speckit"
|
||||
name: "Full SDD Cycle"
|
||||
version: "1.0.0"
|
||||
author: "GitHub"
|
||||
description: "Runs specify → plan → tasks → implement with review gates"
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.7.2"
|
||||
integrations:
|
||||
any: ["copilot", "claude", "gemini"]
|
||||
|
||||
inputs:
|
||||
spec:
|
||||
type: string
|
||||
required: true
|
||||
prompt: "Describe what you want to build"
|
||||
integration:
|
||||
type: string
|
||||
default: "copilot"
|
||||
prompt: "Integration to use (e.g. claude, copilot, gemini)"
|
||||
scope:
|
||||
type: string
|
||||
default: "full"
|
||||
enum: ["full", "backend-only", "frontend-only"]
|
||||
|
||||
steps:
|
||||
- id: specify
|
||||
command: speckit.specify
|
||||
integration: "{{ inputs.integration }}"
|
||||
input:
|
||||
args: "{{ inputs.spec }}"
|
||||
|
||||
- id: review-spec
|
||||
type: gate
|
||||
message: "Review the generated spec before planning."
|
||||
options: [approve, reject]
|
||||
on_reject: abort
|
||||
|
||||
- id: plan
|
||||
command: speckit.plan
|
||||
integration: "{{ inputs.integration }}"
|
||||
input:
|
||||
args: "{{ inputs.spec }}"
|
||||
|
||||
- id: review-plan
|
||||
type: gate
|
||||
message: "Review the plan before generating tasks."
|
||||
options: [approve, reject]
|
||||
on_reject: abort
|
||||
|
||||
- id: tasks
|
||||
command: speckit.tasks
|
||||
integration: "{{ inputs.integration }}"
|
||||
input:
|
||||
args: "{{ inputs.spec }}"
|
||||
|
||||
- id: implement
|
||||
command: speckit.implement
|
||||
integration: "{{ inputs.integration }}"
|
||||
input:
|
||||
args: "{{ inputs.spec }}"
|
||||
@ -1,13 +0,0 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"workflows": {
|
||||
"speckit": {
|
||||
"name": "Full SDD Cycle",
|
||||
"version": "1.0.0",
|
||||
"description": "Runs specify \u2192 plan \u2192 tasks \u2192 implement with review gates",
|
||||
"source": "bundled",
|
||||
"installed_at": "2026-04-22T21:58:03.039039+00:00",
|
||||
"updated_at": "2026-04-22T21:58:03.039046+00:00"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -722,7 +722,6 @@ ## Application Structure & Architecture
|
||||
## Frontend Bundling
|
||||
|
||||
- Repo-root JavaScript orchestration now uses `corepack pnpm install`, `corepack pnpm dev:platform`, `corepack pnpm dev:website`, `corepack pnpm dev`, `corepack pnpm build:website`, and `corepack pnpm build:platform`.
|
||||
- `corepack pnpm dev:platform` starts the platform Sail stack and the Laravel panel Vite watcher. `corepack pnpm dev` starts that platform watcher plus the website dev server.
|
||||
- `apps/website` is a standalone Astro app, not a second Laravel runtime, so Boost MCP remains platform-only.
|
||||
- If the user doesn't see a platform frontend change reflected in the UI, it could mean they need to run `cd apps/platform && ./vendor/bin/sail pnpm build`, `cd apps/platform && ./vendor/bin/sail pnpm dev`, or `cd apps/platform && ./vendor/bin/sail composer run dev`. Ask them.
|
||||
|
||||
|
||||
@ -560,7 +560,6 @@ ## Application Structure & Architecture
|
||||
## Frontend Bundling
|
||||
|
||||
- Repo-root JavaScript orchestration now uses `corepack pnpm install`, `corepack pnpm dev:platform`, `corepack pnpm dev:website`, `corepack pnpm dev`, `corepack pnpm build:website`, and `corepack pnpm build:platform`.
|
||||
- `corepack pnpm dev:platform` starts the platform Sail stack and the Laravel panel Vite watcher. `corepack pnpm dev` starts that platform watcher plus the website dev server.
|
||||
- `apps/website` is a standalone Astro app, not a second Laravel runtime, so Boost MCP remains platform-only.
|
||||
- If the user doesn't see a platform frontend change reflected in the UI, it could mean they need to run `cd apps/platform && ./vendor/bin/sail pnpm build`, `cd apps/platform && ./vendor/bin/sail pnpm dev`, or `cd apps/platform && ./vendor/bin/sail composer run dev`. Ask them.
|
||||
|
||||
|
||||
126
README.md
126
README.md
@ -12,17 +12,14 @@ ## Multi-App Topology
|
||||
- repo root: workspace manifests, documentation, scripts, editor tooling, and `docker-compose.yml`
|
||||
- `./scripts/platform-sail`: platform-only compatibility helper for tooling that cannot set `cwd`
|
||||
|
||||
Website-track guardrails for independent evolution live in
|
||||
[`docs/strategy/website-working-contract.md`](docs/strategy/website-working-contract.md).
|
||||
|
||||
## Official Root Commands
|
||||
|
||||
- Install workspace-managed JavaScript dependencies: `corepack pnpm install`
|
||||
- Start the platform stack and Laravel panel Vite watcher: `corepack pnpm dev:platform`
|
||||
- Start the platform stack: `corepack pnpm dev:platform`
|
||||
- Start the website dev server: `corepack pnpm dev:website`
|
||||
- Start platform Vite + website together: `corepack pnpm dev`
|
||||
- Start platform + website together: `corepack pnpm dev`
|
||||
- Build the website: `corepack pnpm build:website`
|
||||
- Build platform frontend assets inside Sail: `corepack pnpm build:platform`
|
||||
- Build platform frontend assets: `corepack pnpm build:platform`
|
||||
|
||||
## App-Local Commands
|
||||
|
||||
@ -32,7 +29,7 @@ ### Platform
|
||||
- Start Sail: `cd apps/platform && ./vendor/bin/sail up -d`
|
||||
- Generate the app key: `cd apps/platform && ./vendor/bin/sail artisan key:generate`
|
||||
- Run migrations and seeders: `cd apps/platform && ./vendor/bin/sail artisan migrate --seed`
|
||||
- Run frontend watch/build inside Sail: `corepack pnpm dev:platform`, `cd apps/platform && ./vendor/bin/sail pnpm dev`, or `cd apps/platform && ./vendor/bin/sail pnpm build`
|
||||
- Run frontend watch/build inside Sail: `cd apps/platform && ./vendor/bin/sail pnpm dev` or `cd apps/platform && ./vendor/bin/sail pnpm build`
|
||||
- Run tests: `cd apps/platform && ./vendor/bin/sail artisan test --compact`
|
||||
|
||||
### Website
|
||||
@ -40,121 +37,6 @@ ### Website
|
||||
- Start the dev server: `cd apps/website && pnpm dev`
|
||||
- Build the static site: `cd apps/website && pnpm build`
|
||||
|
||||
## Test Suite Governance
|
||||
|
||||
### Canonical Lane Commands
|
||||
|
||||
- Preferred repo-root wrappers:
|
||||
- `./scripts/platform-test-lane fast-feedback`
|
||||
- `./scripts/platform-test-lane confidence`
|
||||
- `./scripts/platform-test-lane heavy-governance`
|
||||
- `./scripts/platform-test-lane browser`
|
||||
- `./scripts/platform-test-lane profiling`
|
||||
- `./scripts/platform-test-lane junit`
|
||||
- Regenerate the latest report artifacts without re-running the lane:
|
||||
- `./scripts/platform-test-report fast-feedback`
|
||||
- `./scripts/platform-test-report confidence`
|
||||
- `./scripts/platform-test-report heavy-governance`
|
||||
- `./scripts/platform-test-report browser`
|
||||
- `./scripts/platform-test-report profiling`
|
||||
- `./scripts/platform-test-report junit`
|
||||
- Trend-aware report refresh options:
|
||||
- `--history-file=/absolute/path/to/<lane>-latest.trend-history.json` seeds one prior comparable window explicitly.
|
||||
- `--history-bundle=/absolute/path/to/bundle-or-zip` hydrates the newest matching `trend-history.json` from a staged artifact bundle.
|
||||
- `--fetch-latest-history` asks the wrapper to download the most recent comparable bundle from Gitea when `TENANTATLAS_GITEA_TOKEN` or `GITEA_TOKEN` is available.
|
||||
- `--skip-latest-history` keeps the run intentionally cold-start so the summary reports `unstable` instead of guessing at trend state.
|
||||
- App-local equivalents remain available through Sail Composer scripts:
|
||||
- `cd apps/platform && ./vendor/bin/sail composer run test`
|
||||
- `cd apps/platform && ./vendor/bin/sail composer run test:confidence`
|
||||
- `cd apps/platform && ./vendor/bin/sail composer run test:heavy`
|
||||
- `cd apps/platform && ./vendor/bin/sail composer run test:browser`
|
||||
- `cd apps/platform && ./vendor/bin/sail composer run test:profile`
|
||||
- `cd apps/platform && ./vendor/bin/sail composer run test:junit`
|
||||
- The root wrapper is the safer default for long lanes because it pins Composer to `--timeout=0`.
|
||||
|
||||
### Trend Summary Reading
|
||||
|
||||
- `healthy`: enough comparable samples exist, the lane is comfortably under budget, and recent variance stays inside the documented noise floor.
|
||||
- `budget-near`: the lane is still within budget, but headroom has entered the lane's near-budget band and needs attention before it becomes a repeated blocker.
|
||||
- `trending-worse`: multiple comparable samples are worsening above the lane variance floor even though the lane is not yet clearly over budget.
|
||||
- `regressed`: the lane is over budget or repeatedly worsening enough that ordinary noise is no longer a credible explanation.
|
||||
- `unstable`: the report intentionally refuses a stronger label because history is too short, the comparison fingerprint changed, or the recent window is noisy.
|
||||
- Recalibration is separate from health. Reports can emit candidate, approved, or rejected baseline or budget decisions, but repository truth never moves automatically.
|
||||
- Hotspot evidence may be unavailable on a given cycle. When that happens the summary must say so explicitly, and `profiling` or `junit` remain the preferred support-lane follow-up paths.
|
||||
|
||||
### Workflow Expectation
|
||||
|
||||
- Every runtime-changing or test-affecting spec, plan, and task set MUST record actual test-purpose classification, target validation lane(s), fixture-cost risks, any heavy-governance or browser expansion, any heavy-family visibility change, and any budget/baseline/trend follow-up.
|
||||
- Test classification follows the real proving purpose of the change, not the filename or folder.
|
||||
- Minimal fixtures and minimal infrastructure are the default; database, Livewire, Filament, provider, workspace, membership, or session-heavy setup must stay explicit and opt-in.
|
||||
- Review treats wrong lane fit, hidden default cost, accidental heavy-family growth, or undocumented runtime drift as merge issues, not later cleanup.
|
||||
- Routine lane recalibration belongs inside the affecting feature spec or PR; open a dedicated follow-up spec only when recurring pain or structural lane changes justify it.
|
||||
|
||||
### Authoring And Review Guardrails
|
||||
|
||||
- Start with the smallest honest surface: `Unit` for isolated logic, `Feature` for HTTP, Livewire, Filament, jobs, or non-browser integration, `heavy-governance` for intentionally expensive governance scans, and `Browser` only for end-to-end workflow coverage.
|
||||
- Specs and plans must state the affected lanes or a deliberate `N/A`, the family impact, the setup-cost impact, and the narrowest reviewer command.
|
||||
- If database, Livewire, Filament, provider setup, workspace or membership context, session state, capability context, or browser coverage is required, say why a narrower proof is insufficient.
|
||||
- Keep shared helpers, factories, seeds, fixtures, and defaults cheap by default. Full-context setup should stay behind explicit opt-ins instead of becoming the default path.
|
||||
- Extend an existing heavy or browser family only when the behavior truly matches it. New heavy families, new browser scope, revived expensive defaults, or material lane-cost shifts require explicit escalation.
|
||||
- Low-impact docs-only or template-only work may answer the governance prompts with `N/A` or `none`; do not invent runtime impact where none exists.
|
||||
- Review should end with one explicit outcome: `keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split`.
|
||||
- Use `document-in-feature` for contained drift or cost that belongs in the active feature. Use `follow-up-spec` for recurring pain or structural lane-model changes. Use `reject-or-split` when hidden cost is still unresolved.
|
||||
|
||||
### CI Trigger Matrix
|
||||
|
||||
- Pull requests (`opened`, `reopened`, `synchronize`) run only `./scripts/platform-test-lane fast-feedback` through `.gitea/workflows/test-pr-fast-feedback.yml` and block on test, wrapper, artifact, and mature Fast Feedback budget failures.
|
||||
- Pushes to `dev` run only `./scripts/platform-test-lane confidence` through `.gitea/workflows/test-main-confidence.yml`; test and artifact failures block, while budget drift remains warning-first.
|
||||
- Heavy Governance runs live in `.gitea/workflows/test-heavy-governance.yml` and stay isolated to manual plus scheduled triggers. The schedule remains gated behind `TENANTATLAS_ENABLE_HEAVY_GOVERNANCE_SCHEDULE=1` until the first successful manual validation.
|
||||
- Browser runs live in `.gitea/workflows/test-browser.yml` and stay isolated to manual plus scheduled triggers. The schedule remains gated behind `TENANTATLAS_ENABLE_BROWSER_SCHEDULE=1` until the first successful manual validation.
|
||||
- Fast Feedback uses a documented CI variance allowance of `15s` before budget overrun becomes blocking. Confidence uses `30s`, Browser uses `20s`, and Heavy Governance keeps warning-first or trend-only handling while its CI baseline stabilizes.
|
||||
|
||||
### CI Artifact Bundles
|
||||
|
||||
- Lane-local artifacts are still generated in `apps/platform/storage/logs/test-lanes` as `*-latest.*` files.
|
||||
- CI workflows stage deterministic upload bundles through `./scripts/platform-test-artifacts` into `.gitea-artifacts/<workflow-profile>` before upload.
|
||||
- Every governed CI lane now publishes `summary.md`, `budget.json`, `report.json`, `junit.xml`, and `trend-history.json`. `profiling` may additionally publish `profile.txt`.
|
||||
- The report refresh step hydrates the most recent comparable `trend-history.json` before regenerating the current summary when CI credentials allow it, then republishes the refreshed bounded history for the next run.
|
||||
- Artifact publication failures are first-class blocking failures for pull request and `dev` workflows.
|
||||
|
||||
### Recorded Baselines
|
||||
|
||||
| Scope | Wall clock | Budget | Notes |
|
||||
|-------|------------|--------|-------|
|
||||
| Full suite baseline | `2624.60s` | reference only | Current broad-suite measurement used as the budget anchor |
|
||||
| `fast-feedback` | `176.74s` | `200s` | More than 50% below the current full-suite baseline |
|
||||
| `confidence` | `394.38s` | `450s` | Broader non-browser pre-merge lane |
|
||||
| `heavy-governance` | `83.66s` | `120s` | Seed heavy family lane for architecture, deprecation, ops UX, and action-surface scans |
|
||||
| `browser` | `128.87s` | `150s` | Dedicated browser smoke and workflow lane |
|
||||
| `junit` | `380.14s` | `450s` | Parallel machine-readable report lane for the confidence scope |
|
||||
| `profiling` | `2701.51s` | `3000s` | Serial slow-test drift lane with profile output |
|
||||
|
||||
Artifacts are written under `apps/platform/storage/logs/test-lanes` and kept out of git except for the checked-in skeleton `.gitignore`.
|
||||
|
||||
### Honest Taxonomy Rules
|
||||
|
||||
- `Unit`: isolated logic, helpers, and low-cost domain behavior.
|
||||
- `Feature`: HTTP, Livewire, Filament, jobs, and non-browser integration slices.
|
||||
- `Browser`: only end-to-end browser smoke and workflow coverage under `tests/Browser`.
|
||||
- `heavy-governance`: intentionally expensive architecture, deprecation, ops UX, and wide contract scans. The first seeded batch is `tests/Architecture`, `tests/Deprecation`, `tests/Feature/078`, `tests/Feature/090`, `tests/Feature/144`, `tests/Feature/OpsUx`, `tests/Feature/Filament/Alerts/AlertsKpiHeaderTest.php`, `tests/Feature/Guards/ActionSurfaceContractTest.php`, `tests/Feature/Guards/OperationLifecycleOpsUxGuardTest.php`, and `tests/Feature/ProviderConnections/CredentialLeakGuardTest.php`.
|
||||
|
||||
### Fixture Cost Guidance
|
||||
|
||||
- `createUserWithTenant()` now defaults to the explicit cheap `minimal` profile.
|
||||
- Use `createMinimalUserWithTenant()` in high-usage callers that only need tenant membership and workspace/session wiring.
|
||||
- Use `createStandardUserWithTenant()` or `fixtureProfile: 'standard'` when a test needs a default Microsoft provider connection without credentials, cache resets, or UI context.
|
||||
- Use `createFullUserWithTenant()` or `fixtureProfile: 'full'` when a test intentionally needs provider, credential, cache-reset, and UI-context side effects together.
|
||||
- Use `OperationRun::factory()->minimal()` for system-style runs and `OperationRun::factory()->withUser($user)` only when the initiator identity is materially part of the assertion.
|
||||
- Use `BackupSet::factory()->full()` only when the test really needs backup items; the default backup-set factory path now stays item-free.
|
||||
- `provider-enabled`, `credential-enabled`, `ui-context`, and `heavy` remain available only as temporary transition aliases while the first migration packs are landing.
|
||||
|
||||
### DB Reset and Seed Rules
|
||||
|
||||
- Default lanes use SQLite `:memory:` with `RefreshDatabase` as the reset strategy.
|
||||
- The isolated PostgreSQL coverage remains the `Pgsql` suite and is reserved for schema or foreign-key assertions.
|
||||
- Keep seeds out of default lanes. Opt into seeded fixtures only inside the test that needs business-truth seed data.
|
||||
- Schema-baseline or dump-based acceleration remains a follow-up investigation, not a default requirement for the current lane model.
|
||||
|
||||
## Port Overrides
|
||||
|
||||
- Platform HTTP and Vite ports: set `APP_PORT` and or `VITE_PORT` before `corepack pnpm dev:platform` or `cd apps/platform && ./vendor/bin/sail up -d`
|
||||
|
||||
@ -59,13 +59,6 @@ MAIL_PASSWORD=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
SUPPORT_DESK_ENABLED=false
|
||||
SUPPORT_DESK_NAME="External support desk"
|
||||
SUPPORT_DESK_CREATE_URL=
|
||||
SUPPORT_DESK_API_TOKEN=
|
||||
SUPPORT_DESK_TICKET_URL_TEMPLATE=
|
||||
SUPPORT_DESK_TIMEOUT_SECONDS=5
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/pg-core/columns/json.ts"],"sourcesContent":["import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts';\nimport type { ColumnBaseConfig } from '~/column.ts';\nimport { entityKind } from '~/entity.ts';\nimport type { AnyPgTable } from '~/pg-core/table.ts';\nimport { PgColumn, PgColumnBuilder } from './common.ts';\n\nexport type PgJsonBuilderInitial<TName extends string> = PgJsonBuilder<{\n\tname: TName;\n\tdataType: 'json';\n\tcolumnType: 'PgJson';\n\tdata: unknown;\n\tdriverParam: unknown;\n\tenumValues: undefined;\n}>;\n\nexport class PgJsonBuilder<T extends ColumnBuilderBaseConfig<'json', 'PgJson'>> extends PgColumnBuilder<\n\tT\n> {\n\tstatic override readonly [entityKind]: string = 'PgJsonBuilder';\n\n\tconstructor(name: T['name']) {\n\t\tsuper(name, 'json', 'PgJson');\n\t}\n\n\t/** @internal */\n\toverride build<TTableName extends string>(\n\t\ttable: AnyPgTable<{ name: TTableName }>,\n\t): PgJson<MakeColumnConfig<T, TTableName>> {\n\t\treturn new PgJson<MakeColumnConfig<T, TTableName>>(table, this.config as ColumnBuilderRuntimeConfig<any, any>);\n\t}\n}\n\nexport class PgJson<T extends ColumnBaseConfig<'json', 'PgJson'>> extends PgColumn<T> {\n\tstatic override readonly [entityKind]: string = 'PgJson';\n\n\tconstructor(table: AnyPgTable<{ name: T['tableName'] }>, config: PgJsonBuilder<T>['config']) {\n\t\tsuper(table, config);\n\t}\n\n\tgetSQLType(): string {\n\t\treturn 'json';\n\t}\n\n\toverride mapToDriverValue(value: T['data']): string {\n\t\treturn JSON.stringify(value);\n\t}\n\n\toverride mapFromDriverValue(value: T['data'] | string): T['data'] {\n\t\tif (typeof value === 'string') {\n\t\t\ttry {\n\t\t\t\treturn JSON.parse(value);\n\t\t\t} catch {\n\t\t\t\treturn value as T['data'];\n\t\t\t}\n\t\t}\n\t\treturn value;\n\t}\n}\n\nexport function json(): PgJsonBuilderInitial<''>;\nexport function json<TName extends string>(name: TName): PgJsonBuilderInitial<TName>;\nexport function json(name?: string) {\n\treturn new PgJsonBuilder(name ?? '');\n}\n"],"mappings":"AAEA,SAAS,kBAAkB;AAE3B,SAAS,UAAU,uBAAuB;AAWnC,MAAM,sBAA2E,gBAEtF;AAAA,EACD,QAA0B,UAAU,IAAY;AAAA,EAEhD,YAAY,MAAiB;AAC5B,UAAM,MAAM,QAAQ,QAAQ;AAAA,EAC7B;AAAA;AAAA,EAGS,MACR,OAC0C;AAC1C,WAAO,IAAI,OAAwC,OAAO,KAAK,MAA8C;AAAA,EAC9G;AACD;AAEO,MAAM,eAA6D,SAAY;AAAA,EACrF,QAA0B,UAAU,IAAY;AAAA,EAEhD,YAAY,OAA6C,QAAoC;AAC5F,UAAM,OAAO,MAAM;AAAA,EACpB;AAAA,EAEA,aAAqB;AACpB,WAAO;AAAA,EACR;AAAA,EAES,iBAAiB,OAA0B;AACnD,WAAO,KAAK,UAAU,KAAK;AAAA,EAC5B;AAAA,EAES,mBAAmB,OAAsC;AACjE,QAAI,OAAO,UAAU,UAAU;AAC9B,UAAI;AACH,eAAO,KAAK,MAAM,KAAK;AAAA,MACxB,QAAQ;AACP,eAAO;AAAA,MACR;AAAA,IACD;AACA,WAAO;AAAA,EACR;AACD;AAIO,SAAS,KAAK,MAAe;AACnC,SAAO,IAAI,cAAc,QAAQ,EAAE;AACpC;","names":[]}
|
||||
@ -1,147 +0,0 @@
|
||||
# Prefixing
|
||||
|
||||
## Prefix Styles
|
||||
|
||||
concurrently will by default prefix each command's outputs with a zero-based index, wrapped in square brackets:
|
||||
|
||||
```bash
|
||||
$ concurrently 'echo Hello there' "echo 'General Kenobi!'"
|
||||
[0] Hello there
|
||||
[1] General Kenobi!
|
||||
[0] echo Hello there exited with code 0
|
||||
[1] echo 'General Kenobi!' exited with code 0
|
||||
```
|
||||
|
||||
If you've given the commands names, they are used instead:
|
||||
|
||||
```bash
|
||||
$ concurrently --names one,two 'echo Hello there' "echo 'General Kenobi!'"
|
||||
[one] Hello there
|
||||
[two] General Kenobi!
|
||||
[one] echo Hello there exited with code 0
|
||||
[two] echo 'General Kenobi!' exited with code 0
|
||||
```
|
||||
|
||||
There are other prefix styles available too:
|
||||
|
||||
| Style | Description |
|
||||
| --------- | --------------------------------- |
|
||||
| `index` | Zero-based command's index |
|
||||
| `name` | The command's name |
|
||||
| `command` | The command's line |
|
||||
| `time` | Time of output |
|
||||
| `pid` | ID of the command's process (PID) |
|
||||
| `none` | No prefix |
|
||||
|
||||
Any of these can be used by setting the `--prefix`/`-p` flag. For example:
|
||||
|
||||
```bash
|
||||
$ concurrently --prefix pid 'echo Hello there' 'echo General Kenobi!'
|
||||
[2222] Hello there
|
||||
[2223] General Kenobi!
|
||||
[2222] echo Hello there exited with code 0
|
||||
[2223] echo 'General Kenobi!' exited with code 0
|
||||
```
|
||||
|
||||
It's also possible to have a prefix based on a template. Any of the styles listed above can be used by wrapping it in `{}`.
|
||||
Doing so will also remove the square brackets:
|
||||
|
||||
```bash
|
||||
$ concurrently --prefix '{index}-{pid}' 'echo Hello there' 'echo General Kenobi!'
|
||||
0-2222 Hello there
|
||||
1-2223 General Kenobi!
|
||||
0-2222 echo Hello there exited with code 0
|
||||
1-2223 echo 'General Kenobi!' exited with code 0
|
||||
```
|
||||
|
||||
## Prefix Colors
|
||||
|
||||
By default, there are no colors applied to concurrently prefixes, and they just use whatever the terminal's defaults are.
|
||||
|
||||
This can be changed by using the `--prefix-colors`/`-c` flag, which takes a comma-separated list of colors to use.<br/>
|
||||
The available values are color names (e.g. `green`, `magenta`, `gray`, etc), a hex value (such as `#23de43`), or `auto`, to automatically select a color.
|
||||
|
||||
```bash
|
||||
$ concurrently -c red,blue 'echo Hello there' 'echo General Kenobi!'
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>List of available color names</summary>
|
||||
|
||||
- `black`
|
||||
- `blue`
|
||||
- `cyan`
|
||||
- `green`
|
||||
- `gray`
|
||||
- `magenta`
|
||||
- `red`
|
||||
- `white`
|
||||
- `yellow`
|
||||
</details>
|
||||
|
||||
Colors can take modifiers too. Several can be applied at once by prepending `.<modifier 1>.<modifier 2>` and so on.
|
||||
|
||||
```bash
|
||||
$ concurrently -c red,bold.blue.dim 'echo Hello there' 'echo General Kenobi!'
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>List of available modifiers</summary>
|
||||
|
||||
- `reset`
|
||||
- `bold`
|
||||
- `dim`
|
||||
- `hidden`
|
||||
- `inverse`
|
||||
- `italic`
|
||||
- `strikethrough`
|
||||
- `underline`
|
||||
</details>
|
||||
|
||||
A background color can be set in a similarly fashion.
|
||||
|
||||
```bash
|
||||
$ concurrently -c bgGray,red.bgBlack 'echo Hello there' 'echo General Kenobi!'
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>List of available background color names</summary>
|
||||
|
||||
- `bgBlack`
|
||||
- `bgBlue`
|
||||
- `bgCyan`
|
||||
- `bgGreen`
|
||||
- `bgGray`
|
||||
- `bgMagenta`
|
||||
- `bgRed`
|
||||
- `bgWhite`
|
||||
- `bgYellow`
|
||||
</details>
|
||||
|
||||
## Prefix Length
|
||||
|
||||
When using the `command` prefix style, it's possible that it'll be too long.<br/>
|
||||
It can be limited by setting the `--prefix-length`/`-l` flag:
|
||||
|
||||
```bash
|
||||
$ concurrently -p command -l 10 'echo Hello there' 'echo General Kenobi!'
|
||||
[echo..here] Hello there
|
||||
[echo..bi!'] General Kenobi!
|
||||
[echo..here] echo Hello there exited with code 0
|
||||
[echo..bi!'] echo 'General Kenobi!' exited with code 0
|
||||
```
|
||||
|
||||
It's also possible that some prefixes are too short, and you want all of them to have the same length.<br/>
|
||||
This can be done by setting the `--pad-prefix` flag:
|
||||
|
||||
```bash
|
||||
$ concurrently -n foo,barbaz --pad-prefix 'echo Hello there' 'echo General Kenobi!'
|
||||
[foo ] Hello there
|
||||
[foo ] echo Hello there exited with code 0
|
||||
[barbaz] General Kenobi!
|
||||
[barbaz] echo 'General Kenobi!' exited with code 0
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> If using the `pid` prefix style in combination with [`--restart-tries`](./restarting.md), the length of the PID might grow, in which case all subsequent lines will match the new length.<br/>
|
||||
> This might happen, for example, if the PID was 99 and it's now 100.
|
||||
@ -1,25 +0,0 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var neon_exports = {};
|
||||
module.exports = __toCommonJS(neon_exports);
|
||||
__reExport(neon_exports, require("./neon-auth.cjs"), module.exports);
|
||||
__reExport(neon_exports, require("./rls.cjs"), module.exports);
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
...require("./neon-auth.cjs"),
|
||||
...require("./rls.cjs")
|
||||
});
|
||||
//# sourceMappingURL=index.cjs.map
|
||||
@ -1,102 +0,0 @@
|
||||
import { AjaxRequest } from './types';
|
||||
import { getXHRResponse } from './getXHRResponse';
|
||||
import { createErrorClass } from '../util/createErrorClass';
|
||||
|
||||
/**
|
||||
* A normalized AJAX error.
|
||||
*
|
||||
* @see {@link ajax}
|
||||
*/
|
||||
export interface AjaxError extends Error {
|
||||
/**
|
||||
* The XHR instance associated with the error.
|
||||
*/
|
||||
xhr: XMLHttpRequest;
|
||||
|
||||
/**
|
||||
* The AjaxRequest associated with the error.
|
||||
*/
|
||||
request: AjaxRequest;
|
||||
|
||||
/**
|
||||
* The HTTP status code, if the request has completed. If not,
|
||||
* it is set to `0`.
|
||||
*/
|
||||
status: number;
|
||||
|
||||
/**
|
||||
* The responseType (e.g. 'json', 'arraybuffer', or 'xml').
|
||||
*/
|
||||
responseType: XMLHttpRequestResponseType;
|
||||
|
||||
/**
|
||||
* The response data.
|
||||
*/
|
||||
response: any;
|
||||
}
|
||||
|
||||
export interface AjaxErrorCtor {
|
||||
/**
|
||||
* @deprecated Internal implementation detail. Do not construct error instances.
|
||||
* Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269
|
||||
*/
|
||||
new (message: string, xhr: XMLHttpRequest, request: AjaxRequest): AjaxError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when an error occurs during an AJAX request.
|
||||
* This is only exported because it is useful for checking to see if an error
|
||||
* is an `instanceof AjaxError`. DO NOT create new instances of `AjaxError` with
|
||||
* the constructor.
|
||||
*
|
||||
* @see {@link ajax}
|
||||
*/
|
||||
export const AjaxError: AjaxErrorCtor = createErrorClass(
|
||||
(_super) =>
|
||||
function AjaxErrorImpl(this: any, message: string, xhr: XMLHttpRequest, request: AjaxRequest) {
|
||||
this.message = message;
|
||||
this.name = 'AjaxError';
|
||||
this.xhr = xhr;
|
||||
this.request = request;
|
||||
this.status = xhr.status;
|
||||
this.responseType = xhr.responseType;
|
||||
let response: any;
|
||||
try {
|
||||
// This can throw in IE, because we have to do a JSON.parse of
|
||||
// the response in some cases to get the expected response property.
|
||||
response = getXHRResponse(xhr);
|
||||
} catch (err) {
|
||||
response = xhr.responseText;
|
||||
}
|
||||
this.response = response;
|
||||
}
|
||||
);
|
||||
|
||||
export interface AjaxTimeoutError extends AjaxError {}
|
||||
|
||||
export interface AjaxTimeoutErrorCtor {
|
||||
/**
|
||||
* @deprecated Internal implementation detail. Do not construct error instances.
|
||||
* Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269
|
||||
*/
|
||||
new (xhr: XMLHttpRequest, request: AjaxRequest): AjaxTimeoutError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when an AJAX request times out. Not to be confused with {@link TimeoutError}.
|
||||
*
|
||||
* This is exported only because it is useful for checking to see if errors are an
|
||||
* `instanceof AjaxTimeoutError`. DO NOT use the constructor to create an instance of
|
||||
* this type.
|
||||
*
|
||||
* @see {@link ajax}
|
||||
*/
|
||||
export const AjaxTimeoutError: AjaxTimeoutErrorCtor = (() => {
|
||||
function AjaxTimeoutErrorImpl(this: any, xhr: XMLHttpRequest, request: AjaxRequest) {
|
||||
AjaxError.call(this, 'ajax timeout', xhr, request);
|
||||
this.name = 'AjaxTimeoutError';
|
||||
return this;
|
||||
}
|
||||
AjaxTimeoutErrorImpl.prototype = Object.create(AjaxError.prototype);
|
||||
return AjaxTimeoutErrorImpl;
|
||||
})() as any;
|
||||
@ -1,39 +0,0 @@
|
||||
/**
|
||||
Check if [`argv`](https://nodejs.org/docs/latest/api/process.html#process_process_argv) has a specific flag.
|
||||
|
||||
@param flag - CLI flag to look for. The `--` prefix is optional.
|
||||
@param argv - CLI arguments. Default: `process.argv`.
|
||||
@returns Whether the flag exists.
|
||||
|
||||
@example
|
||||
```
|
||||
// $ ts-node foo.ts -f --unicorn --foo=bar -- --rainbow
|
||||
|
||||
// foo.ts
|
||||
import hasFlag = require('has-flag');
|
||||
|
||||
hasFlag('unicorn');
|
||||
//=> true
|
||||
|
||||
hasFlag('--unicorn');
|
||||
//=> true
|
||||
|
||||
hasFlag('f');
|
||||
//=> true
|
||||
|
||||
hasFlag('-f');
|
||||
//=> true
|
||||
|
||||
hasFlag('foo=bar');
|
||||
//=> true
|
||||
|
||||
hasFlag('foo');
|
||||
//=> false
|
||||
|
||||
hasFlag('rainbow');
|
||||
//=> false
|
||||
```
|
||||
*/
|
||||
declare function hasFlag(flag: string, argv?: string[]): boolean;
|
||||
|
||||
export = hasFlag;
|
||||
@ -1,25 +0,0 @@
|
||||
export * from "./bigint.cjs";
|
||||
export * from "./binary.cjs";
|
||||
export * from "./boolean.cjs";
|
||||
export * from "./char.cjs";
|
||||
export * from "./common.cjs";
|
||||
export * from "./custom.cjs";
|
||||
export * from "./date.cjs";
|
||||
export * from "./datetime.cjs";
|
||||
export * from "./decimal.cjs";
|
||||
export * from "./double.cjs";
|
||||
export * from "./enum.cjs";
|
||||
export * from "./float.cjs";
|
||||
export * from "./int.cjs";
|
||||
export * from "./json.cjs";
|
||||
export * from "./mediumint.cjs";
|
||||
export * from "./real.cjs";
|
||||
export * from "./serial.cjs";
|
||||
export * from "./smallint.cjs";
|
||||
export * from "./text.cjs";
|
||||
export * from "./time.cjs";
|
||||
export * from "./timestamp.cjs";
|
||||
export * from "./tinyint.cjs";
|
||||
export * from "./varbinary.cjs";
|
||||
export * from "./varchar.cjs";
|
||||
export * from "./year.cjs";
|
||||
@ -1,61 +0,0 @@
|
||||
import { Observable } from '../Observable';
|
||||
import { MonoTypeOperatorFunction, ObservableInput } from '../types';
|
||||
/**
|
||||
* Returns an Observable that mirrors the source Observable with the exception of an `error`. If the source Observable
|
||||
* calls `error`, this method will emit the Throwable that caused the error to the `ObservableInput` returned from `notifier`.
|
||||
* If that Observable calls `complete` or `error` then this method will call `complete` or `error` on the child
|
||||
* subscription. Otherwise this method will resubscribe to the source Observable.
|
||||
*
|
||||
* 
|
||||
*
|
||||
* Retry an observable sequence on error based on custom criteria.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```ts
|
||||
* import { interval, map, retryWhen, tap, delayWhen, timer } from 'rxjs';
|
||||
*
|
||||
* const source = interval(1000);
|
||||
* const result = source.pipe(
|
||||
* map(value => {
|
||||
* if (value > 5) {
|
||||
* // error will be picked up by retryWhen
|
||||
* throw value;
|
||||
* }
|
||||
* return value;
|
||||
* }),
|
||||
* retryWhen(errors =>
|
||||
* errors.pipe(
|
||||
* // log error message
|
||||
* tap(value => console.log(`Value ${ value } was too high!`)),
|
||||
* // restart in 5 seconds
|
||||
* delayWhen(value => timer(value * 1000))
|
||||
* )
|
||||
* )
|
||||
* );
|
||||
*
|
||||
* result.subscribe(value => console.log(value));
|
||||
*
|
||||
* // results:
|
||||
* // 0
|
||||
* // 1
|
||||
* // 2
|
||||
* // 3
|
||||
* // 4
|
||||
* // 5
|
||||
* // 'Value 6 was too high!'
|
||||
* // - Wait 5 seconds then repeat
|
||||
* ```
|
||||
*
|
||||
* @see {@link retry}
|
||||
*
|
||||
* @param notifier Function that receives an Observable of notifications with which a
|
||||
* user can `complete` or `error`, aborting the retry.
|
||||
* @return A function that returns an Observable that mirrors the source
|
||||
* Observable with the exception of an `error`.
|
||||
* @deprecated Will be removed in v9 or v10, use {@link retry}'s `delay` option instead.
|
||||
* Will be removed in v9 or v10. Use {@link retry}'s {@link RetryConfig#delay delay} option instead.
|
||||
* Instead of `retryWhen(() => notify$)`, use: `retry({ delay: () => notify$ })`.
|
||||
*/
|
||||
export declare function retryWhen<T>(notifier: (errors: Observable<any>) => ObservableInput<any>): MonoTypeOperatorFunction<T>;
|
||||
//# sourceMappingURL=retryWhen.d.ts.map
|
||||
@ -1,633 +0,0 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var base_exports = {};
|
||||
__export(base_exports, {
|
||||
TerminalReporter: () => TerminalReporter,
|
||||
fitToWidth: () => fitToWidth,
|
||||
formatError: () => formatError,
|
||||
formatFailure: () => formatFailure,
|
||||
formatResultFailure: () => formatResultFailure,
|
||||
formatRetry: () => formatRetry,
|
||||
internalScreen: () => internalScreen,
|
||||
kOutputSymbol: () => kOutputSymbol,
|
||||
markErrorsAsReported: () => markErrorsAsReported,
|
||||
nonTerminalScreen: () => nonTerminalScreen,
|
||||
prepareErrorStack: () => prepareErrorStack,
|
||||
relativeFilePath: () => relativeFilePath,
|
||||
resolveOutputFile: () => resolveOutputFile,
|
||||
separator: () => separator,
|
||||
stepSuffix: () => stepSuffix,
|
||||
terminalScreen: () => terminalScreen
|
||||
});
|
||||
module.exports = __toCommonJS(base_exports);
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_utils2 = require("playwright-core/lib/utils");
|
||||
var import_util = require("../util");
|
||||
var import_utilsBundle = require("../utilsBundle");
|
||||
const kOutputSymbol = Symbol("output");
|
||||
const DEFAULT_TTY_WIDTH = 100;
|
||||
const DEFAULT_TTY_HEIGHT = 40;
|
||||
const originalProcessStdout = process.stdout;
|
||||
const originalProcessStderr = process.stderr;
|
||||
const terminalScreen = (() => {
|
||||
let isTTY = !!originalProcessStdout.isTTY;
|
||||
let ttyWidth = originalProcessStdout.columns || 0;
|
||||
let ttyHeight = originalProcessStdout.rows || 0;
|
||||
if (process.env.PLAYWRIGHT_FORCE_TTY === "false" || process.env.PLAYWRIGHT_FORCE_TTY === "0") {
|
||||
isTTY = false;
|
||||
ttyWidth = 0;
|
||||
ttyHeight = 0;
|
||||
} else if (process.env.PLAYWRIGHT_FORCE_TTY === "true" || process.env.PLAYWRIGHT_FORCE_TTY === "1") {
|
||||
isTTY = true;
|
||||
ttyWidth = originalProcessStdout.columns || DEFAULT_TTY_WIDTH;
|
||||
ttyHeight = originalProcessStdout.rows || DEFAULT_TTY_HEIGHT;
|
||||
} else if (process.env.PLAYWRIGHT_FORCE_TTY) {
|
||||
isTTY = true;
|
||||
const sizeMatch = process.env.PLAYWRIGHT_FORCE_TTY.match(/^(\d+)x(\d+)$/);
|
||||
if (sizeMatch) {
|
||||
ttyWidth = +sizeMatch[1];
|
||||
ttyHeight = +sizeMatch[2];
|
||||
} else {
|
||||
ttyWidth = +process.env.PLAYWRIGHT_FORCE_TTY;
|
||||
ttyHeight = DEFAULT_TTY_HEIGHT;
|
||||
}
|
||||
if (isNaN(ttyWidth))
|
||||
ttyWidth = DEFAULT_TTY_WIDTH;
|
||||
if (isNaN(ttyHeight))
|
||||
ttyHeight = DEFAULT_TTY_HEIGHT;
|
||||
}
|
||||
let useColors = isTTY;
|
||||
if (process.env.DEBUG_COLORS === "0" || process.env.DEBUG_COLORS === "false" || process.env.FORCE_COLOR === "0" || process.env.FORCE_COLOR === "false")
|
||||
useColors = false;
|
||||
else if (process.env.DEBUG_COLORS || process.env.FORCE_COLOR)
|
||||
useColors = true;
|
||||
const colors = useColors ? import_utils2.colors : import_utils2.noColors;
|
||||
return {
|
||||
resolveFiles: "cwd",
|
||||
isTTY,
|
||||
ttyWidth,
|
||||
ttyHeight,
|
||||
colors,
|
||||
stdout: originalProcessStdout,
|
||||
stderr: originalProcessStderr
|
||||
};
|
||||
})();
|
||||
const nonTerminalScreen = {
|
||||
colors: terminalScreen.colors,
|
||||
isTTY: false,
|
||||
ttyWidth: 0,
|
||||
ttyHeight: 0,
|
||||
resolveFiles: "rootDir"
|
||||
};
|
||||
const internalScreen = {
|
||||
colors: import_utils2.colors,
|
||||
isTTY: false,
|
||||
ttyWidth: 0,
|
||||
ttyHeight: 0,
|
||||
resolveFiles: "rootDir"
|
||||
};
|
||||
class TerminalReporter {
|
||||
constructor(options = {}) {
|
||||
this.totalTestCount = 0;
|
||||
this.fileDurations = /* @__PURE__ */ new Map();
|
||||
this._fatalErrors = [];
|
||||
this._failureCount = 0;
|
||||
this.screen = options.screen ?? terminalScreen;
|
||||
this._options = options;
|
||||
}
|
||||
version() {
|
||||
return "v2";
|
||||
}
|
||||
onConfigure(config) {
|
||||
this.config = config;
|
||||
}
|
||||
onBegin(suite) {
|
||||
this.suite = suite;
|
||||
this.totalTestCount = suite.allTests().length;
|
||||
}
|
||||
onStdOut(chunk, test, result) {
|
||||
this._appendOutput({ chunk, type: "stdout" }, result);
|
||||
}
|
||||
onStdErr(chunk, test, result) {
|
||||
this._appendOutput({ chunk, type: "stderr" }, result);
|
||||
}
|
||||
_appendOutput(output, result) {
|
||||
if (!result)
|
||||
return;
|
||||
result[kOutputSymbol] = result[kOutputSymbol] || [];
|
||||
result[kOutputSymbol].push(output);
|
||||
}
|
||||
onTestEnd(test, result) {
|
||||
if (result.status !== "skipped" && result.status !== test.expectedStatus)
|
||||
++this._failureCount;
|
||||
const projectName = test.titlePath()[1];
|
||||
const relativePath = relativeTestPath(this.screen, this.config, test);
|
||||
const fileAndProject = (projectName ? `[${projectName}] \u203A ` : "") + relativePath;
|
||||
const entry = this.fileDurations.get(fileAndProject) || { duration: 0, workers: /* @__PURE__ */ new Set() };
|
||||
entry.duration += result.duration;
|
||||
entry.workers.add(result.workerIndex);
|
||||
this.fileDurations.set(fileAndProject, entry);
|
||||
}
|
||||
onError(error) {
|
||||
this._fatalErrors.push(error);
|
||||
}
|
||||
async onEnd(result) {
|
||||
this.result = result;
|
||||
}
|
||||
fitToScreen(line, prefix) {
|
||||
if (!this.screen.ttyWidth) {
|
||||
return line;
|
||||
}
|
||||
return fitToWidth(line, this.screen.ttyWidth, prefix);
|
||||
}
|
||||
generateStartingMessage() {
|
||||
const jobs = this.config.metadata.actualWorkers ?? this.config.workers;
|
||||
const shardDetails = this.config.shard ? `, shard ${this.config.shard.current} of ${this.config.shard.total}` : "";
|
||||
if (!this.totalTestCount)
|
||||
return "";
|
||||
return "\n" + this.screen.colors.dim("Running ") + this.totalTestCount + this.screen.colors.dim(` test${this.totalTestCount !== 1 ? "s" : ""} using `) + jobs + this.screen.colors.dim(` worker${jobs !== 1 ? "s" : ""}${shardDetails}`);
|
||||
}
|
||||
getSlowTests() {
|
||||
if (!this.config.reportSlowTests)
|
||||
return [];
|
||||
const fileDurations = [...this.fileDurations.entries()].filter(([key, value]) => value.workers.size === 1).map(([key, value]) => [key, value.duration]);
|
||||
fileDurations.sort((a, b) => b[1] - a[1]);
|
||||
const count = Math.min(fileDurations.length, this.config.reportSlowTests.max || Number.POSITIVE_INFINITY);
|
||||
const threshold = this.config.reportSlowTests.threshold;
|
||||
return fileDurations.filter(([, duration]) => duration > threshold).slice(0, count);
|
||||
}
|
||||
generateSummaryMessage({ didNotRun, skipped, expected, interrupted, unexpected, flaky, fatalErrors }) {
|
||||
const tokens = [];
|
||||
if (unexpected.length) {
|
||||
tokens.push(this.screen.colors.red(` ${unexpected.length} failed`));
|
||||
for (const test of unexpected)
|
||||
tokens.push(this.screen.colors.red(this.formatTestHeader(test, { indent: " " })));
|
||||
}
|
||||
if (interrupted.length) {
|
||||
tokens.push(this.screen.colors.yellow(` ${interrupted.length} interrupted`));
|
||||
for (const test of interrupted)
|
||||
tokens.push(this.screen.colors.yellow(this.formatTestHeader(test, { indent: " " })));
|
||||
}
|
||||
if (flaky.length) {
|
||||
tokens.push(this.screen.colors.yellow(` ${flaky.length} flaky`));
|
||||
for (const test of flaky)
|
||||
tokens.push(this.screen.colors.yellow(this.formatTestHeader(test, { indent: " " })));
|
||||
}
|
||||
if (skipped)
|
||||
tokens.push(this.screen.colors.yellow(` ${skipped} skipped`));
|
||||
if (didNotRun)
|
||||
tokens.push(this.screen.colors.yellow(` ${didNotRun} did not run`));
|
||||
if (expected)
|
||||
tokens.push(this.screen.colors.green(` ${expected} passed`) + this.screen.colors.dim(` (${(0, import_utils.msToString)(this.result.duration)})`));
|
||||
if (fatalErrors.length && expected + unexpected.length + interrupted.length + flaky.length > 0)
|
||||
tokens.push(this.screen.colors.red(` ${fatalErrors.length === 1 ? "1 error was not a part of any test" : fatalErrors.length + " errors were not a part of any test"}, see above for details`));
|
||||
return tokens.join("\n");
|
||||
}
|
||||
generateSummary() {
|
||||
let didNotRun = 0;
|
||||
let skipped = 0;
|
||||
let expected = 0;
|
||||
const interrupted = [];
|
||||
const interruptedToPrint = [];
|
||||
const unexpected = [];
|
||||
const flaky = [];
|
||||
this.suite.allTests().forEach((test) => {
|
||||
switch (test.outcome()) {
|
||||
case "skipped": {
|
||||
if (test.results.some((result) => result.status === "interrupted")) {
|
||||
if (test.results.some((result) => !!result.error))
|
||||
interruptedToPrint.push(test);
|
||||
interrupted.push(test);
|
||||
} else if (!test.results.length || test.expectedStatus !== "skipped") {
|
||||
++didNotRun;
|
||||
} else {
|
||||
++skipped;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "expected":
|
||||
++expected;
|
||||
break;
|
||||
case "unexpected":
|
||||
unexpected.push(test);
|
||||
break;
|
||||
case "flaky":
|
||||
flaky.push(test);
|
||||
break;
|
||||
}
|
||||
});
|
||||
const failuresToPrint = [...unexpected, ...flaky, ...interruptedToPrint];
|
||||
return {
|
||||
didNotRun,
|
||||
skipped,
|
||||
expected,
|
||||
interrupted,
|
||||
unexpected,
|
||||
flaky,
|
||||
failuresToPrint,
|
||||
fatalErrors: this._fatalErrors
|
||||
};
|
||||
}
|
||||
epilogue(full) {
|
||||
const summary = this.generateSummary();
|
||||
const summaryMessage = this.generateSummaryMessage(summary);
|
||||
if (full && summary.failuresToPrint.length && !this._options.omitFailures)
|
||||
this._printFailures(summary.failuresToPrint);
|
||||
this._printSlowTests();
|
||||
this._printSummary(summaryMessage);
|
||||
}
|
||||
_printFailures(failures) {
|
||||
this.writeLine("");
|
||||
failures.forEach((test, index) => {
|
||||
this.writeLine(this.formatFailure(test, index + 1));
|
||||
});
|
||||
}
|
||||
_printSlowTests() {
|
||||
const slowTests = this.getSlowTests();
|
||||
slowTests.forEach(([file, duration]) => {
|
||||
this.writeLine(this.screen.colors.yellow(" Slow test file: ") + file + this.screen.colors.yellow(` (${(0, import_utils.msToString)(duration)})`));
|
||||
});
|
||||
if (slowTests.length)
|
||||
this.writeLine(this.screen.colors.yellow(" Consider running tests from slow files in parallel. See: https://playwright.dev/docs/test-parallel"));
|
||||
}
|
||||
_printSummary(summary) {
|
||||
if (summary.trim())
|
||||
this.writeLine(summary);
|
||||
}
|
||||
willRetry(test) {
|
||||
return test.outcome() === "unexpected" && test.results.length <= test.retries;
|
||||
}
|
||||
formatTestTitle(test, step) {
|
||||
return formatTestTitle(this.screen, this.config, test, step, this._options);
|
||||
}
|
||||
formatTestHeader(test, options = {}) {
|
||||
return formatTestHeader(this.screen, this.config, test, { ...options, includeTestId: this._options.includeTestId });
|
||||
}
|
||||
formatFailure(test, index) {
|
||||
return formatFailure(this.screen, this.config, test, index, this._options);
|
||||
}
|
||||
formatError(error) {
|
||||
return formatError(this.screen, error);
|
||||
}
|
||||
formatResultErrors(test, result) {
|
||||
return formatResultErrors(this.screen, test, result);
|
||||
}
|
||||
writeLine(line) {
|
||||
this.screen.stdout?.write(line ? line + "\n" : "\n");
|
||||
}
|
||||
}
|
||||
function formatResultErrors(screen, test, result) {
|
||||
const lines = [];
|
||||
if (test.outcome() === "unexpected") {
|
||||
const errorDetails = formatResultFailure(screen, test, result, " ");
|
||||
if (errorDetails.length > 0)
|
||||
lines.push("");
|
||||
for (const error of errorDetails)
|
||||
lines.push(error.message, "");
|
||||
}
|
||||
return lines.join("\n");
|
||||
}
|
||||
function formatFailure(screen, config, test, index, options) {
|
||||
const lines = [];
|
||||
let printedHeader = false;
|
||||
for (const result of test.results) {
|
||||
const resultLines = [];
|
||||
const errors = formatResultFailure(screen, test, result, " ");
|
||||
if (!errors.length)
|
||||
continue;
|
||||
if (!printedHeader) {
|
||||
const header = formatTestHeader(screen, config, test, { indent: " ", index, mode: "error", includeTestId: options?.includeTestId });
|
||||
lines.push(screen.colors.red(header));
|
||||
printedHeader = true;
|
||||
}
|
||||
if (result.retry) {
|
||||
resultLines.push("");
|
||||
resultLines.push(screen.colors.gray(separator(screen, ` Retry #${result.retry}`)));
|
||||
}
|
||||
resultLines.push(...errors.map((error) => "\n" + error.message));
|
||||
const attachmentGroups = groupAttachments(result.attachments);
|
||||
for (let i = 0; i < attachmentGroups.length; ++i) {
|
||||
const attachment = attachmentGroups[i];
|
||||
if (attachment.name === "error-context" && attachment.path) {
|
||||
resultLines.push("");
|
||||
resultLines.push(screen.colors.dim(` Error Context: ${relativeFilePath(screen, config, attachment.path)}`));
|
||||
continue;
|
||||
}
|
||||
if (attachment.name.startsWith("_"))
|
||||
continue;
|
||||
const hasPrintableContent = attachment.contentType.startsWith("text/");
|
||||
if (!attachment.path && !hasPrintableContent)
|
||||
continue;
|
||||
resultLines.push("");
|
||||
resultLines.push(screen.colors.dim(separator(screen, ` attachment #${i + 1}: ${screen.colors.bold(attachment.name)} (${attachment.contentType})`)));
|
||||
if (attachment.actual?.path) {
|
||||
if (attachment.expected?.path) {
|
||||
const expectedPath = relativeFilePath(screen, config, attachment.expected.path);
|
||||
resultLines.push(screen.colors.dim(` Expected: ${expectedPath}`));
|
||||
}
|
||||
const actualPath = relativeFilePath(screen, config, attachment.actual.path);
|
||||
resultLines.push(screen.colors.dim(` Received: ${actualPath}`));
|
||||
if (attachment.previous?.path) {
|
||||
const previousPath = relativeFilePath(screen, config, attachment.previous.path);
|
||||
resultLines.push(screen.colors.dim(` Previous: ${previousPath}`));
|
||||
}
|
||||
if (attachment.diff?.path) {
|
||||
const diffPath = relativeFilePath(screen, config, attachment.diff.path);
|
||||
resultLines.push(screen.colors.dim(` Diff: ${diffPath}`));
|
||||
}
|
||||
} else if (attachment.path) {
|
||||
const relativePath = relativeFilePath(screen, config, attachment.path);
|
||||
resultLines.push(screen.colors.dim(` ${relativePath}`));
|
||||
if (attachment.name === "trace") {
|
||||
const packageManagerCommand = (0, import_utils.getPackageManagerExecCommand)();
|
||||
resultLines.push(screen.colors.dim(` Usage:`));
|
||||
resultLines.push("");
|
||||
resultLines.push(screen.colors.dim(` ${packageManagerCommand} playwright show-trace ${quotePathIfNeeded(relativePath)}`));
|
||||
resultLines.push("");
|
||||
}
|
||||
} else {
|
||||
if (attachment.contentType.startsWith("text/") && attachment.body) {
|
||||
let text = attachment.body.toString();
|
||||
if (text.length > 300)
|
||||
text = text.slice(0, 300) + "...";
|
||||
for (const line of text.split("\n"))
|
||||
resultLines.push(screen.colors.dim(` ${line}`));
|
||||
}
|
||||
}
|
||||
resultLines.push(screen.colors.dim(separator(screen, " ")));
|
||||
}
|
||||
lines.push(...resultLines);
|
||||
}
|
||||
lines.push("");
|
||||
return lines.join("\n");
|
||||
}
|
||||
function formatRetry(screen, result) {
|
||||
const retryLines = [];
|
||||
if (result.retry) {
|
||||
retryLines.push("");
|
||||
retryLines.push(screen.colors.gray(separator(screen, ` Retry #${result.retry}`)));
|
||||
}
|
||||
return retryLines;
|
||||
}
|
||||
function quotePathIfNeeded(path2) {
|
||||
if (/\s/.test(path2))
|
||||
return `"${path2}"`;
|
||||
return path2;
|
||||
}
|
||||
const kReportedSymbol = Symbol("reported");
|
||||
function markErrorsAsReported(result) {
|
||||
result[kReportedSymbol] = result.errors.length;
|
||||
}
|
||||
function formatResultFailure(screen, test, result, initialIndent) {
|
||||
const errorDetails = [];
|
||||
if (result.status === "passed" && test.expectedStatus === "failed") {
|
||||
errorDetails.push({
|
||||
message: indent(screen.colors.red(`Expected to fail, but passed.`), initialIndent)
|
||||
});
|
||||
}
|
||||
if (result.status === "interrupted") {
|
||||
errorDetails.push({
|
||||
message: indent(screen.colors.red(`Test was interrupted.`), initialIndent)
|
||||
});
|
||||
}
|
||||
const reportedIndex = result[kReportedSymbol] || 0;
|
||||
for (const error of result.errors.slice(reportedIndex)) {
|
||||
const formattedError = formatError(screen, error);
|
||||
errorDetails.push({
|
||||
message: indent(formattedError.message, initialIndent),
|
||||
location: formattedError.location
|
||||
});
|
||||
}
|
||||
return errorDetails;
|
||||
}
|
||||
function relativeFilePath(screen, config, file) {
|
||||
if (screen.resolveFiles === "cwd")
|
||||
return import_path.default.relative(process.cwd(), file);
|
||||
return import_path.default.relative(config.rootDir, file);
|
||||
}
|
||||
function relativeTestPath(screen, config, test) {
|
||||
return relativeFilePath(screen, config, test.location.file);
|
||||
}
|
||||
function stepSuffix(step) {
|
||||
const stepTitles = step ? step.titlePath() : [];
|
||||
return stepTitles.map((t) => t.split("\n")[0]).map((t) => " \u203A " + t).join("");
|
||||
}
|
||||
function formatTestTitle(screen, config, test, step, options = {}) {
|
||||
const [, projectName, , ...titles] = test.titlePath();
|
||||
const location = `${relativeTestPath(screen, config, test)}:${test.location.line}:${test.location.column}`;
|
||||
const testId = options.includeTestId ? `[id=${test.id}] ` : "";
|
||||
const projectLabel = options.includeTestId ? `project=` : "";
|
||||
const projectTitle = projectName ? `[${projectLabel}${projectName}] \u203A ` : "";
|
||||
const testTitle = `${testId}${projectTitle}${location} \u203A ${titles.join(" \u203A ")}`;
|
||||
const extraTags = test.tags.filter((t) => !testTitle.includes(t) && !config.tags.includes(t));
|
||||
return `${testTitle}${stepSuffix(step)}${extraTags.length ? " " + extraTags.join(" ") : ""}`;
|
||||
}
|
||||
function formatTestHeader(screen, config, test, options = {}) {
|
||||
const title = formatTestTitle(screen, config, test, void 0, options);
|
||||
const header = `${options.indent || ""}${options.index ? options.index + ") " : ""}${title}`;
|
||||
let fullHeader = header;
|
||||
if (options.mode === "error") {
|
||||
const stepPaths = /* @__PURE__ */ new Set();
|
||||
for (const result of test.results.filter((r) => !!r.errors.length)) {
|
||||
const stepPath = [];
|
||||
const visit = (steps) => {
|
||||
const errors = steps.filter((s) => s.error);
|
||||
if (errors.length > 1)
|
||||
return;
|
||||
if (errors.length === 1 && errors[0].category === "test.step") {
|
||||
stepPath.push(errors[0].title);
|
||||
visit(errors[0].steps);
|
||||
}
|
||||
};
|
||||
visit(result.steps);
|
||||
stepPaths.add(["", ...stepPath].join(" \u203A "));
|
||||
}
|
||||
fullHeader = header + (stepPaths.size === 1 ? stepPaths.values().next().value : "");
|
||||
}
|
||||
return separator(screen, fullHeader);
|
||||
}
|
||||
function formatError(screen, error) {
|
||||
const message = error.message || error.value || "";
|
||||
const stack = error.stack;
|
||||
if (!stack && !error.location)
|
||||
return { message };
|
||||
const tokens = [];
|
||||
const parsedStack = stack ? prepareErrorStack(stack) : void 0;
|
||||
tokens.push(parsedStack?.message || message);
|
||||
if (error.snippet) {
|
||||
let snippet = error.snippet;
|
||||
if (!screen.colors.enabled)
|
||||
snippet = (0, import_util.stripAnsiEscapes)(snippet);
|
||||
tokens.push("");
|
||||
tokens.push(snippet);
|
||||
}
|
||||
if (parsedStack && parsedStack.stackLines.length)
|
||||
tokens.push(screen.colors.dim(parsedStack.stackLines.join("\n")));
|
||||
let location = error.location;
|
||||
if (parsedStack && !location)
|
||||
location = parsedStack.location;
|
||||
if (error.cause)
|
||||
tokens.push(screen.colors.dim("[cause]: ") + formatError(screen, error.cause).message);
|
||||
return {
|
||||
location,
|
||||
message: tokens.join("\n")
|
||||
};
|
||||
}
|
||||
function separator(screen, text = "") {
|
||||
if (text)
|
||||
text += " ";
|
||||
const columns = Math.min(100, screen.ttyWidth || 100);
|
||||
return text + screen.colors.dim("\u2500".repeat(Math.max(0, columns - (0, import_util.stripAnsiEscapes)(text).length)));
|
||||
}
|
||||
function indent(lines, tab) {
|
||||
return lines.replace(/^(?=.+$)/gm, tab);
|
||||
}
|
||||
function prepareErrorStack(stack) {
|
||||
return (0, import_utils.parseErrorStack)(stack, import_path.default.sep, !!process.env.PWDEBUGIMPL);
|
||||
}
|
||||
function characterWidth(c) {
|
||||
return import_utilsBundle.getEastAsianWidth.eastAsianWidth(c.codePointAt(0));
|
||||
}
|
||||
function stringWidth(v) {
|
||||
let width = 0;
|
||||
for (const { segment } of new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(v))
|
||||
width += characterWidth(segment);
|
||||
return width;
|
||||
}
|
||||
function suffixOfWidth(v, width) {
|
||||
const segments = [...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(v)];
|
||||
let suffixBegin = v.length;
|
||||
for (const { segment, index } of segments.reverse()) {
|
||||
const segmentWidth = stringWidth(segment);
|
||||
if (segmentWidth > width)
|
||||
break;
|
||||
width -= segmentWidth;
|
||||
suffixBegin = index;
|
||||
}
|
||||
return v.substring(suffixBegin);
|
||||
}
|
||||
function fitToWidth(line, width, prefix) {
|
||||
const prefixLength = prefix ? (0, import_util.stripAnsiEscapes)(prefix).length : 0;
|
||||
width -= prefixLength;
|
||||
if (stringWidth(line) <= width)
|
||||
return line;
|
||||
const parts = line.split(import_util.ansiRegex);
|
||||
const taken = [];
|
||||
for (let i = parts.length - 1; i >= 0; i--) {
|
||||
if (i % 2) {
|
||||
taken.push(parts[i]);
|
||||
} else {
|
||||
let part = suffixOfWidth(parts[i], width);
|
||||
const wasTruncated = part.length < parts[i].length;
|
||||
if (wasTruncated && parts[i].length > 0) {
|
||||
part = "\u2026" + suffixOfWidth(parts[i], width - 1);
|
||||
}
|
||||
taken.push(part);
|
||||
width -= stringWidth(part);
|
||||
}
|
||||
}
|
||||
return taken.reverse().join("");
|
||||
}
|
||||
function resolveFromEnv(name) {
|
||||
const value = process.env[name];
|
||||
if (value)
|
||||
return import_path.default.resolve(process.cwd(), value);
|
||||
return void 0;
|
||||
}
|
||||
function resolveOutputFile(reporterName, options) {
|
||||
const name = reporterName.toUpperCase();
|
||||
let outputFile = resolveFromEnv(`PLAYWRIGHT_${name}_OUTPUT_FILE`);
|
||||
if (!outputFile && options.outputFile)
|
||||
outputFile = import_path.default.resolve(options.configDir, options.outputFile);
|
||||
if (outputFile)
|
||||
return { outputFile };
|
||||
let outputDir = resolveFromEnv(`PLAYWRIGHT_${name}_OUTPUT_DIR`);
|
||||
if (!outputDir && options.outputDir)
|
||||
outputDir = import_path.default.resolve(options.configDir, options.outputDir);
|
||||
if (!outputDir && options.default)
|
||||
outputDir = (0, import_util.resolveReporterOutputPath)(options.default.outputDir, options.configDir, void 0);
|
||||
if (!outputDir)
|
||||
outputDir = options.configDir;
|
||||
const reportName = process.env[`PLAYWRIGHT_${name}_OUTPUT_NAME`] ?? options.fileName ?? options.default?.fileName;
|
||||
if (!reportName)
|
||||
return void 0;
|
||||
outputFile = import_path.default.resolve(outputDir, reportName);
|
||||
return { outputFile, outputDir };
|
||||
}
|
||||
function groupAttachments(attachments) {
|
||||
const result = [];
|
||||
const attachmentsByPrefix = /* @__PURE__ */ new Map();
|
||||
for (const attachment of attachments) {
|
||||
if (!attachment.path) {
|
||||
result.push(attachment);
|
||||
continue;
|
||||
}
|
||||
const match = attachment.name.match(/^(.*)-(expected|actual|diff|previous)(\.[^.]+)?$/);
|
||||
if (!match) {
|
||||
result.push(attachment);
|
||||
continue;
|
||||
}
|
||||
const [, name, category] = match;
|
||||
let group = attachmentsByPrefix.get(name);
|
||||
if (!group) {
|
||||
group = { ...attachment, name };
|
||||
attachmentsByPrefix.set(name, group);
|
||||
result.push(group);
|
||||
}
|
||||
if (category === "expected")
|
||||
group.expected = attachment;
|
||||
else if (category === "actual")
|
||||
group.actual = attachment;
|
||||
else if (category === "diff")
|
||||
group.diff = attachment;
|
||||
else if (category === "previous")
|
||||
group.previous = attachment;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
TerminalReporter,
|
||||
fitToWidth,
|
||||
formatError,
|
||||
formatFailure,
|
||||
formatResultFailure,
|
||||
formatRetry,
|
||||
internalScreen,
|
||||
kOutputSymbol,
|
||||
markErrorsAsReported,
|
||||
nonTerminalScreen,
|
||||
prepareErrorStack,
|
||||
relativeFilePath,
|
||||
resolveOutputFile,
|
||||
separator,
|
||||
stepSuffix,
|
||||
terminalScreen
|
||||
});
|
||||
@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/mysql-core/columns/serial.ts"],"sourcesContent":["import type {\n\tColumnBuilderBaseConfig,\n\tColumnBuilderRuntimeConfig,\n\tHasDefault,\n\tIsAutoincrement,\n\tIsPrimaryKey,\n\tMakeColumnConfig,\n\tNotNull,\n} from '~/column-builder.ts';\nimport type { ColumnBaseConfig } from '~/column.ts';\nimport { entityKind } from '~/entity.ts';\nimport type { AnyMySqlTable } from '~/mysql-core/table.ts';\nimport { MySqlColumnBuilderWithAutoIncrement, MySqlColumnWithAutoIncrement } from './common.ts';\n\nexport type MySqlSerialBuilderInitial<TName extends string> = IsAutoincrement<\n\tIsPrimaryKey<\n\t\tNotNull<\n\t\t\tHasDefault<\n\t\t\t\tMySqlSerialBuilder<{\n\t\t\t\t\tname: TName;\n\t\t\t\t\tdataType: 'number';\n\t\t\t\t\tcolumnType: 'MySqlSerial';\n\t\t\t\t\tdata: number;\n\t\t\t\t\tdriverParam: number;\n\t\t\t\t\tenumValues: undefined;\n\t\t\t\t}>\n\t\t\t>\n\t\t>\n\t>\n>;\n\nexport class MySqlSerialBuilder<T extends ColumnBuilderBaseConfig<'number', 'MySqlSerial'>>\n\textends MySqlColumnBuilderWithAutoIncrement<T>\n{\n\tstatic override readonly [entityKind]: string = 'MySqlSerialBuilder';\n\n\tconstructor(name: T['name']) {\n\t\tsuper(name, 'number', 'MySqlSerial');\n\t\tthis.config.hasDefault = true;\n\t\tthis.config.autoIncrement = true;\n\t}\n\n\t/** @internal */\n\toverride build<TTableName extends string>(\n\t\ttable: AnyMySqlTable<{ name: TTableName }>,\n\t): MySqlSerial<MakeColumnConfig<T, TTableName>> {\n\t\treturn new MySqlSerial<MakeColumnConfig<T, TTableName>>(table, this.config as ColumnBuilderRuntimeConfig<any, any>);\n\t}\n}\n\nexport class MySqlSerial<\n\tT extends ColumnBaseConfig<'number', 'MySqlSerial'>,\n> extends MySqlColumnWithAutoIncrement<T> {\n\tstatic override readonly [entityKind]: string = 'MySqlSerial';\n\n\tgetSQLType(): string {\n\t\treturn 'serial';\n\t}\n\n\toverride mapFromDriverValue(value: number | string): number {\n\t\tif (typeof value === 'string') {\n\t\t\treturn Number(value);\n\t\t}\n\t\treturn value;\n\t}\n}\n\nexport function serial(): MySqlSerialBuilderInitial<''>;\nexport function serial<TName extends string>(name: TName): MySqlSerialBuilderInitial<TName>;\nexport function serial(name?: string) {\n\treturn new MySqlSerialBuilder(name ?? '');\n}\n"],"mappings":"AAUA,SAAS,kBAAkB;AAE3B,SAAS,qCAAqC,oCAAoC;AAmB3E,MAAM,2BACJ,oCACT;AAAA,EACC,QAA0B,UAAU,IAAY;AAAA,EAEhD,YAAY,MAAiB;AAC5B,UAAM,MAAM,UAAU,aAAa;AACnC,SAAK,OAAO,aAAa;AACzB,SAAK,OAAO,gBAAgB;AAAA,EAC7B;AAAA;AAAA,EAGS,MACR,OAC+C;AAC/C,WAAO,IAAI,YAA6C,OAAO,KAAK,MAA8C;AAAA,EACnH;AACD;AAEO,MAAM,oBAEH,6BAAgC;AAAA,EACzC,QAA0B,UAAU,IAAY;AAAA,EAEhD,aAAqB;AACpB,WAAO;AAAA,EACR;AAAA,EAES,mBAAmB,OAAgC;AAC3D,QAAI,OAAO,UAAU,UAAU;AAC9B,aAAO,OAAO,KAAK;AAAA,IACpB;AACA,WAAO;AAAA,EACR;AACD;AAIO,SAAS,OAAO,MAAe;AACrC,SAAO,IAAI,mBAAmB,QAAQ,EAAE;AACzC;","names":[]}
|
||||
@ -1,796 +0,0 @@
|
||||
import type { CacheConfig, WithCacheConfig } from "../../cache/core/types.cjs";
|
||||
import { entityKind } from "../../entity.cjs";
|
||||
import type { PgColumn } from "../columns/index.cjs";
|
||||
import type { PgDialect } from "../dialect.cjs";
|
||||
import type { PgSession } from "../session.cjs";
|
||||
import type { SubqueryWithSelection } from "../subquery.cjs";
|
||||
import type { PgTable } from "../table.cjs";
|
||||
import { PgViewBase } from "../view-base.cjs";
|
||||
import { TypedQueryBuilder } from "../../query-builders/query-builder.cjs";
|
||||
import type { BuildSubquerySelection, GetSelectTableName, GetSelectTableSelection, JoinNullability, SelectMode, SelectResult } from "../../query-builders/select.types.cjs";
|
||||
import { QueryPromise } from "../../query-promise.cjs";
|
||||
import type { RunnableQuery } from "../../runnable-query.cjs";
|
||||
import { SQL } from "../../sql/sql.cjs";
|
||||
import type { ColumnsSelection, Placeholder, Query, SQLWrapper } from "../../sql/sql.cjs";
|
||||
import { Subquery } from "../../subquery.cjs";
|
||||
import { type DrizzleTypeError, type ValueOrArray } from "../../utils.cjs";
|
||||
import type { CreatePgSelectFromBuilderMode, GetPgSetOperators, LockConfig, LockStrength, PgCreateSetOperatorFn, PgSelectConfig, PgSelectCrossJoinFn, PgSelectDynamic, PgSelectHKT, PgSelectHKTBase, PgSelectJoinFn, PgSelectPrepare, PgSelectWithout, PgSetOperatorExcludedMethods, PgSetOperatorWithResult, SelectedFields, SetOperatorRightSelect, TableLikeHasEmptySelection } from "./select.types.cjs";
|
||||
export declare class PgSelectBuilder<TSelection extends SelectedFields | undefined, TBuilderMode extends 'db' | 'qb' = 'db'> {
|
||||
static readonly [entityKind]: string;
|
||||
private fields;
|
||||
private session;
|
||||
private dialect;
|
||||
private withList;
|
||||
private distinct;
|
||||
constructor(config: {
|
||||
fields: TSelection;
|
||||
session: PgSession | undefined;
|
||||
dialect: PgDialect;
|
||||
withList?: Subquery[];
|
||||
distinct?: boolean | {
|
||||
on: (PgColumn | SQLWrapper)[];
|
||||
};
|
||||
});
|
||||
private authToken?;
|
||||
/**
|
||||
* Specify the table, subquery, or other target that you're
|
||||
* building a select query against.
|
||||
*
|
||||
* {@link https://www.postgresql.org/docs/current/sql-select.html#SQL-FROM | Postgres from documentation}
|
||||
*/
|
||||
from<TFrom extends PgTable | Subquery | PgViewBase | SQL>(source: TableLikeHasEmptySelection<TFrom> extends true ? DrizzleTypeError<"Cannot reference a data-modifying statement subquery if it doesn't contain a `returning` clause"> : TFrom): CreatePgSelectFromBuilderMode<TBuilderMode, GetSelectTableName<TFrom>, TSelection extends undefined ? GetSelectTableSelection<TFrom> : TSelection, TSelection extends undefined ? 'single' : 'partial'>;
|
||||
}
|
||||
export declare abstract class PgSelectQueryBuilderBase<THKT extends PgSelectHKTBase, TTableName extends string | undefined, TSelection extends ColumnsSelection, TSelectMode extends SelectMode, TNullabilityMap extends Record<string, JoinNullability> = TTableName extends string ? Record<TTableName, 'not-null'> : {}, TDynamic extends boolean = false, TExcludedMethods extends string = never, TResult extends any[] = SelectResult<TSelection, TSelectMode, TNullabilityMap>[], TSelectedFields extends ColumnsSelection = BuildSubquerySelection<TSelection, TNullabilityMap>> extends TypedQueryBuilder<TSelectedFields, TResult> {
|
||||
static readonly [entityKind]: string;
|
||||
readonly _: {
|
||||
readonly dialect: 'pg';
|
||||
readonly hkt: THKT;
|
||||
readonly tableName: TTableName;
|
||||
readonly selection: TSelection;
|
||||
readonly selectMode: TSelectMode;
|
||||
readonly nullabilityMap: TNullabilityMap;
|
||||
readonly dynamic: TDynamic;
|
||||
readonly excludedMethods: TExcludedMethods;
|
||||
readonly result: TResult;
|
||||
readonly selectedFields: TSelectedFields;
|
||||
readonly config: PgSelectConfig;
|
||||
};
|
||||
protected config: PgSelectConfig;
|
||||
protected joinsNotNullableMap: Record<string, boolean>;
|
||||
protected tableName: string | undefined;
|
||||
private isPartialSelect;
|
||||
protected session: PgSession | undefined;
|
||||
protected dialect: PgDialect;
|
||||
protected cacheConfig?: WithCacheConfig;
|
||||
protected usedTables: Set<string>;
|
||||
constructor({ table, fields, isPartialSelect, session, dialect, withList, distinct }: {
|
||||
table: PgSelectConfig['table'];
|
||||
fields: PgSelectConfig['fields'];
|
||||
isPartialSelect: boolean;
|
||||
session: PgSession | undefined;
|
||||
dialect: PgDialect;
|
||||
withList: Subquery[];
|
||||
distinct: boolean | {
|
||||
on: (PgColumn | SQLWrapper)[];
|
||||
} | undefined;
|
||||
});
|
||||
private createJoin;
|
||||
/**
|
||||
* Executes a `left join` operation by adding another table to the current query.
|
||||
*
|
||||
* Calling this method associates each row of the table with the corresponding row from the joined table, if a match is found. If no matching row exists, it sets all columns of the joined table to null.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/joins#left-join}
|
||||
*
|
||||
* @param table the table to join.
|
||||
* @param on the `on` clause.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all users and their pets
|
||||
* const usersWithPets: { user: User; pets: Pet | null; }[] = await db.select()
|
||||
* .from(users)
|
||||
* .leftJoin(pets, eq(users.id, pets.ownerId))
|
||||
*
|
||||
* // Select userId and petId
|
||||
* const usersIdsAndPetIds: { userId: number; petId: number | null; }[] = await db.select({
|
||||
* userId: users.id,
|
||||
* petId: pets.id,
|
||||
* })
|
||||
* .from(users)
|
||||
* .leftJoin(pets, eq(users.id, pets.ownerId))
|
||||
* ```
|
||||
*/
|
||||
leftJoin: PgSelectJoinFn<this, TDynamic, "left", false>;
|
||||
/**
|
||||
* Executes a `left join lateral` operation by adding subquery to the current query.
|
||||
*
|
||||
* A `lateral` join allows the right-hand expression to refer to columns from the left-hand side.
|
||||
*
|
||||
* Calling this method associates each row of the table with the corresponding row from the joined table, if a match is found. If no matching row exists, it sets all columns of the joined table to null.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/joins#left-join-lateral}
|
||||
*
|
||||
* @param table the subquery to join.
|
||||
* @param on the `on` clause.
|
||||
*/
|
||||
leftJoinLateral: PgSelectJoinFn<this, TDynamic, "left", true>;
|
||||
/**
|
||||
* Executes a `right join` operation by adding another table to the current query.
|
||||
*
|
||||
* Calling this method associates each row of the joined table with the corresponding row from the main table, if a match is found. If no matching row exists, it sets all columns of the main table to null.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/joins#right-join}
|
||||
*
|
||||
* @param table the table to join.
|
||||
* @param on the `on` clause.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all users and their pets
|
||||
* const usersWithPets: { user: User | null; pets: Pet; }[] = await db.select()
|
||||
* .from(users)
|
||||
* .rightJoin(pets, eq(users.id, pets.ownerId))
|
||||
*
|
||||
* // Select userId and petId
|
||||
* const usersIdsAndPetIds: { userId: number | null; petId: number; }[] = await db.select({
|
||||
* userId: users.id,
|
||||
* petId: pets.id,
|
||||
* })
|
||||
* .from(users)
|
||||
* .rightJoin(pets, eq(users.id, pets.ownerId))
|
||||
* ```
|
||||
*/
|
||||
rightJoin: PgSelectJoinFn<this, TDynamic, "right", false>;
|
||||
/**
|
||||
* Executes an `inner join` operation, creating a new table by combining rows from two tables that have matching values.
|
||||
*
|
||||
* Calling this method retrieves rows that have corresponding entries in both joined tables. Rows without matching entries in either table are excluded, resulting in a table that includes only matching pairs.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/joins#inner-join}
|
||||
*
|
||||
* @param table the table to join.
|
||||
* @param on the `on` clause.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all users and their pets
|
||||
* const usersWithPets: { user: User; pets: Pet; }[] = await db.select()
|
||||
* .from(users)
|
||||
* .innerJoin(pets, eq(users.id, pets.ownerId))
|
||||
*
|
||||
* // Select userId and petId
|
||||
* const usersIdsAndPetIds: { userId: number; petId: number; }[] = await db.select({
|
||||
* userId: users.id,
|
||||
* petId: pets.id,
|
||||
* })
|
||||
* .from(users)
|
||||
* .innerJoin(pets, eq(users.id, pets.ownerId))
|
||||
* ```
|
||||
*/
|
||||
innerJoin: PgSelectJoinFn<this, TDynamic, "inner", false>;
|
||||
/**
|
||||
* Executes an `inner join lateral` operation, creating a new table by combining rows from two queries that have matching values.
|
||||
*
|
||||
* A `lateral` join allows the right-hand expression to refer to columns from the left-hand side.
|
||||
*
|
||||
* Calling this method retrieves rows that have corresponding entries in both joined tables. Rows without matching entries in either table are excluded, resulting in a table that includes only matching pairs.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/joins#inner-join-lateral}
|
||||
*
|
||||
* @param table the subquery to join.
|
||||
* @param on the `on` clause.
|
||||
*/
|
||||
innerJoinLateral: PgSelectJoinFn<this, TDynamic, "inner", true>;
|
||||
/**
|
||||
* Executes a `full join` operation by combining rows from two tables into a new table.
|
||||
*
|
||||
* Calling this method retrieves all rows from both main and joined tables, merging rows with matching values and filling in `null` for non-matching columns.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/joins#full-join}
|
||||
*
|
||||
* @param table the table to join.
|
||||
* @param on the `on` clause.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all users and their pets
|
||||
* const usersWithPets: { user: User | null; pets: Pet | null; }[] = await db.select()
|
||||
* .from(users)
|
||||
* .fullJoin(pets, eq(users.id, pets.ownerId))
|
||||
*
|
||||
* // Select userId and petId
|
||||
* const usersIdsAndPetIds: { userId: number | null; petId: number | null; }[] = await db.select({
|
||||
* userId: users.id,
|
||||
* petId: pets.id,
|
||||
* })
|
||||
* .from(users)
|
||||
* .fullJoin(pets, eq(users.id, pets.ownerId))
|
||||
* ```
|
||||
*/
|
||||
fullJoin: PgSelectJoinFn<this, TDynamic, "full", false>;
|
||||
/**
|
||||
* Executes a `cross join` operation by combining rows from two tables into a new table.
|
||||
*
|
||||
* Calling this method retrieves all rows from both main and joined tables, merging all rows from each table.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/joins#cross-join}
|
||||
*
|
||||
* @param table the table to join.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all users, each user with every pet
|
||||
* const usersWithPets: { user: User; pets: Pet; }[] = await db.select()
|
||||
* .from(users)
|
||||
* .crossJoin(pets)
|
||||
*
|
||||
* // Select userId and petId
|
||||
* const usersIdsAndPetIds: { userId: number; petId: number; }[] = await db.select({
|
||||
* userId: users.id,
|
||||
* petId: pets.id,
|
||||
* })
|
||||
* .from(users)
|
||||
* .crossJoin(pets)
|
||||
* ```
|
||||
*/
|
||||
crossJoin: PgSelectCrossJoinFn<this, TDynamic, false>;
|
||||
/**
|
||||
* Executes a `cross join lateral` operation by combining rows from two queries into a new table.
|
||||
*
|
||||
* A `lateral` join allows the right-hand expression to refer to columns from the left-hand side.
|
||||
*
|
||||
* Calling this method retrieves all rows from both main and joined queries, merging all rows from each query.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/joins#cross-join-lateral}
|
||||
*
|
||||
* @param table the query to join.
|
||||
*/
|
||||
crossJoinLateral: PgSelectCrossJoinFn<this, TDynamic, true>;
|
||||
private createSetOperator;
|
||||
/**
|
||||
* Adds `union` set operator to the query.
|
||||
*
|
||||
* Calling this method will combine the result sets of the `select` statements and remove any duplicate rows that appear across them.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/set-operations#union}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all unique names from customers and users tables
|
||||
* await db.select({ name: users.name })
|
||||
* .from(users)
|
||||
* .union(
|
||||
* db.select({ name: customers.name }).from(customers)
|
||||
* );
|
||||
* // or
|
||||
* import { union } from 'drizzle-orm/pg-core'
|
||||
*
|
||||
* await union(
|
||||
* db.select({ name: users.name }).from(users),
|
||||
* db.select({ name: customers.name }).from(customers)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
union: <TValue extends PgSetOperatorWithResult<TResult>>(rightSelection: ((setOperators: GetPgSetOperators) => SetOperatorRightSelect<TValue, TResult>) | SetOperatorRightSelect<TValue, TResult>) => PgSelectWithout<this, TDynamic, PgSetOperatorExcludedMethods, true>;
|
||||
/**
|
||||
* Adds `union all` set operator to the query.
|
||||
*
|
||||
* Calling this method will combine the result-set of the `select` statements and keep all duplicate rows that appear across them.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/set-operations#union-all}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all transaction ids from both online and in-store sales
|
||||
* await db.select({ transaction: onlineSales.transactionId })
|
||||
* .from(onlineSales)
|
||||
* .unionAll(
|
||||
* db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales)
|
||||
* );
|
||||
* // or
|
||||
* import { unionAll } from 'drizzle-orm/pg-core'
|
||||
*
|
||||
* await unionAll(
|
||||
* db.select({ transaction: onlineSales.transactionId }).from(onlineSales),
|
||||
* db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
unionAll: <TValue extends PgSetOperatorWithResult<TResult>>(rightSelection: ((setOperators: GetPgSetOperators) => SetOperatorRightSelect<TValue, TResult>) | SetOperatorRightSelect<TValue, TResult>) => PgSelectWithout<this, TDynamic, PgSetOperatorExcludedMethods, true>;
|
||||
/**
|
||||
* Adds `intersect` set operator to the query.
|
||||
*
|
||||
* Calling this method will retain only the rows that are present in both result sets and eliminate duplicates.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select course names that are offered in both departments A and B
|
||||
* await db.select({ courseName: depA.courseName })
|
||||
* .from(depA)
|
||||
* .intersect(
|
||||
* db.select({ courseName: depB.courseName }).from(depB)
|
||||
* );
|
||||
* // or
|
||||
* import { intersect } from 'drizzle-orm/pg-core'
|
||||
*
|
||||
* await intersect(
|
||||
* db.select({ courseName: depA.courseName }).from(depA),
|
||||
* db.select({ courseName: depB.courseName }).from(depB)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
intersect: <TValue extends PgSetOperatorWithResult<TResult>>(rightSelection: ((setOperators: GetPgSetOperators) => SetOperatorRightSelect<TValue, TResult>) | SetOperatorRightSelect<TValue, TResult>) => PgSelectWithout<this, TDynamic, PgSetOperatorExcludedMethods, true>;
|
||||
/**
|
||||
* Adds `intersect all` set operator to the query.
|
||||
*
|
||||
* Calling this method will retain only the rows that are present in both result sets including all duplicates.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect-all}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all products and quantities that are ordered by both regular and VIP customers
|
||||
* await db.select({
|
||||
* productId: regularCustomerOrders.productId,
|
||||
* quantityOrdered: regularCustomerOrders.quantityOrdered
|
||||
* })
|
||||
* .from(regularCustomerOrders)
|
||||
* .intersectAll(
|
||||
* db.select({
|
||||
* productId: vipCustomerOrders.productId,
|
||||
* quantityOrdered: vipCustomerOrders.quantityOrdered
|
||||
* })
|
||||
* .from(vipCustomerOrders)
|
||||
* );
|
||||
* // or
|
||||
* import { intersectAll } from 'drizzle-orm/pg-core'
|
||||
*
|
||||
* await intersectAll(
|
||||
* db.select({
|
||||
* productId: regularCustomerOrders.productId,
|
||||
* quantityOrdered: regularCustomerOrders.quantityOrdered
|
||||
* })
|
||||
* .from(regularCustomerOrders),
|
||||
* db.select({
|
||||
* productId: vipCustomerOrders.productId,
|
||||
* quantityOrdered: vipCustomerOrders.quantityOrdered
|
||||
* })
|
||||
* .from(vipCustomerOrders)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
intersectAll: <TValue extends PgSetOperatorWithResult<TResult>>(rightSelection: ((setOperators: GetPgSetOperators) => SetOperatorRightSelect<TValue, TResult>) | SetOperatorRightSelect<TValue, TResult>) => PgSelectWithout<this, TDynamic, PgSetOperatorExcludedMethods, true>;
|
||||
/**
|
||||
* Adds `except` set operator to the query.
|
||||
*
|
||||
* Calling this method will retrieve all unique rows from the left query, except for the rows that are present in the result set of the right query.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/set-operations#except}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all courses offered in department A but not in department B
|
||||
* await db.select({ courseName: depA.courseName })
|
||||
* .from(depA)
|
||||
* .except(
|
||||
* db.select({ courseName: depB.courseName }).from(depB)
|
||||
* );
|
||||
* // or
|
||||
* import { except } from 'drizzle-orm/pg-core'
|
||||
*
|
||||
* await except(
|
||||
* db.select({ courseName: depA.courseName }).from(depA),
|
||||
* db.select({ courseName: depB.courseName }).from(depB)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
except: <TValue extends PgSetOperatorWithResult<TResult>>(rightSelection: ((setOperators: GetPgSetOperators) => SetOperatorRightSelect<TValue, TResult>) | SetOperatorRightSelect<TValue, TResult>) => PgSelectWithout<this, TDynamic, PgSetOperatorExcludedMethods, true>;
|
||||
/**
|
||||
* Adds `except all` set operator to the query.
|
||||
*
|
||||
* Calling this method will retrieve all rows from the left query, except for the rows that are present in the result set of the right query.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/set-operations#except-all}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all products that are ordered by regular customers but not by VIP customers
|
||||
* await db.select({
|
||||
* productId: regularCustomerOrders.productId,
|
||||
* quantityOrdered: regularCustomerOrders.quantityOrdered,
|
||||
* })
|
||||
* .from(regularCustomerOrders)
|
||||
* .exceptAll(
|
||||
* db.select({
|
||||
* productId: vipCustomerOrders.productId,
|
||||
* quantityOrdered: vipCustomerOrders.quantityOrdered,
|
||||
* })
|
||||
* .from(vipCustomerOrders)
|
||||
* );
|
||||
* // or
|
||||
* import { exceptAll } from 'drizzle-orm/pg-core'
|
||||
*
|
||||
* await exceptAll(
|
||||
* db.select({
|
||||
* productId: regularCustomerOrders.productId,
|
||||
* quantityOrdered: regularCustomerOrders.quantityOrdered
|
||||
* })
|
||||
* .from(regularCustomerOrders),
|
||||
* db.select({
|
||||
* productId: vipCustomerOrders.productId,
|
||||
* quantityOrdered: vipCustomerOrders.quantityOrdered
|
||||
* })
|
||||
* .from(vipCustomerOrders)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
exceptAll: <TValue extends PgSetOperatorWithResult<TResult>>(rightSelection: ((setOperators: GetPgSetOperators) => SetOperatorRightSelect<TValue, TResult>) | SetOperatorRightSelect<TValue, TResult>) => PgSelectWithout<this, TDynamic, PgSetOperatorExcludedMethods, true>;
|
||||
/**
|
||||
* Adds a `where` clause to the query.
|
||||
*
|
||||
* Calling this method will select only those rows that fulfill a specified condition.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/select#filtering}
|
||||
*
|
||||
* @param where the `where` clause.
|
||||
*
|
||||
* @example
|
||||
* You can use conditional operators and `sql function` to filter the rows to be selected.
|
||||
*
|
||||
* ```ts
|
||||
* // Select all cars with green color
|
||||
* await db.select().from(cars).where(eq(cars.color, 'green'));
|
||||
* // or
|
||||
* await db.select().from(cars).where(sql`${cars.color} = 'green'`)
|
||||
* ```
|
||||
*
|
||||
* You can logically combine conditional operators with `and()` and `or()` operators:
|
||||
*
|
||||
* ```ts
|
||||
* // Select all BMW cars with a green color
|
||||
* await db.select().from(cars).where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW')));
|
||||
*
|
||||
* // Select all cars with the green or blue color
|
||||
* await db.select().from(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue')));
|
||||
* ```
|
||||
*/
|
||||
where(where: ((aliases: this['_']['selection']) => SQL | undefined) | SQL | undefined): PgSelectWithout<this, TDynamic, 'where'>;
|
||||
/**
|
||||
* Adds a `having` clause to the query.
|
||||
*
|
||||
* Calling this method will select only those rows that fulfill a specified condition. It is typically used with aggregate functions to filter the aggregated data based on a specified condition.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/select#aggregations}
|
||||
*
|
||||
* @param having the `having` clause.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all brands with more than one car
|
||||
* await db.select({
|
||||
* brand: cars.brand,
|
||||
* count: sql<number>`cast(count(${cars.id}) as int)`,
|
||||
* })
|
||||
* .from(cars)
|
||||
* .groupBy(cars.brand)
|
||||
* .having(({ count }) => gt(count, 1));
|
||||
* ```
|
||||
*/
|
||||
having(having: ((aliases: this['_']['selection']) => SQL | undefined) | SQL | undefined): PgSelectWithout<this, TDynamic, 'having'>;
|
||||
/**
|
||||
* Adds a `group by` clause to the query.
|
||||
*
|
||||
* Calling this method will group rows that have the same values into summary rows, often used for aggregation purposes.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/select#aggregations}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Group and count people by their last names
|
||||
* await db.select({
|
||||
* lastName: people.lastName,
|
||||
* count: sql<number>`cast(count(*) as int)`
|
||||
* })
|
||||
* .from(people)
|
||||
* .groupBy(people.lastName);
|
||||
* ```
|
||||
*/
|
||||
groupBy(builder: (aliases: this['_']['selection']) => ValueOrArray<PgColumn | SQL | SQL.Aliased>): PgSelectWithout<this, TDynamic, 'groupBy'>;
|
||||
groupBy(...columns: (PgColumn | SQL | SQL.Aliased)[]): PgSelectWithout<this, TDynamic, 'groupBy'>;
|
||||
/**
|
||||
* Adds an `order by` clause to the query.
|
||||
*
|
||||
* Calling this method will sort the result-set in ascending or descending order. By default, the sort order is ascending.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/select#order-by}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```
|
||||
* // Select cars ordered by year
|
||||
* await db.select().from(cars).orderBy(cars.year);
|
||||
* ```
|
||||
*
|
||||
* You can specify whether results are in ascending or descending order with the `asc()` and `desc()` operators.
|
||||
*
|
||||
* ```ts
|
||||
* // Select cars ordered by year in descending order
|
||||
* await db.select().from(cars).orderBy(desc(cars.year));
|
||||
*
|
||||
* // Select cars ordered by year and price
|
||||
* await db.select().from(cars).orderBy(asc(cars.year), desc(cars.price));
|
||||
* ```
|
||||
*/
|
||||
orderBy(builder: (aliases: this['_']['selection']) => ValueOrArray<PgColumn | SQL | SQL.Aliased>): PgSelectWithout<this, TDynamic, 'orderBy'>;
|
||||
orderBy(...columns: (PgColumn | SQL | SQL.Aliased)[]): PgSelectWithout<this, TDynamic, 'orderBy'>;
|
||||
/**
|
||||
* Adds a `limit` clause to the query.
|
||||
*
|
||||
* Calling this method will set the maximum number of rows that will be returned by this query.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/select#limit--offset}
|
||||
*
|
||||
* @param limit the `limit` clause.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Get the first 10 people from this query.
|
||||
* await db.select().from(people).limit(10);
|
||||
* ```
|
||||
*/
|
||||
limit(limit: number | Placeholder): PgSelectWithout<this, TDynamic, 'limit'>;
|
||||
/**
|
||||
* Adds an `offset` clause to the query.
|
||||
*
|
||||
* Calling this method will skip a number of rows when returning results from this query.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/select#limit--offset}
|
||||
*
|
||||
* @param offset the `offset` clause.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Get the 10th-20th people from this query.
|
||||
* await db.select().from(people).offset(10).limit(10);
|
||||
* ```
|
||||
*/
|
||||
offset(offset: number | Placeholder): PgSelectWithout<this, TDynamic, 'offset'>;
|
||||
/**
|
||||
* Adds a `for` clause to the query.
|
||||
*
|
||||
* Calling this method will specify a lock strength for this query that controls how strictly it acquires exclusive access to the rows being queried.
|
||||
*
|
||||
* See docs: {@link https://www.postgresql.org/docs/current/sql-select.html#SQL-FOR-UPDATE-SHARE}
|
||||
*
|
||||
* @param strength the lock strength.
|
||||
* @param config the lock configuration.
|
||||
*/
|
||||
for(strength: LockStrength, config?: LockConfig): PgSelectWithout<this, TDynamic, 'for'>;
|
||||
toSQL(): Query;
|
||||
as<TAlias extends string>(alias: TAlias): SubqueryWithSelection<this['_']['selectedFields'], TAlias>;
|
||||
$dynamic(): PgSelectDynamic<this>;
|
||||
$withCache(config?: {
|
||||
config?: CacheConfig;
|
||||
tag?: string;
|
||||
autoInvalidate?: boolean;
|
||||
} | false): this;
|
||||
}
|
||||
export interface PgSelectBase<TTableName extends string | undefined, TSelection extends ColumnsSelection, TSelectMode extends SelectMode, TNullabilityMap extends Record<string, JoinNullability> = TTableName extends string ? Record<TTableName, 'not-null'> : {}, TDynamic extends boolean = false, TExcludedMethods extends string = never, TResult extends any[] = SelectResult<TSelection, TSelectMode, TNullabilityMap>[], TSelectedFields extends ColumnsSelection = BuildSubquerySelection<TSelection, TNullabilityMap>> extends PgSelectQueryBuilderBase<PgSelectHKT, TTableName, TSelection, TSelectMode, TNullabilityMap, TDynamic, TExcludedMethods, TResult, TSelectedFields>, QueryPromise<TResult>, SQLWrapper {
|
||||
}
|
||||
export declare class PgSelectBase<TTableName extends string | undefined, TSelection extends ColumnsSelection, TSelectMode extends SelectMode, TNullabilityMap extends Record<string, JoinNullability> = TTableName extends string ? Record<TTableName, 'not-null'> : {}, TDynamic extends boolean = false, TExcludedMethods extends string = never, TResult = SelectResult<TSelection, TSelectMode, TNullabilityMap>[], TSelectedFields = BuildSubquerySelection<TSelection, TNullabilityMap>> extends PgSelectQueryBuilderBase<PgSelectHKT, TTableName, TSelection, TSelectMode, TNullabilityMap, TDynamic, TExcludedMethods, TResult, TSelectedFields> implements RunnableQuery<TResult, 'pg'>, SQLWrapper {
|
||||
static readonly [entityKind]: string;
|
||||
/**
|
||||
* Create a prepared statement for this query. This allows
|
||||
* the database to remember this query for the given session
|
||||
* and call it by name, rather than specifying the full query.
|
||||
*
|
||||
* {@link https://www.postgresql.org/docs/current/sql-prepare.html | Postgres prepare documentation}
|
||||
*/
|
||||
prepare(name: string): PgSelectPrepare<this>;
|
||||
private authToken?;
|
||||
execute: ReturnType<this['prepare']>['execute'];
|
||||
}
|
||||
/**
|
||||
* Adds `union` set operator to the query.
|
||||
*
|
||||
* Calling this method will combine the result sets of the `select` statements and remove any duplicate rows that appear across them.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/set-operations#union}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all unique names from customers and users tables
|
||||
* import { union } from 'drizzle-orm/pg-core'
|
||||
*
|
||||
* await union(
|
||||
* db.select({ name: users.name }).from(users),
|
||||
* db.select({ name: customers.name }).from(customers)
|
||||
* );
|
||||
* // or
|
||||
* await db.select({ name: users.name })
|
||||
* .from(users)
|
||||
* .union(
|
||||
* db.select({ name: customers.name }).from(customers)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export declare const union: PgCreateSetOperatorFn;
|
||||
/**
|
||||
* Adds `union all` set operator to the query.
|
||||
*
|
||||
* Calling this method will combine the result-set of the `select` statements and keep all duplicate rows that appear across them.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/set-operations#union-all}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all transaction ids from both online and in-store sales
|
||||
* import { unionAll } from 'drizzle-orm/pg-core'
|
||||
*
|
||||
* await unionAll(
|
||||
* db.select({ transaction: onlineSales.transactionId }).from(onlineSales),
|
||||
* db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales)
|
||||
* );
|
||||
* // or
|
||||
* await db.select({ transaction: onlineSales.transactionId })
|
||||
* .from(onlineSales)
|
||||
* .unionAll(
|
||||
* db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export declare const unionAll: PgCreateSetOperatorFn;
|
||||
/**
|
||||
* Adds `intersect` set operator to the query.
|
||||
*
|
||||
* Calling this method will retain only the rows that are present in both result sets and eliminate duplicates.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select course names that are offered in both departments A and B
|
||||
* import { intersect } from 'drizzle-orm/pg-core'
|
||||
*
|
||||
* await intersect(
|
||||
* db.select({ courseName: depA.courseName }).from(depA),
|
||||
* db.select({ courseName: depB.courseName }).from(depB)
|
||||
* );
|
||||
* // or
|
||||
* await db.select({ courseName: depA.courseName })
|
||||
* .from(depA)
|
||||
* .intersect(
|
||||
* db.select({ courseName: depB.courseName }).from(depB)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export declare const intersect: PgCreateSetOperatorFn;
|
||||
/**
|
||||
* Adds `intersect all` set operator to the query.
|
||||
*
|
||||
* Calling this method will retain only the rows that are present in both result sets including all duplicates.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect-all}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all products and quantities that are ordered by both regular and VIP customers
|
||||
* import { intersectAll } from 'drizzle-orm/pg-core'
|
||||
*
|
||||
* await intersectAll(
|
||||
* db.select({
|
||||
* productId: regularCustomerOrders.productId,
|
||||
* quantityOrdered: regularCustomerOrders.quantityOrdered
|
||||
* })
|
||||
* .from(regularCustomerOrders),
|
||||
* db.select({
|
||||
* productId: vipCustomerOrders.productId,
|
||||
* quantityOrdered: vipCustomerOrders.quantityOrdered
|
||||
* })
|
||||
* .from(vipCustomerOrders)
|
||||
* );
|
||||
* // or
|
||||
* await db.select({
|
||||
* productId: regularCustomerOrders.productId,
|
||||
* quantityOrdered: regularCustomerOrders.quantityOrdered
|
||||
* })
|
||||
* .from(regularCustomerOrders)
|
||||
* .intersectAll(
|
||||
* db.select({
|
||||
* productId: vipCustomerOrders.productId,
|
||||
* quantityOrdered: vipCustomerOrders.quantityOrdered
|
||||
* })
|
||||
* .from(vipCustomerOrders)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export declare const intersectAll: PgCreateSetOperatorFn;
|
||||
/**
|
||||
* Adds `except` set operator to the query.
|
||||
*
|
||||
* Calling this method will retrieve all unique rows from the left query, except for the rows that are present in the result set of the right query.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/set-operations#except}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all courses offered in department A but not in department B
|
||||
* import { except } from 'drizzle-orm/pg-core'
|
||||
*
|
||||
* await except(
|
||||
* db.select({ courseName: depA.courseName }).from(depA),
|
||||
* db.select({ courseName: depB.courseName }).from(depB)
|
||||
* );
|
||||
* // or
|
||||
* await db.select({ courseName: depA.courseName })
|
||||
* .from(depA)
|
||||
* .except(
|
||||
* db.select({ courseName: depB.courseName }).from(depB)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export declare const except: PgCreateSetOperatorFn;
|
||||
/**
|
||||
* Adds `except all` set operator to the query.
|
||||
*
|
||||
* Calling this method will retrieve all rows from the left query, except for the rows that are present in the result set of the right query.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/set-operations#except-all}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all products that are ordered by regular customers but not by VIP customers
|
||||
* import { exceptAll } from 'drizzle-orm/pg-core'
|
||||
*
|
||||
* await exceptAll(
|
||||
* db.select({
|
||||
* productId: regularCustomerOrders.productId,
|
||||
* quantityOrdered: regularCustomerOrders.quantityOrdered
|
||||
* })
|
||||
* .from(regularCustomerOrders),
|
||||
* db.select({
|
||||
* productId: vipCustomerOrders.productId,
|
||||
* quantityOrdered: vipCustomerOrders.quantityOrdered
|
||||
* })
|
||||
* .from(vipCustomerOrders)
|
||||
* );
|
||||
* // or
|
||||
* await db.select({
|
||||
* productId: regularCustomerOrders.productId,
|
||||
* quantityOrdered: regularCustomerOrders.quantityOrdered,
|
||||
* })
|
||||
* .from(regularCustomerOrders)
|
||||
* .exceptAll(
|
||||
* db.select({
|
||||
* productId: vipCustomerOrders.productId,
|
||||
* quantityOrdered: vipCustomerOrders.quantityOrdered,
|
||||
* })
|
||||
* .from(vipCustomerOrders)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export declare const exceptAll: PgCreateSetOperatorFn;
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,149 +0,0 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var indexes_exports = {};
|
||||
__export(indexes_exports, {
|
||||
Index: () => Index,
|
||||
IndexBuilder: () => IndexBuilder,
|
||||
IndexBuilderOn: () => IndexBuilderOn,
|
||||
index: () => index,
|
||||
uniqueIndex: () => uniqueIndex
|
||||
});
|
||||
module.exports = __toCommonJS(indexes_exports);
|
||||
var import_sql = require("../sql/sql.cjs");
|
||||
var import_entity = require("../entity.cjs");
|
||||
var import_columns = require("./columns/index.cjs");
|
||||
class IndexBuilderOn {
|
||||
constructor(unique, name) {
|
||||
this.unique = unique;
|
||||
this.name = name;
|
||||
}
|
||||
static [import_entity.entityKind] = "GelIndexBuilderOn";
|
||||
on(...columns) {
|
||||
return new IndexBuilder(
|
||||
columns.map((it) => {
|
||||
if ((0, import_entity.is)(it, import_sql.SQL)) {
|
||||
return it;
|
||||
}
|
||||
it = it;
|
||||
const clonedIndexedColumn = new import_columns.IndexedColumn(it.name, !!it.keyAsName, it.columnType, it.indexConfig);
|
||||
it.indexConfig = JSON.parse(JSON.stringify(it.defaultConfig));
|
||||
return clonedIndexedColumn;
|
||||
}),
|
||||
this.unique,
|
||||
false,
|
||||
this.name
|
||||
);
|
||||
}
|
||||
onOnly(...columns) {
|
||||
return new IndexBuilder(
|
||||
columns.map((it) => {
|
||||
if ((0, import_entity.is)(it, import_sql.SQL)) {
|
||||
return it;
|
||||
}
|
||||
it = it;
|
||||
const clonedIndexedColumn = new import_columns.IndexedColumn(it.name, !!it.keyAsName, it.columnType, it.indexConfig);
|
||||
it.indexConfig = it.defaultConfig;
|
||||
return clonedIndexedColumn;
|
||||
}),
|
||||
this.unique,
|
||||
true,
|
||||
this.name
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Specify what index method to use. Choices are `btree`, `hash`, `gist`, `sGelist`, `gin`, `brin`, or user-installed access methods like `bloom`. The default method is `btree.
|
||||
*
|
||||
* If you have the `Gel_vector` extension installed in your database, you can use the `hnsw` and `ivfflat` options, which are predefined types.
|
||||
*
|
||||
* **You can always specify any string you want in the method, in case Drizzle doesn't have it natively in its types**
|
||||
*
|
||||
* @param method The name of the index method to be used
|
||||
* @param columns
|
||||
* @returns
|
||||
*/
|
||||
using(method, ...columns) {
|
||||
return new IndexBuilder(
|
||||
columns.map((it) => {
|
||||
if ((0, import_entity.is)(it, import_sql.SQL)) {
|
||||
return it;
|
||||
}
|
||||
it = it;
|
||||
const clonedIndexedColumn = new import_columns.IndexedColumn(it.name, !!it.keyAsName, it.columnType, it.indexConfig);
|
||||
it.indexConfig = JSON.parse(JSON.stringify(it.defaultConfig));
|
||||
return clonedIndexedColumn;
|
||||
}),
|
||||
this.unique,
|
||||
true,
|
||||
this.name,
|
||||
method
|
||||
);
|
||||
}
|
||||
}
|
||||
class IndexBuilder {
|
||||
static [import_entity.entityKind] = "GelIndexBuilder";
|
||||
/** @internal */
|
||||
config;
|
||||
constructor(columns, unique, only, name, method = "btree") {
|
||||
this.config = {
|
||||
name,
|
||||
columns,
|
||||
unique,
|
||||
only,
|
||||
method
|
||||
};
|
||||
}
|
||||
concurrently() {
|
||||
this.config.concurrently = true;
|
||||
return this;
|
||||
}
|
||||
with(obj) {
|
||||
this.config.with = obj;
|
||||
return this;
|
||||
}
|
||||
where(condition) {
|
||||
this.config.where = condition;
|
||||
return this;
|
||||
}
|
||||
/** @internal */
|
||||
build(table) {
|
||||
return new Index(this.config, table);
|
||||
}
|
||||
}
|
||||
class Index {
|
||||
static [import_entity.entityKind] = "GelIndex";
|
||||
config;
|
||||
constructor(config, table) {
|
||||
this.config = { ...config, table };
|
||||
}
|
||||
}
|
||||
function index(name) {
|
||||
return new IndexBuilderOn(false, name);
|
||||
}
|
||||
function uniqueIndex(name) {
|
||||
return new IndexBuilderOn(true, name);
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
Index,
|
||||
IndexBuilder,
|
||||
IndexBuilderOn,
|
||||
index,
|
||||
uniqueIndex
|
||||
});
|
||||
//# sourceMappingURL=indexes.cjs.map
|
||||
@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/singlestore-core/columns/year.ts"],"sourcesContent":["import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts';\nimport type { ColumnBaseConfig } from '~/column.ts';\nimport { entityKind } from '~/entity.ts';\nimport type { AnySingleStoreTable } from '~/singlestore-core/table.ts';\nimport { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts';\n\nexport type SingleStoreYearBuilderInitial<TName extends string> = SingleStoreYearBuilder<{\n\tname: TName;\n\tdataType: 'number';\n\tcolumnType: 'SingleStoreYear';\n\tdata: number;\n\tdriverParam: number;\n\tenumValues: undefined;\n\tgenerated: undefined;\n}>;\n\nexport class SingleStoreYearBuilder<T extends ColumnBuilderBaseConfig<'number', 'SingleStoreYear'>>\n\textends SingleStoreColumnBuilder<T>\n{\n\tstatic override readonly [entityKind]: string = 'SingleStoreYearBuilder';\n\n\tconstructor(name: T['name']) {\n\t\tsuper(name, 'number', 'SingleStoreYear');\n\t}\n\n\t/** @internal */\n\toverride build<TTableName extends string>(\n\t\ttable: AnySingleStoreTable<{ name: TTableName }>,\n\t): SingleStoreYear<MakeColumnConfig<T, TTableName>> {\n\t\treturn new SingleStoreYear<MakeColumnConfig<T, TTableName>>(\n\t\t\ttable,\n\t\t\tthis.config as ColumnBuilderRuntimeConfig<any, any>,\n\t\t);\n\t}\n}\n\nexport class SingleStoreYear<\n\tT extends ColumnBaseConfig<'number', 'SingleStoreYear'>,\n> extends SingleStoreColumn<T> {\n\tstatic override readonly [entityKind]: string = 'SingleStoreYear';\n\n\tgetSQLType(): string {\n\t\treturn `year`;\n\t}\n}\n\nexport function year(): SingleStoreYearBuilderInitial<''>;\nexport function year<TName extends string>(name: TName): SingleStoreYearBuilderInitial<TName>;\nexport function year(name?: string) {\n\treturn new SingleStoreYearBuilder(name ?? '');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,oBAA2B;AAE3B,oBAA4D;AAYrD,MAAM,+BACJ,uCACT;AAAA,EACC,QAA0B,wBAAU,IAAY;AAAA,EAEhD,YAAY,MAAiB;AAC5B,UAAM,MAAM,UAAU,iBAAiB;AAAA,EACxC;AAAA;AAAA,EAGS,MACR,OACmD;AACnD,WAAO,IAAI;AAAA,MACV;AAAA,MACA,KAAK;AAAA,IACN;AAAA,EACD;AACD;AAEO,MAAM,wBAEH,gCAAqB;AAAA,EAC9B,QAA0B,wBAAU,IAAY;AAAA,EAEhD,aAAqB;AACpB,WAAO;AAAA,EACR;AACD;AAIO,SAAS,KAAK,MAAe;AACnC,SAAO,IAAI,uBAAuB,QAAQ,EAAE;AAC7C;","names":[]}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
{"version":3,"file":"concatMap.js","sourceRoot":"","sources":["../../../../src/internal/operators/concatMap.ts"],"names":[],"mappings":";;;AAAA,uCAAsC;AAEtC,iDAAgD;AA2EhD,SAAgB,SAAS,CACvB,OAAuC,EACvC,cAA6G;IAE7G,OAAO,uBAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,mBAAQ,CAAC,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,mBAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;AAClG,CAAC;AALD,8BAKC"}
|
||||
@ -1,5 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
module.exports.isClean = Symbol('isClean')
|
||||
|
||||
module.exports.my = Symbol('my')
|
||||
@ -1,34 +0,0 @@
|
||||
var defer = require('./defer.js');
|
||||
|
||||
// API
|
||||
module.exports = async;
|
||||
|
||||
/**
|
||||
* Runs provided callback asynchronously
|
||||
* even if callback itself is not
|
||||
*
|
||||
* @param {function} callback - callback to invoke
|
||||
* @returns {function} - augmented callback
|
||||
*/
|
||||
function async(callback)
|
||||
{
|
||||
var isAsync = false;
|
||||
|
||||
// check if async happened
|
||||
defer(function() { isAsync = true; });
|
||||
|
||||
return function async_callback(err, result)
|
||||
{
|
||||
if (isAsync)
|
||||
{
|
||||
callback(err, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
defer(function nextTick_callback()
|
||||
{
|
||||
callback(err, result);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/gel-core/columns/localdate.ts"],"sourcesContent":["import type { LocalDate } from 'gel';\nimport type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts';\nimport type { ColumnBaseConfig } from '~/column.ts';\nimport { entityKind } from '~/entity.ts';\nimport type { AnyGelTable } from '~/gel-core/table.ts';\nimport { GelColumn } from './common.ts';\nimport { GelLocalDateColumnBaseBuilder } from './date.common.ts';\n\nexport type GelLocalDateStringBuilderInitial<TName extends string> = GelLocalDateStringBuilder<{\n\tname: TName;\n\tdataType: 'localDate';\n\tcolumnType: 'GelLocalDateString';\n\tdata: LocalDate;\n\tdriverParam: LocalDate;\n\tenumValues: undefined;\n}>;\n\nexport class GelLocalDateStringBuilder<T extends ColumnBuilderBaseConfig<'localDate', 'GelLocalDateString'>>\n\textends GelLocalDateColumnBaseBuilder<T>\n{\n\tstatic override readonly [entityKind]: string = 'GelLocalDateStringBuilder';\n\n\tconstructor(name: T['name']) {\n\t\tsuper(name, 'localDate', 'GelLocalDateString');\n\t}\n\n\t/** @internal */\n\toverride build<TTableName extends string>(\n\t\ttable: AnyGelTable<{ name: TTableName }>,\n\t): GelLocalDateString<MakeColumnConfig<T, TTableName>> {\n\t\treturn new GelLocalDateString<MakeColumnConfig<T, TTableName>>(\n\t\t\ttable,\n\t\t\tthis.config as ColumnBuilderRuntimeConfig<any, any>,\n\t\t);\n\t}\n}\n\nexport class GelLocalDateString<T extends ColumnBaseConfig<'localDate', 'GelLocalDateString'>> extends GelColumn<T> {\n\tstatic override readonly [entityKind]: string = 'GelLocalDateString';\n\n\tgetSQLType(): string {\n\t\treturn 'cal::local_date';\n\t}\n}\n\nexport function localDate(): GelLocalDateStringBuilderInitial<''>;\nexport function localDate<TName extends string>(name: TName): GelLocalDateStringBuilderInitial<TName>;\nexport function localDate(name?: string) {\n\treturn new GelLocalDateStringBuilder(name ?? '');\n}\n"],"mappings":"AAGA,SAAS,kBAAkB;AAE3B,SAAS,iBAAiB;AAC1B,SAAS,qCAAqC;AAWvC,MAAM,kCACJ,8BACT;AAAA,EACC,QAA0B,UAAU,IAAY;AAAA,EAEhD,YAAY,MAAiB;AAC5B,UAAM,MAAM,aAAa,oBAAoB;AAAA,EAC9C;AAAA;AAAA,EAGS,MACR,OACsD;AACtD,WAAO,IAAI;AAAA,MACV;AAAA,MACA,KAAK;AAAA,IACN;AAAA,EACD;AACD;AAEO,MAAM,2BAA0F,UAAa;AAAA,EACnH,QAA0B,UAAU,IAAY;AAAA,EAEhD,aAAqB;AACpB,WAAO;AAAA,EACR;AACD;AAIO,SAAS,UAAU,MAAe;AACxC,SAAO,IAAI,0BAA0B,QAAQ,EAAE;AAChD;","names":[]}
|
||||
@ -1 +0,0 @@
|
||||
{"version":3,"file":"refCount.js","sourceRoot":"","sources":["../../../../src/internal/operators/refCount.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AA4DhE,MAAM,UAAU,QAAQ;IACtB,OAAO,OAAO,CAAC,UAAC,MAAM,EAAE,UAAU;QAChC,IAAI,UAAU,GAAwB,IAAI,CAAC;QAE1C,MAAc,CAAC,SAAS,EAAE,CAAC;QAE5B,IAAM,UAAU,GAAG,wBAAwB,CAAC,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;YACvF,IAAI,CAAC,MAAM,IAAK,MAAc,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,GAAG,EAAG,MAAc,CAAC,SAAS,EAAE;gBAChF,UAAU,GAAG,IAAI,CAAC;gBAClB,OAAO;aACR;YA2BD,IAAM,gBAAgB,GAAI,MAAc,CAAC,WAAW,CAAC;YACrD,IAAM,IAAI,GAAG,UAAU,CAAC;YACxB,UAAU,GAAG,IAAI,CAAC;YAElB,IAAI,gBAAgB,IAAI,CAAC,CAAC,IAAI,IAAI,gBAAgB,KAAK,IAAI,CAAC,EAAE;gBAC5D,gBAAgB,CAAC,WAAW,EAAE,CAAC;aAChC;YAED,UAAU,CAAC,WAAW,EAAE,CAAC;QAC3B,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAE7B,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE;YACtB,UAAU,GAAI,MAAmC,CAAC,OAAO,EAAE,CAAC;SAC7D;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
|
||||
@ -1,138 +0,0 @@
|
||||
# combined-stream
|
||||
|
||||
A stream that emits multiple other streams one after another.
|
||||
|
||||
**NB** Currently `combined-stream` works with streams version 1 only. There is ongoing effort to switch this library to streams version 2. Any help is welcome. :) Meanwhile you can explore other libraries that provide streams2 support with more or less compatibility with `combined-stream`.
|
||||
|
||||
- [combined-stream2](https://www.npmjs.com/package/combined-stream2): A drop-in streams2-compatible replacement for the combined-stream module.
|
||||
|
||||
- [multistream](https://www.npmjs.com/package/multistream): A stream that emits multiple other streams one after another.
|
||||
|
||||
## Installation
|
||||
|
||||
``` bash
|
||||
npm install combined-stream
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Here is a simple example that shows how you can use combined-stream to combine
|
||||
two files into one:
|
||||
|
||||
``` javascript
|
||||
var CombinedStream = require('combined-stream');
|
||||
var fs = require('fs');
|
||||
|
||||
var combinedStream = CombinedStream.create();
|
||||
combinedStream.append(fs.createReadStream('file1.txt'));
|
||||
combinedStream.append(fs.createReadStream('file2.txt'));
|
||||
|
||||
combinedStream.pipe(fs.createWriteStream('combined.txt'));
|
||||
```
|
||||
|
||||
While the example above works great, it will pause all source streams until
|
||||
they are needed. If you don't want that to happen, you can set `pauseStreams`
|
||||
to `false`:
|
||||
|
||||
``` javascript
|
||||
var CombinedStream = require('combined-stream');
|
||||
var fs = require('fs');
|
||||
|
||||
var combinedStream = CombinedStream.create({pauseStreams: false});
|
||||
combinedStream.append(fs.createReadStream('file1.txt'));
|
||||
combinedStream.append(fs.createReadStream('file2.txt'));
|
||||
|
||||
combinedStream.pipe(fs.createWriteStream('combined.txt'));
|
||||
```
|
||||
|
||||
However, what if you don't have all the source streams yet, or you don't want
|
||||
to allocate the resources (file descriptors, memory, etc.) for them right away?
|
||||
Well, in that case you can simply provide a callback that supplies the stream
|
||||
by calling a `next()` function:
|
||||
|
||||
``` javascript
|
||||
var CombinedStream = require('combined-stream');
|
||||
var fs = require('fs');
|
||||
|
||||
var combinedStream = CombinedStream.create();
|
||||
combinedStream.append(function(next) {
|
||||
next(fs.createReadStream('file1.txt'));
|
||||
});
|
||||
combinedStream.append(function(next) {
|
||||
next(fs.createReadStream('file2.txt'));
|
||||
});
|
||||
|
||||
combinedStream.pipe(fs.createWriteStream('combined.txt'));
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### CombinedStream.create([options])
|
||||
|
||||
Returns a new combined stream object. Available options are:
|
||||
|
||||
* `maxDataSize`
|
||||
* `pauseStreams`
|
||||
|
||||
The effect of those options is described below.
|
||||
|
||||
### combinedStream.pauseStreams = `true`
|
||||
|
||||
Whether to apply back pressure to the underlaying streams. If set to `false`,
|
||||
the underlaying streams will never be paused. If set to `true`, the
|
||||
underlaying streams will be paused right after being appended, as well as when
|
||||
`delayedStream.pipe()` wants to throttle.
|
||||
|
||||
### combinedStream.maxDataSize = `2 * 1024 * 1024`
|
||||
|
||||
The maximum amount of bytes (or characters) to buffer for all source streams.
|
||||
If this value is exceeded, `combinedStream` emits an `'error'` event.
|
||||
|
||||
### combinedStream.dataSize = `0`
|
||||
|
||||
The amount of bytes (or characters) currently buffered by `combinedStream`.
|
||||
|
||||
### combinedStream.append(stream)
|
||||
|
||||
Appends the given `stream` to the combinedStream object. If `pauseStreams` is
|
||||
set to `true, this stream will also be paused right away.
|
||||
|
||||
`streams` can also be a function that takes one parameter called `next`. `next`
|
||||
is a function that must be invoked in order to provide the `next` stream, see
|
||||
example above.
|
||||
|
||||
Regardless of how the `stream` is appended, combined-stream always attaches an
|
||||
`'error'` listener to it, so you don't have to do that manually.
|
||||
|
||||
Special case: `stream` can also be a String or Buffer.
|
||||
|
||||
### combinedStream.write(data)
|
||||
|
||||
You should not call this, `combinedStream` takes care of piping the appended
|
||||
streams into itself for you.
|
||||
|
||||
### combinedStream.resume()
|
||||
|
||||
Causes `combinedStream` to start drain the streams it manages. The function is
|
||||
idempotent, and also emits a `'resume'` event each time which usually goes to
|
||||
the stream that is currently being drained.
|
||||
|
||||
### combinedStream.pause();
|
||||
|
||||
If `combinedStream.pauseStreams` is set to `false`, this does nothing.
|
||||
Otherwise a `'pause'` event is emitted, this goes to the stream that is
|
||||
currently being drained, so you can use it to apply back pressure.
|
||||
|
||||
### combinedStream.end();
|
||||
|
||||
Sets `combinedStream.writable` to false, emits an `'end'` event, and removes
|
||||
all streams from the queue.
|
||||
|
||||
### combinedStream.destroy();
|
||||
|
||||
Same as `combinedStream.end()`, except it emits a `'close'` event instead of
|
||||
`'end'`.
|
||||
|
||||
## License
|
||||
|
||||
combined-stream is licensed under the MIT license.
|
||||
@ -1,14 +0,0 @@
|
||||
import { AsyncAction } from './AsyncAction';
|
||||
import { Subscription } from '../Subscription';
|
||||
import { QueueScheduler } from './QueueScheduler';
|
||||
import { SchedulerAction } from '../types';
|
||||
import { TimerHandle } from './timerHandle';
|
||||
export declare class QueueAction<T> extends AsyncAction<T> {
|
||||
protected scheduler: QueueScheduler;
|
||||
protected work: (this: SchedulerAction<T>, state?: T) => void;
|
||||
constructor(scheduler: QueueScheduler, work: (this: SchedulerAction<T>, state?: T) => void);
|
||||
schedule(state?: T, delay?: number): Subscription;
|
||||
execute(state: T, delay: number): any;
|
||||
protected requestAsyncId(scheduler: QueueScheduler, id?: TimerHandle, delay?: number): TimerHandle;
|
||||
}
|
||||
//# sourceMappingURL=QueueAction.d.ts.map
|
||||
@ -1,714 +0,0 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except2, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except2)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var select_exports = {};
|
||||
__export(select_exports, {
|
||||
SQLiteSelectBase: () => SQLiteSelectBase,
|
||||
SQLiteSelectBuilder: () => SQLiteSelectBuilder,
|
||||
SQLiteSelectQueryBuilderBase: () => SQLiteSelectQueryBuilderBase,
|
||||
except: () => except,
|
||||
intersect: () => intersect,
|
||||
union: () => union,
|
||||
unionAll: () => unionAll
|
||||
});
|
||||
module.exports = __toCommonJS(select_exports);
|
||||
var import_entity = require("../../entity.cjs");
|
||||
var import_query_builder = require("../../query-builders/query-builder.cjs");
|
||||
var import_query_promise = require("../../query-promise.cjs");
|
||||
var import_selection_proxy = require("../../selection-proxy.cjs");
|
||||
var import_sql = require("../../sql/sql.cjs");
|
||||
var import_subquery = require("../../subquery.cjs");
|
||||
var import_table = require("../../table.cjs");
|
||||
var import_utils = require("../../utils.cjs");
|
||||
var import_view_common = require("../../view-common.cjs");
|
||||
var import_utils2 = require("../utils.cjs");
|
||||
var import_view_base = require("../view-base.cjs");
|
||||
class SQLiteSelectBuilder {
|
||||
static [import_entity.entityKind] = "SQLiteSelectBuilder";
|
||||
fields;
|
||||
session;
|
||||
dialect;
|
||||
withList;
|
||||
distinct;
|
||||
constructor(config) {
|
||||
this.fields = config.fields;
|
||||
this.session = config.session;
|
||||
this.dialect = config.dialect;
|
||||
this.withList = config.withList;
|
||||
this.distinct = config.distinct;
|
||||
}
|
||||
from(source) {
|
||||
const isPartialSelect = !!this.fields;
|
||||
let fields;
|
||||
if (this.fields) {
|
||||
fields = this.fields;
|
||||
} else if ((0, import_entity.is)(source, import_subquery.Subquery)) {
|
||||
fields = Object.fromEntries(
|
||||
Object.keys(source._.selectedFields).map((key) => [key, source[key]])
|
||||
);
|
||||
} else if ((0, import_entity.is)(source, import_view_base.SQLiteViewBase)) {
|
||||
fields = source[import_view_common.ViewBaseConfig].selectedFields;
|
||||
} else if ((0, import_entity.is)(source, import_sql.SQL)) {
|
||||
fields = {};
|
||||
} else {
|
||||
fields = (0, import_utils.getTableColumns)(source);
|
||||
}
|
||||
return new SQLiteSelectBase({
|
||||
table: source,
|
||||
fields,
|
||||
isPartialSelect,
|
||||
session: this.session,
|
||||
dialect: this.dialect,
|
||||
withList: this.withList,
|
||||
distinct: this.distinct
|
||||
});
|
||||
}
|
||||
}
|
||||
class SQLiteSelectQueryBuilderBase extends import_query_builder.TypedQueryBuilder {
|
||||
static [import_entity.entityKind] = "SQLiteSelectQueryBuilder";
|
||||
_;
|
||||
/** @internal */
|
||||
config;
|
||||
joinsNotNullableMap;
|
||||
tableName;
|
||||
isPartialSelect;
|
||||
session;
|
||||
dialect;
|
||||
cacheConfig = void 0;
|
||||
usedTables = /* @__PURE__ */ new Set();
|
||||
constructor({ table, fields, isPartialSelect, session, dialect, withList, distinct }) {
|
||||
super();
|
||||
this.config = {
|
||||
withList,
|
||||
table,
|
||||
fields: { ...fields },
|
||||
distinct,
|
||||
setOperators: []
|
||||
};
|
||||
this.isPartialSelect = isPartialSelect;
|
||||
this.session = session;
|
||||
this.dialect = dialect;
|
||||
this._ = {
|
||||
selectedFields: fields,
|
||||
config: this.config
|
||||
};
|
||||
this.tableName = (0, import_utils.getTableLikeName)(table);
|
||||
this.joinsNotNullableMap = typeof this.tableName === "string" ? { [this.tableName]: true } : {};
|
||||
for (const item of (0, import_utils2.extractUsedTable)(table)) this.usedTables.add(item);
|
||||
}
|
||||
/** @internal */
|
||||
getUsedTables() {
|
||||
return [...this.usedTables];
|
||||
}
|
||||
createJoin(joinType) {
|
||||
return (table, on) => {
|
||||
const baseTableName = this.tableName;
|
||||
const tableName = (0, import_utils.getTableLikeName)(table);
|
||||
for (const item of (0, import_utils2.extractUsedTable)(table)) this.usedTables.add(item);
|
||||
if (typeof tableName === "string" && this.config.joins?.some((join) => join.alias === tableName)) {
|
||||
throw new Error(`Alias "${tableName}" is already used in this query`);
|
||||
}
|
||||
if (!this.isPartialSelect) {
|
||||
if (Object.keys(this.joinsNotNullableMap).length === 1 && typeof baseTableName === "string") {
|
||||
this.config.fields = {
|
||||
[baseTableName]: this.config.fields
|
||||
};
|
||||
}
|
||||
if (typeof tableName === "string" && !(0, import_entity.is)(table, import_sql.SQL)) {
|
||||
const selection = (0, import_entity.is)(table, import_subquery.Subquery) ? table._.selectedFields : (0, import_entity.is)(table, import_sql.View) ? table[import_view_common.ViewBaseConfig].selectedFields : table[import_table.Table.Symbol.Columns];
|
||||
this.config.fields[tableName] = selection;
|
||||
}
|
||||
}
|
||||
if (typeof on === "function") {
|
||||
on = on(
|
||||
new Proxy(
|
||||
this.config.fields,
|
||||
new import_selection_proxy.SelectionProxyHandler({ sqlAliasedBehavior: "sql", sqlBehavior: "sql" })
|
||||
)
|
||||
);
|
||||
}
|
||||
if (!this.config.joins) {
|
||||
this.config.joins = [];
|
||||
}
|
||||
this.config.joins.push({ on, table, joinType, alias: tableName });
|
||||
if (typeof tableName === "string") {
|
||||
switch (joinType) {
|
||||
case "left": {
|
||||
this.joinsNotNullableMap[tableName] = false;
|
||||
break;
|
||||
}
|
||||
case "right": {
|
||||
this.joinsNotNullableMap = Object.fromEntries(
|
||||
Object.entries(this.joinsNotNullableMap).map(([key]) => [key, false])
|
||||
);
|
||||
this.joinsNotNullableMap[tableName] = true;
|
||||
break;
|
||||
}
|
||||
case "cross":
|
||||
case "inner": {
|
||||
this.joinsNotNullableMap[tableName] = true;
|
||||
break;
|
||||
}
|
||||
case "full": {
|
||||
this.joinsNotNullableMap = Object.fromEntries(
|
||||
Object.entries(this.joinsNotNullableMap).map(([key]) => [key, false])
|
||||
);
|
||||
this.joinsNotNullableMap[tableName] = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Executes a `left join` operation by adding another table to the current query.
|
||||
*
|
||||
* Calling this method associates each row of the table with the corresponding row from the joined table, if a match is found. If no matching row exists, it sets all columns of the joined table to null.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/joins#left-join}
|
||||
*
|
||||
* @param table the table to join.
|
||||
* @param on the `on` clause.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all users and their pets
|
||||
* const usersWithPets: { user: User; pets: Pet | null; }[] = await db.select()
|
||||
* .from(users)
|
||||
* .leftJoin(pets, eq(users.id, pets.ownerId))
|
||||
*
|
||||
* // Select userId and petId
|
||||
* const usersIdsAndPetIds: { userId: number; petId: number | null; }[] = await db.select({
|
||||
* userId: users.id,
|
||||
* petId: pets.id,
|
||||
* })
|
||||
* .from(users)
|
||||
* .leftJoin(pets, eq(users.id, pets.ownerId))
|
||||
* ```
|
||||
*/
|
||||
leftJoin = this.createJoin("left");
|
||||
/**
|
||||
* Executes a `right join` operation by adding another table to the current query.
|
||||
*
|
||||
* Calling this method associates each row of the joined table with the corresponding row from the main table, if a match is found. If no matching row exists, it sets all columns of the main table to null.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/joins#right-join}
|
||||
*
|
||||
* @param table the table to join.
|
||||
* @param on the `on` clause.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all users and their pets
|
||||
* const usersWithPets: { user: User | null; pets: Pet; }[] = await db.select()
|
||||
* .from(users)
|
||||
* .rightJoin(pets, eq(users.id, pets.ownerId))
|
||||
*
|
||||
* // Select userId and petId
|
||||
* const usersIdsAndPetIds: { userId: number | null; petId: number; }[] = await db.select({
|
||||
* userId: users.id,
|
||||
* petId: pets.id,
|
||||
* })
|
||||
* .from(users)
|
||||
* .rightJoin(pets, eq(users.id, pets.ownerId))
|
||||
* ```
|
||||
*/
|
||||
rightJoin = this.createJoin("right");
|
||||
/**
|
||||
* Executes an `inner join` operation, creating a new table by combining rows from two tables that have matching values.
|
||||
*
|
||||
* Calling this method retrieves rows that have corresponding entries in both joined tables. Rows without matching entries in either table are excluded, resulting in a table that includes only matching pairs.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/joins#inner-join}
|
||||
*
|
||||
* @param table the table to join.
|
||||
* @param on the `on` clause.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all users and their pets
|
||||
* const usersWithPets: { user: User; pets: Pet; }[] = await db.select()
|
||||
* .from(users)
|
||||
* .innerJoin(pets, eq(users.id, pets.ownerId))
|
||||
*
|
||||
* // Select userId and petId
|
||||
* const usersIdsAndPetIds: { userId: number; petId: number; }[] = await db.select({
|
||||
* userId: users.id,
|
||||
* petId: pets.id,
|
||||
* })
|
||||
* .from(users)
|
||||
* .innerJoin(pets, eq(users.id, pets.ownerId))
|
||||
* ```
|
||||
*/
|
||||
innerJoin = this.createJoin("inner");
|
||||
/**
|
||||
* Executes a `full join` operation by combining rows from two tables into a new table.
|
||||
*
|
||||
* Calling this method retrieves all rows from both main and joined tables, merging rows with matching values and filling in `null` for non-matching columns.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/joins#full-join}
|
||||
*
|
||||
* @param table the table to join.
|
||||
* @param on the `on` clause.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all users and their pets
|
||||
* const usersWithPets: { user: User | null; pets: Pet | null; }[] = await db.select()
|
||||
* .from(users)
|
||||
* .fullJoin(pets, eq(users.id, pets.ownerId))
|
||||
*
|
||||
* // Select userId and petId
|
||||
* const usersIdsAndPetIds: { userId: number | null; petId: number | null; }[] = await db.select({
|
||||
* userId: users.id,
|
||||
* petId: pets.id,
|
||||
* })
|
||||
* .from(users)
|
||||
* .fullJoin(pets, eq(users.id, pets.ownerId))
|
||||
* ```
|
||||
*/
|
||||
fullJoin = this.createJoin("full");
|
||||
/**
|
||||
* Executes a `cross join` operation by combining rows from two tables into a new table.
|
||||
*
|
||||
* Calling this method retrieves all rows from both main and joined tables, merging all rows from each table.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/joins#cross-join}
|
||||
*
|
||||
* @param table the table to join.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all users, each user with every pet
|
||||
* const usersWithPets: { user: User; pets: Pet; }[] = await db.select()
|
||||
* .from(users)
|
||||
* .crossJoin(pets)
|
||||
*
|
||||
* // Select userId and petId
|
||||
* const usersIdsAndPetIds: { userId: number; petId: number; }[] = await db.select({
|
||||
* userId: users.id,
|
||||
* petId: pets.id,
|
||||
* })
|
||||
* .from(users)
|
||||
* .crossJoin(pets)
|
||||
* ```
|
||||
*/
|
||||
crossJoin = this.createJoin("cross");
|
||||
createSetOperator(type, isAll) {
|
||||
return (rightSelection) => {
|
||||
const rightSelect = typeof rightSelection === "function" ? rightSelection(getSQLiteSetOperators()) : rightSelection;
|
||||
if (!(0, import_utils.haveSameKeys)(this.getSelectedFields(), rightSelect.getSelectedFields())) {
|
||||
throw new Error(
|
||||
"Set operator error (union / intersect / except): selected fields are not the same or are in a different order"
|
||||
);
|
||||
}
|
||||
this.config.setOperators.push({ type, isAll, rightSelect });
|
||||
return this;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Adds `union` set operator to the query.
|
||||
*
|
||||
* Calling this method will combine the result sets of the `select` statements and remove any duplicate rows that appear across them.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/set-operations#union}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all unique names from customers and users tables
|
||||
* await db.select({ name: users.name })
|
||||
* .from(users)
|
||||
* .union(
|
||||
* db.select({ name: customers.name }).from(customers)
|
||||
* );
|
||||
* // or
|
||||
* import { union } from 'drizzle-orm/sqlite-core'
|
||||
*
|
||||
* await union(
|
||||
* db.select({ name: users.name }).from(users),
|
||||
* db.select({ name: customers.name }).from(customers)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
union = this.createSetOperator("union", false);
|
||||
/**
|
||||
* Adds `union all` set operator to the query.
|
||||
*
|
||||
* Calling this method will combine the result-set of the `select` statements and keep all duplicate rows that appear across them.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/set-operations#union-all}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all transaction ids from both online and in-store sales
|
||||
* await db.select({ transaction: onlineSales.transactionId })
|
||||
* .from(onlineSales)
|
||||
* .unionAll(
|
||||
* db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales)
|
||||
* );
|
||||
* // or
|
||||
* import { unionAll } from 'drizzle-orm/sqlite-core'
|
||||
*
|
||||
* await unionAll(
|
||||
* db.select({ transaction: onlineSales.transactionId }).from(onlineSales),
|
||||
* db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
unionAll = this.createSetOperator("union", true);
|
||||
/**
|
||||
* Adds `intersect` set operator to the query.
|
||||
*
|
||||
* Calling this method will retain only the rows that are present in both result sets and eliminate duplicates.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select course names that are offered in both departments A and B
|
||||
* await db.select({ courseName: depA.courseName })
|
||||
* .from(depA)
|
||||
* .intersect(
|
||||
* db.select({ courseName: depB.courseName }).from(depB)
|
||||
* );
|
||||
* // or
|
||||
* import { intersect } from 'drizzle-orm/sqlite-core'
|
||||
*
|
||||
* await intersect(
|
||||
* db.select({ courseName: depA.courseName }).from(depA),
|
||||
* db.select({ courseName: depB.courseName }).from(depB)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
intersect = this.createSetOperator("intersect", false);
|
||||
/**
|
||||
* Adds `except` set operator to the query.
|
||||
*
|
||||
* Calling this method will retrieve all unique rows from the left query, except for the rows that are present in the result set of the right query.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/set-operations#except}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all courses offered in department A but not in department B
|
||||
* await db.select({ courseName: depA.courseName })
|
||||
* .from(depA)
|
||||
* .except(
|
||||
* db.select({ courseName: depB.courseName }).from(depB)
|
||||
* );
|
||||
* // or
|
||||
* import { except } from 'drizzle-orm/sqlite-core'
|
||||
*
|
||||
* await except(
|
||||
* db.select({ courseName: depA.courseName }).from(depA),
|
||||
* db.select({ courseName: depB.courseName }).from(depB)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
except = this.createSetOperator("except", false);
|
||||
/** @internal */
|
||||
addSetOperators(setOperators) {
|
||||
this.config.setOperators.push(...setOperators);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Adds a `where` clause to the query.
|
||||
*
|
||||
* Calling this method will select only those rows that fulfill a specified condition.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/select#filtering}
|
||||
*
|
||||
* @param where the `where` clause.
|
||||
*
|
||||
* @example
|
||||
* You can use conditional operators and `sql function` to filter the rows to be selected.
|
||||
*
|
||||
* ```ts
|
||||
* // Select all cars with green color
|
||||
* await db.select().from(cars).where(eq(cars.color, 'green'));
|
||||
* // or
|
||||
* await db.select().from(cars).where(sql`${cars.color} = 'green'`)
|
||||
* ```
|
||||
*
|
||||
* You can logically combine conditional operators with `and()` and `or()` operators:
|
||||
*
|
||||
* ```ts
|
||||
* // Select all BMW cars with a green color
|
||||
* await db.select().from(cars).where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW')));
|
||||
*
|
||||
* // Select all cars with the green or blue color
|
||||
* await db.select().from(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue')));
|
||||
* ```
|
||||
*/
|
||||
where(where) {
|
||||
if (typeof where === "function") {
|
||||
where = where(
|
||||
new Proxy(
|
||||
this.config.fields,
|
||||
new import_selection_proxy.SelectionProxyHandler({ sqlAliasedBehavior: "sql", sqlBehavior: "sql" })
|
||||
)
|
||||
);
|
||||
}
|
||||
this.config.where = where;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Adds a `having` clause to the query.
|
||||
*
|
||||
* Calling this method will select only those rows that fulfill a specified condition. It is typically used with aggregate functions to filter the aggregated data based on a specified condition.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/select#aggregations}
|
||||
*
|
||||
* @param having the `having` clause.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Select all brands with more than one car
|
||||
* await db.select({
|
||||
* brand: cars.brand,
|
||||
* count: sql<number>`cast(count(${cars.id}) as int)`,
|
||||
* })
|
||||
* .from(cars)
|
||||
* .groupBy(cars.brand)
|
||||
* .having(({ count }) => gt(count, 1));
|
||||
* ```
|
||||
*/
|
||||
having(having) {
|
||||
if (typeof having === "function") {
|
||||
having = having(
|
||||
new Proxy(
|
||||
this.config.fields,
|
||||
new import_selection_proxy.SelectionProxyHandler({ sqlAliasedBehavior: "sql", sqlBehavior: "sql" })
|
||||
)
|
||||
);
|
||||
}
|
||||
this.config.having = having;
|
||||
return this;
|
||||
}
|
||||
groupBy(...columns) {
|
||||
if (typeof columns[0] === "function") {
|
||||
const groupBy = columns[0](
|
||||
new Proxy(
|
||||
this.config.fields,
|
||||
new import_selection_proxy.SelectionProxyHandler({ sqlAliasedBehavior: "alias", sqlBehavior: "sql" })
|
||||
)
|
||||
);
|
||||
this.config.groupBy = Array.isArray(groupBy) ? groupBy : [groupBy];
|
||||
} else {
|
||||
this.config.groupBy = columns;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
orderBy(...columns) {
|
||||
if (typeof columns[0] === "function") {
|
||||
const orderBy = columns[0](
|
||||
new Proxy(
|
||||
this.config.fields,
|
||||
new import_selection_proxy.SelectionProxyHandler({ sqlAliasedBehavior: "alias", sqlBehavior: "sql" })
|
||||
)
|
||||
);
|
||||
const orderByArray = Array.isArray(orderBy) ? orderBy : [orderBy];
|
||||
if (this.config.setOperators.length > 0) {
|
||||
this.config.setOperators.at(-1).orderBy = orderByArray;
|
||||
} else {
|
||||
this.config.orderBy = orderByArray;
|
||||
}
|
||||
} else {
|
||||
const orderByArray = columns;
|
||||
if (this.config.setOperators.length > 0) {
|
||||
this.config.setOperators.at(-1).orderBy = orderByArray;
|
||||
} else {
|
||||
this.config.orderBy = orderByArray;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Adds a `limit` clause to the query.
|
||||
*
|
||||
* Calling this method will set the maximum number of rows that will be returned by this query.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/select#limit--offset}
|
||||
*
|
||||
* @param limit the `limit` clause.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Get the first 10 people from this query.
|
||||
* await db.select().from(people).limit(10);
|
||||
* ```
|
||||
*/
|
||||
limit(limit) {
|
||||
if (this.config.setOperators.length > 0) {
|
||||
this.config.setOperators.at(-1).limit = limit;
|
||||
} else {
|
||||
this.config.limit = limit;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Adds an `offset` clause to the query.
|
||||
*
|
||||
* Calling this method will skip a number of rows when returning results from this query.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/select#limit--offset}
|
||||
*
|
||||
* @param offset the `offset` clause.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Get the 10th-20th people from this query.
|
||||
* await db.select().from(people).offset(10).limit(10);
|
||||
* ```
|
||||
*/
|
||||
offset(offset) {
|
||||
if (this.config.setOperators.length > 0) {
|
||||
this.config.setOperators.at(-1).offset = offset;
|
||||
} else {
|
||||
this.config.offset = offset;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
/** @internal */
|
||||
getSQL() {
|
||||
return this.dialect.buildSelectQuery(this.config);
|
||||
}
|
||||
toSQL() {
|
||||
const { typings: _typings, ...rest } = this.dialect.sqlToQuery(this.getSQL());
|
||||
return rest;
|
||||
}
|
||||
as(alias) {
|
||||
const usedTables = [];
|
||||
usedTables.push(...(0, import_utils2.extractUsedTable)(this.config.table));
|
||||
if (this.config.joins) {
|
||||
for (const it of this.config.joins) usedTables.push(...(0, import_utils2.extractUsedTable)(it.table));
|
||||
}
|
||||
return new Proxy(
|
||||
new import_subquery.Subquery(this.getSQL(), this.config.fields, alias, false, [...new Set(usedTables)]),
|
||||
new import_selection_proxy.SelectionProxyHandler({ alias, sqlAliasedBehavior: "alias", sqlBehavior: "error" })
|
||||
);
|
||||
}
|
||||
/** @internal */
|
||||
getSelectedFields() {
|
||||
return new Proxy(
|
||||
this.config.fields,
|
||||
new import_selection_proxy.SelectionProxyHandler({ alias: this.tableName, sqlAliasedBehavior: "alias", sqlBehavior: "error" })
|
||||
);
|
||||
}
|
||||
$dynamic() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class SQLiteSelectBase extends SQLiteSelectQueryBuilderBase {
|
||||
static [import_entity.entityKind] = "SQLiteSelect";
|
||||
/** @internal */
|
||||
_prepare(isOneTimeQuery = true) {
|
||||
if (!this.session) {
|
||||
throw new Error("Cannot execute a query on a query builder. Please use a database instance instead.");
|
||||
}
|
||||
const fieldsList = (0, import_utils.orderSelectedFields)(this.config.fields);
|
||||
const query = this.session[isOneTimeQuery ? "prepareOneTimeQuery" : "prepareQuery"](
|
||||
this.dialect.sqlToQuery(this.getSQL()),
|
||||
fieldsList,
|
||||
"all",
|
||||
true,
|
||||
void 0,
|
||||
{
|
||||
type: "select",
|
||||
tables: [...this.usedTables]
|
||||
},
|
||||
this.cacheConfig
|
||||
);
|
||||
query.joinsNotNullableMap = this.joinsNotNullableMap;
|
||||
return query;
|
||||
}
|
||||
$withCache(config) {
|
||||
this.cacheConfig = config === void 0 ? { config: {}, enable: true, autoInvalidate: true } : config === false ? { enable: false } : { enable: true, autoInvalidate: true, ...config };
|
||||
return this;
|
||||
}
|
||||
prepare() {
|
||||
return this._prepare(false);
|
||||
}
|
||||
run = (placeholderValues) => {
|
||||
return this._prepare().run(placeholderValues);
|
||||
};
|
||||
all = (placeholderValues) => {
|
||||
return this._prepare().all(placeholderValues);
|
||||
};
|
||||
get = (placeholderValues) => {
|
||||
return this._prepare().get(placeholderValues);
|
||||
};
|
||||
values = (placeholderValues) => {
|
||||
return this._prepare().values(placeholderValues);
|
||||
};
|
||||
async execute() {
|
||||
return this.all();
|
||||
}
|
||||
}
|
||||
(0, import_utils.applyMixins)(SQLiteSelectBase, [import_query_promise.QueryPromise]);
|
||||
function createSetOperator(type, isAll) {
|
||||
return (leftSelect, rightSelect, ...restSelects) => {
|
||||
const setOperators = [rightSelect, ...restSelects].map((select) => ({
|
||||
type,
|
||||
isAll,
|
||||
rightSelect: select
|
||||
}));
|
||||
for (const setOperator of setOperators) {
|
||||
if (!(0, import_utils.haveSameKeys)(leftSelect.getSelectedFields(), setOperator.rightSelect.getSelectedFields())) {
|
||||
throw new Error(
|
||||
"Set operator error (union / intersect / except): selected fields are not the same or are in a different order"
|
||||
);
|
||||
}
|
||||
}
|
||||
return leftSelect.addSetOperators(setOperators);
|
||||
};
|
||||
}
|
||||
const getSQLiteSetOperators = () => ({
|
||||
union,
|
||||
unionAll,
|
||||
intersect,
|
||||
except
|
||||
});
|
||||
const union = createSetOperator("union", false);
|
||||
const unionAll = createSetOperator("union", true);
|
||||
const intersect = createSetOperator("intersect", false);
|
||||
const except = createSetOperator("except", false);
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
SQLiteSelectBase,
|
||||
SQLiteSelectBuilder,
|
||||
SQLiteSelectQueryBuilderBase,
|
||||
except,
|
||||
intersect,
|
||||
union,
|
||||
unionAll
|
||||
});
|
||||
//# sourceMappingURL=select.cjs.map
|
||||
@ -1 +0,0 @@
|
||||
{"version":3,"file":"innerFrom.js","sourceRoot":"","sources":["../../../../src/internal/observable/innerFrom.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,gCAAgC,EAAE,MAAM,gCAAgC,CAAC;AAClF,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,kCAAkC,EAAE,MAAM,8BAA8B,CAAC;AAExG,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,UAAU,IAAI,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAGvE,MAAM,UAAU,SAAS,CAAI,KAAyB;IACpD,IAAI,KAAK,YAAY,UAAU,EAAE;QAC/B,OAAO,KAAK,CAAC;KACd;IACD,IAAI,KAAK,IAAI,IAAI,EAAE;QACjB,IAAI,mBAAmB,CAAC,KAAK,CAAC,EAAE;YAC9B,OAAO,qBAAqB,CAAC,KAAK,CAAC,CAAC;SACrC;QACD,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE;YACtB,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;SAC7B;QACD,IAAI,SAAS,CAAC,KAAK,CAAC,EAAE;YACpB,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;SAC3B;QACD,IAAI,eAAe,CAAC,KAAK,CAAC,EAAE;YAC1B,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;SACjC;QACD,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE;YACrB,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;SAC5B;QACD,IAAI,oBAAoB,CAAC,KAAK,CAAC,EAAE;YAC/B,OAAO,sBAAsB,CAAC,KAAK,CAAC,CAAC;SACtC;KACF;IAED,MAAM,gCAAgC,CAAC,KAAK,CAAC,CAAC;AAChD,CAAC;AAMD,MAAM,UAAU,qBAAqB,CAAI,GAAQ;IAC/C,OAAO,IAAI,UAAU,CAAC,UAAC,UAAyB;QAC9C,IAAM,GAAG,GAAG,GAAG,CAAC,iBAAiB,CAAC,EAAE,CAAC;QACrC,IAAI,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE;YAC7B,OAAO,GAAG,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;SAClC;QAED,MAAM,IAAI,SAAS,CAAC,gEAAgE,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;AACL,CAAC;AASD,MAAM,UAAU,aAAa,CAAI,KAAmB;IAClD,OAAO,IAAI,UAAU,CAAC,UAAC,UAAyB;QAU9C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;YAC3D,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;SAC3B;QACD,UAAU,CAAC,QAAQ,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,WAAW,CAAI,OAAuB;IACpD,OAAO,IAAI,UAAU,CAAC,UAAC,UAAyB;QAC9C,OAAO;aACJ,IAAI,CACH,UAAC,KAAK;YACJ,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE;gBACtB,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACvB,UAAU,CAAC,QAAQ,EAAE,CAAC;aACvB;QACH,CAAC,EACD,UAAC,GAAQ,IAAK,OAAA,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,EAArB,CAAqB,CACpC;aACA,IAAI,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,YAAY,CAAI,QAAqB;IACnD,OAAO,IAAI,UAAU,CAAC,UAAC,UAAyB;;;YAC9C,KAAoB,IAAA,aAAA,SAAA,QAAQ,CAAA,kCAAA,wDAAE;gBAAzB,IAAM,KAAK,qBAAA;gBACd,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACvB,IAAI,UAAU,CAAC,MAAM,EAAE;oBACrB,OAAO;iBACR;aACF;;;;;;;;;QACD,UAAU,CAAC,QAAQ,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAI,aAA+B;IAClE,OAAO,IAAI,UAAU,CAAC,UAAC,UAAyB;QAC9C,OAAO,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC,KAAK,CAAC,UAAC,GAAG,IAAK,OAAA,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,EAArB,CAAqB,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAI,cAAqC;IAC7E,OAAO,iBAAiB,CAAC,kCAAkC,CAAC,cAAc,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,SAAe,OAAO,CAAI,aAA+B,EAAE,UAAyB;;;;;;;;;oBACxD,kBAAA,cAAA,aAAa,CAAA;;;;;oBAAtB,KAAK,0BAAA,CAAA;oBACpB,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;oBAGvB,IAAI,UAAU,CAAC,MAAM,EAAE;wBACrB,WAAO;qBACR;;;;;;;;;;;;;;;;;;;;;oBAEH,UAAU,CAAC,QAAQ,EAAE,CAAC;;;;;CACvB"}
|
||||
@ -1,83 +0,0 @@
|
||||
import { entityKind } from "../../entity.cjs";
|
||||
import { QueryPromise } from "../../query-promise.cjs";
|
||||
import type { SingleStoreDialect } from "../dialect.cjs";
|
||||
import type { AnySingleStoreQueryResultHKT, PreparedQueryHKTBase, PreparedQueryKind, SingleStorePreparedQueryConfig, SingleStoreQueryResultHKT, SingleStoreQueryResultKind, SingleStoreSession } from "../session.cjs";
|
||||
import type { SingleStoreTable } from "../table.cjs";
|
||||
import type { Placeholder, Query, SQL, SQLWrapper } from "../../sql/sql.cjs";
|
||||
import type { Subquery } from "../../subquery.cjs";
|
||||
import type { ValueOrArray } from "../../utils.cjs";
|
||||
import type { SingleStoreColumn } from "../columns/common.cjs";
|
||||
import type { SelectedFieldsOrdered } from "./select.types.cjs";
|
||||
export type SingleStoreDeleteWithout<T extends AnySingleStoreDeleteBase, TDynamic extends boolean, K extends keyof T & string> = TDynamic extends true ? T : Omit<SingleStoreDeleteBase<T['_']['table'], T['_']['queryResult'], T['_']['preparedQueryHKT'], TDynamic, T['_']['excludedMethods'] | K>, T['_']['excludedMethods'] | K>;
|
||||
export type SingleStoreDelete<TTable extends SingleStoreTable = SingleStoreTable, TQueryResult extends SingleStoreQueryResultHKT = AnySingleStoreQueryResultHKT, TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase> = SingleStoreDeleteBase<TTable, TQueryResult, TPreparedQueryHKT, true, never>;
|
||||
export interface SingleStoreDeleteConfig {
|
||||
where?: SQL | undefined;
|
||||
limit?: number | Placeholder;
|
||||
orderBy?: (SingleStoreColumn | SQL | SQL.Aliased)[];
|
||||
table: SingleStoreTable;
|
||||
returning?: SelectedFieldsOrdered;
|
||||
withList?: Subquery[];
|
||||
}
|
||||
export type SingleStoreDeletePrepare<T extends AnySingleStoreDeleteBase> = PreparedQueryKind<T['_']['preparedQueryHKT'], SingleStorePreparedQueryConfig & {
|
||||
execute: SingleStoreQueryResultKind<T['_']['queryResult'], never>;
|
||||
iterator: never;
|
||||
}, true>;
|
||||
type SingleStoreDeleteDynamic<T extends AnySingleStoreDeleteBase> = SingleStoreDelete<T['_']['table'], T['_']['queryResult'], T['_']['preparedQueryHKT']>;
|
||||
type AnySingleStoreDeleteBase = SingleStoreDeleteBase<any, any, any, any, any>;
|
||||
export interface SingleStoreDeleteBase<TTable extends SingleStoreTable, TQueryResult extends SingleStoreQueryResultHKT, TPreparedQueryHKT extends PreparedQueryHKTBase, TDynamic extends boolean = false, TExcludedMethods extends string = never> extends QueryPromise<SingleStoreQueryResultKind<TQueryResult, never>> {
|
||||
readonly _: {
|
||||
readonly table: TTable;
|
||||
readonly queryResult: TQueryResult;
|
||||
readonly preparedQueryHKT: TPreparedQueryHKT;
|
||||
readonly dynamic: TDynamic;
|
||||
readonly excludedMethods: TExcludedMethods;
|
||||
};
|
||||
}
|
||||
export declare class SingleStoreDeleteBase<TTable extends SingleStoreTable, TQueryResult extends SingleStoreQueryResultHKT, TPreparedQueryHKT extends PreparedQueryHKTBase, TDynamic extends boolean = false, TExcludedMethods extends string = never> extends QueryPromise<SingleStoreQueryResultKind<TQueryResult, never>> implements SQLWrapper {
|
||||
private table;
|
||||
private session;
|
||||
private dialect;
|
||||
static readonly [entityKind]: string;
|
||||
private config;
|
||||
constructor(table: TTable, session: SingleStoreSession, dialect: SingleStoreDialect, withList?: Subquery[]);
|
||||
/**
|
||||
* Adds a `where` clause to the query.
|
||||
*
|
||||
* Calling this method will delete only those rows that fulfill a specified condition.
|
||||
*
|
||||
* See docs: {@link https://orm.drizzle.team/docs/delete}
|
||||
*
|
||||
* @param where the `where` clause.
|
||||
*
|
||||
* @example
|
||||
* You can use conditional operators and `sql function` to filter the rows to be deleted.
|
||||
*
|
||||
* ```ts
|
||||
* // Delete all cars with green color
|
||||
* db.delete(cars).where(eq(cars.color, 'green'));
|
||||
* // or
|
||||
* db.delete(cars).where(sql`${cars.color} = 'green'`)
|
||||
* ```
|
||||
*
|
||||
* You can logically combine conditional operators with `and()` and `or()` operators:
|
||||
*
|
||||
* ```ts
|
||||
* // Delete all BMW cars with a green color
|
||||
* db.delete(cars).where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW')));
|
||||
*
|
||||
* // Delete all cars with the green or blue color
|
||||
* db.delete(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue')));
|
||||
* ```
|
||||
*/
|
||||
where(where: SQL | undefined): SingleStoreDeleteWithout<this, TDynamic, 'where'>;
|
||||
orderBy(builder: (deleteTable: TTable) => ValueOrArray<SingleStoreColumn | SQL | SQL.Aliased>): SingleStoreDeleteWithout<this, TDynamic, 'orderBy'>;
|
||||
orderBy(...columns: (SingleStoreColumn | SQL | SQL.Aliased)[]): SingleStoreDeleteWithout<this, TDynamic, 'orderBy'>;
|
||||
limit(limit: number | Placeholder): SingleStoreDeleteWithout<this, TDynamic, 'limit'>;
|
||||
toSQL(): Query;
|
||||
prepare(): SingleStoreDeletePrepare<this>;
|
||||
execute: ReturnType<this['prepare']>['execute'];
|
||||
private createIterator;
|
||||
iterator: ReturnType<this["prepare"]>["iterator"];
|
||||
$dynamic(): SingleStoreDeleteDynamic<this>;
|
||||
}
|
||||
export {};
|
||||
@ -1,89 +0,0 @@
|
||||
# has-flag [](https://travis-ci.org/sindresorhus/has-flag)
|
||||
|
||||
> Check if [`argv`](https://nodejs.org/docs/latest/api/process.html#process_process_argv) has a specific flag
|
||||
|
||||
Correctly stops looking after an `--` argument terminator.
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<b>
|
||||
<a href="https://tidelift.com/subscription/pkg/npm-has-flag?utm_source=npm-has-flag&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
|
||||
</b>
|
||||
<br>
|
||||
<sub>
|
||||
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
|
||||
</sub>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
$ npm install has-flag
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
// foo.js
|
||||
const hasFlag = require('has-flag');
|
||||
|
||||
hasFlag('unicorn');
|
||||
//=> true
|
||||
|
||||
hasFlag('--unicorn');
|
||||
//=> true
|
||||
|
||||
hasFlag('f');
|
||||
//=> true
|
||||
|
||||
hasFlag('-f');
|
||||
//=> true
|
||||
|
||||
hasFlag('foo=bar');
|
||||
//=> true
|
||||
|
||||
hasFlag('foo');
|
||||
//=> false
|
||||
|
||||
hasFlag('rainbow');
|
||||
//=> false
|
||||
```
|
||||
|
||||
```
|
||||
$ node foo.js -f --unicorn --foo=bar -- --rainbow
|
||||
```
|
||||
|
||||
|
||||
## API
|
||||
|
||||
### hasFlag(flag, [argv])
|
||||
|
||||
Returns a boolean for whether the flag exists.
|
||||
|
||||
#### flag
|
||||
|
||||
Type: `string`
|
||||
|
||||
CLI flag to look for. The `--` prefix is optional.
|
||||
|
||||
#### argv
|
||||
|
||||
Type: `string[]`<br>
|
||||
Default: `process.argv`
|
||||
|
||||
CLI arguments.
|
||||
|
||||
|
||||
## Security
|
||||
|
||||
To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
MIT © [Sindre Sorhus](https://sindresorhus.com)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user