Compare commits
1 Commits
dev
...
198-monito
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88a65678c6 |
@ -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
|
||||
94
.github/agents/copilot-instructions.md
vendored
94
.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.
|
||||
|
||||
@ -191,81 +190,6 @@ ## Active Technologies
|
||||
- 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 +224,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
|
||||
- 198-monitoring-page-state: Added 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
|
||||
- 197-shared-detail-contract: Added PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `VerificationReportViewer`, `VerificationReportChangeIndicator`, `PolicyNormalizer`, `VersionDiff`, `DriftFindingDiffBuilder`, and `SettingsCatalogSettingsTable`
|
||||
- 205-compare-job-cleanup: Added 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
|
||||
<!-- 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.
|
||||
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"
|
||||
}
|
||||
}
|
||||
@ -1,37 +1,37 @@
|
||||
<!--
|
||||
Sync Impact Report
|
||||
|
||||
- Version change: 2.10.0 -> 2.11.0
|
||||
- Version change: 2.2.0 -> 2.3.0
|
||||
- Modified principles:
|
||||
- Expanded decision-first and operator-surface rules so operational,
|
||||
governance, evidence, onboarding, review, and support-facing
|
||||
detail/status surfaces separate decision content, operator
|
||||
diagnostics, and support/raw evidence
|
||||
- Expanded review and enforcement expectations so specs, plans,
|
||||
tasks, and checklists must make audience modes, raw/support
|
||||
gating, one dominant next action, and duplicate-truth prevention
|
||||
explicit
|
||||
- UI-CONST-001: expanded to make TenantPilot's decision-first
|
||||
governance identity explicit
|
||||
- UI-REVIEW-001: spec and PR review gates expanded for surface role,
|
||||
human-in-the-loop justification, workflow-vs-storage IA, and
|
||||
attention-load reduction
|
||||
- Immediate Retrofit Priorities: expanded with a classification-first
|
||||
wave for existing surfaces
|
||||
- Added sections:
|
||||
- Audience-Aware Decision Surfaces & Disclosure Ladder
|
||||
(DECIDE-AUD-001): requires customer-readable default paths,
|
||||
operator diagnostics as progressive disclosure, support/raw
|
||||
evidence gating, one dominant next action, and no duplicate truth
|
||||
across equal-priority cards
|
||||
- Decision-First Operating Model & Progressive Disclosure
|
||||
(DECIDE-001)
|
||||
- Removed sections: None
|
||||
- Templates requiring updates:
|
||||
- .specify/templates/spec-template.md: add audience-aware disclosure
|
||||
section + constitution prompts ✅
|
||||
- .specify/templates/plan-template.md: add audience/disclosure
|
||||
planning prompts + constitution checks ✅
|
||||
- .specify/templates/tasks-template.md: add decision/disclosure
|
||||
implementation + test tasks ✅
|
||||
- .specify/templates/checklist-template.md: add disclosure, raw-gating,
|
||||
one-primary-action, and duplicate-truth review checks ✅
|
||||
- docs/product/standards/README.md: refresh constitution index for
|
||||
the new audience-aware disclosure contract ✅
|
||||
- ✅ .specify/memory/constitution.md
|
||||
- ✅ .specify/templates/plan-template.md (Constitution Check updated for
|
||||
decision-first surface roles, workflow-first IA, and calm-surface
|
||||
review)
|
||||
- ✅ .specify/templates/spec-template.md (surface role classification,
|
||||
operator contract, and requirements updated for decision-first
|
||||
governance)
|
||||
- ✅ .specify/templates/tasks-template.md (implementation task guidance
|
||||
updated for progressive disclosure, single-case context, and
|
||||
attention-load reduction)
|
||||
- ✅ docs/product/standards/README.md (Constitution index updated for
|
||||
DECIDE-001)
|
||||
- Commands checked:
|
||||
- N/A `.specify/templates/commands/*.md` directory is not present
|
||||
- Follow-up TODOs: None
|
||||
- N/A `.specify/templates/commands/*.md` directory is not present in this repo
|
||||
- Follow-up TODOs:
|
||||
- Create a dedicated surface / IA classification spec to retrofit
|
||||
existing surfaces against DECIDE-001.
|
||||
-->
|
||||
|
||||
# TenantPilot Constitution
|
||||
@ -68,15 +68,6 @@ ### No Premature Abstraction (ABSTR-001)
|
||||
- Test convenience alone is not sufficient justification for a new abstraction.
|
||||
- Narrow abstractions are allowed when required for security, tenant isolation, auditability, compliance evidence, or queue/job execution correctness.
|
||||
|
||||
### First Provider Is Not Platform Core (PROV-001)
|
||||
- Microsoft is the current first provider, not the platform core.
|
||||
- Shared platform-owned contracts, taxonomies, identifiers, compare semantics, and operator vocabulary MUST NOT silently become Microsoft-shaped truth just because Microsoft is the only provider today.
|
||||
- Shared platform-owned boundaries SHOULD prefer neutral core terms such as `provider`, `connection`, `target scope`, `governed subject`, and `operation` unless the feature is intentionally provider-owned and explicitly bounded.
|
||||
- Shared core terms at shared boundaries (PROV-002): if a boundary is reused across multiple domains, features, or workflows, the default is neutral platform language rather than provider-specific labels or semantics.
|
||||
- No accidental deepening of provider coupling (PROV-003): a feature MAY retain provider-specific semantics at a provider-owned seam, but it MUST NOT spread those semantics deeper into platform-core contracts, shared persistence truth, shared taxonomies, or shared UI language without proving that the narrower current-release truth genuinely requires it.
|
||||
- Shared-boundary review is mandatory (PROV-004): when a feature touches a shared provider/platform seam, the spec, plan, and review MUST state whether the seam is provider-owned or platform-core, what provider-specific semantics remain, and why that choice is the narrowest correct implementation now.
|
||||
- Prefer bounded extraction over premature generalization (PROV-005): if an existing hotspot is too Microsoft-specific, the default remedy is a bounded normalization or extraction of that hotspot, not a speculative multi-provider framework with unused extension points.
|
||||
|
||||
### No New Persisted Truth Without Source-of-Truth Need (PERSIST-001)
|
||||
- New tables, persisted entities, or stored artifacts MUST represent real product truth that survives independently of the originating request, run, or view.
|
||||
- Persisted storage is justified only when at least one of these is true: it is a source of truth, has an independent lifecycle, must be audited independently, must outlive its originating run/request, is required for permissions/routing/compliance evidence, or is required for stable operator workflows over time.
|
||||
@ -94,14 +85,6 @@ ### UI Semantics Must Not Become Their Own Framework (UI-SEM-001)
|
||||
- Direct mapping from canonical domain truth to UI is preferred over intermediate semantic superstructures.
|
||||
- Presentation helpers SHOULD remain optional adapters, not mandatory architecture.
|
||||
|
||||
### Shared Pattern First For Cross-Cutting Interaction Classes (XCUT-001)
|
||||
- Cross-cutting interaction classes such as notifications, status messaging, action links, header actions, dashboard signals/cards, navigation entry points, alerts, evidence/report viewers, and similar operator-facing infrastructure MUST first attach to an existing shared contract, presenter, builder, renderer, or other shared path when one already exists.
|
||||
- New local or domain-specific implementations for an existing interaction class are allowed only when the current shared path is demonstrably insufficient for current-release truth.
|
||||
- The active spec MUST name the shared path being reused or explicitly record the deviation, why the existing path is insufficient, what consistency must still be preserved, and what ownership or spread-control cost the deviation creates.
|
||||
- The same interaction class MUST NOT develop parallel operator-facing UX languages for title/body/action structure, status semantics, action-label patterns, or deep-link behavior unless the deviation is explicit and justified.
|
||||
- Reviews MUST treat undocumented bypass of an existing shared path as drift and block merge until the feature converges on the shared path or records a bounded exception.
|
||||
- If the drift is discovered only after a feature is already implemented, the remedy is NOT to rewrite historical closed specs retroactively by default; instead the active work MUST record the issue as `document-in-feature` or escalate it as `follow-up-spec`, depending on whether the drift is contained or structural.
|
||||
|
||||
### V1 Prefers Explicit Narrow Implementations (V1-EXP-001)
|
||||
- For V1 and early product maturity, direct implementation, local mapping, explicit per-domain logic, small focused helpers, derived read models, and minimal UI adapters are preferred.
|
||||
- Generic platform engines, meta-frameworks, universal resolver systems, workflow frameworks, and broad semantic taxonomies are disfavored until real variance proves them necessary.
|
||||
@ -124,20 +107,6 @@ ### Tests Must Protect Business Truth (TEST-TRUTH-001)
|
||||
- Large dedicated test surfaces for thin presentation indirection SHOULD be avoided.
|
||||
- If a pattern creates more test burden than product certainty, the pattern SHOULD be simplified.
|
||||
|
||||
### Test Suite Governance Must Live In The Delivery Workflow (TEST-GOV-001)
|
||||
- Test-suite governance is a standing workflow rule, not an occasional cleanup project.
|
||||
- Every spec or implementation change that changes runtime behavior, tests, lane mix, or shared test infrastructure MUST state test impact explicitly: affected validation lane(s), actual test purpose classification (`Unit`, `Feature`, `Heavy-Governance`, `Browser`), any new or broader test family, fixture/helper/factory/seed/context cost change, and any budget, baseline, or trend follow-up.
|
||||
- Docs-only, template-only, or otherwise no-runtime-impact work MAY answer the test-governance prompts with concise `N/A` or `none`, but MUST still make the absence of runtime or suite impact explicit.
|
||||
- Test classification MUST follow the proving purpose of the change rather than directory names, filenames, or convenience. Fast or narrow lanes MUST NOT silently absorb discovery, surface, workflow, or browser cost that belongs in heavier governance lanes.
|
||||
- Minimal fixtures and minimal infrastructure are the default. Database, Livewire, Filament, provider setup, workspace or membership context, session state, capability context, and similar expensive dependencies MUST be used only when the asserted behavior requires them.
|
||||
- Heavy families MUST remain explicit in naming, lane assignment, and review rationale. New or expanded heavy-governance, discovery, surface, broad workflow, or browser families MUST NOT appear accidentally through helper drift, copied setup, or folder placement alone.
|
||||
- Shared helpers, factories, seeds, and support layers MUST keep expensive context opt-in. Provider, workspace, membership, capability, session, or similar full-context defaults MUST NOT become implicit norms.
|
||||
- Lane budgets, baselines, and trend reports are engineering constraints. Changes that materially worsen runtime, shift lane semantics, or create heavy cost centers MUST be validated, documented, and escalated when the impact exceeds ordinary feature-local upkeep.
|
||||
- Reviews MUST stop test drift before merge. Reviewers MUST verify lane fit, test breadth, fixture cost, heavy-family risk, and runtime impact, and MUST treat unnecessary breadth, wrong classification, or hidden cost as merge blockers rather than later CI cleanup.
|
||||
- Review checklists MUST end with one explicit outcome: `keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split`, so the decision about suite-cost risk is attributable instead of implied.
|
||||
- New governance cost centers, including new heavy families, new browser coverage, material lane-cost shifts, revived expensive defaults, or budget/baseline-relevant regressions, MUST be documented explicitly. Contained feature-local cases MAY be `document-in-feature`; structural or recurring cases MUST escalate to `follow-up-spec`; unjustified scope or hidden cost MUST resolve as `reject-or-split`.
|
||||
- These rules MUST stay visible in the spec, plan, task, and review routine. Test governance MUST NOT live only in CI output, wrapper scripts, or tribal knowledge.
|
||||
|
||||
### Enterprise Complexity Is Allowed Only Where Risk Demands It (RISK-COMP-001)
|
||||
- Heavier architecture is explicitly legitimate for workspace or tenant isolation, RBAC and policy enforcement, auditability, immutable history and snapshot truth, queue/job execution legitimacy, provider credential safety, retention/compliance evidence, and operator-critical lifecycle correctness.
|
||||
- Badge systems, explanation builders, trust/confidence overlays, presentation taxonomies, generic provider frameworks without real provider variance, speculative export/report/review infrastructure, UI meta-governance frameworks, and derived helper entities promoted into persisted truth are high-risk overproduction zones and require extra restraint.
|
||||
@ -165,37 +134,6 @@ ### Spec Candidate Gate (SPEC-GATE-001)
|
||||
### Default Bias (BIAS-001)
|
||||
- Default codebase bias is: derive before persist, map before frameworkize, localize before generalize, simplify before extend, replace before layer, explicit before generic, and present directly before interpreting recursively.
|
||||
|
||||
### Pre-Production Lean Doctrine (LEAN-001)
|
||||
|
||||
This product has no production deployment, no live customer data, no shared staging with migration-relevant state, and no external API contract consumers.
|
||||
|
||||
#### Data and schema
|
||||
- Old data shapes, column names, enum values, and operation types MAY be replaced in place.
|
||||
- Migration shims, dual-write logic, and fallback readers MUST NOT be created unless a spec explicitly requires compatibility behavior.
|
||||
|
||||
#### Terminology and types
|
||||
- Renamed or unified operation types, reason codes, and status values MUST replace the old value everywhere (code, config, tests, fixtures, seed data).
|
||||
- Legacy aliases kept "just in case" are forbidden.
|
||||
|
||||
#### Codebase hygiene
|
||||
- Dead constants, dead enum cases, orphan config keys, and test fixtures that reference replaced shapes MUST be removed in the same PR that introduces the replacement.
|
||||
- "Old runs / old rows don't matter" is the standing assumption until the product ships.
|
||||
|
||||
#### AI-agent rule
|
||||
- Before adding aliases, fallback readers, dual-write logic, migration shims, or legacy fixtures, agents MUST verify:
|
||||
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.
|
||||
|
||||
#### Review rule
|
||||
- Any PR that introduces a new legacy alias, compatibility shim, or historical fixture without answering the four questions above is a merge blocker.
|
||||
|
||||
#### Exit condition
|
||||
- LEAN-001 expires when the first production deployment occurs.
|
||||
- At that point, the constitution MUST be amended to define the real migration and compatibility policy.
|
||||
|
||||
### Workspace Isolation is Non-negotiable
|
||||
- Workspace membership is an isolation boundary. If the actor is not entitled to the workspace scope, the system MUST respond as
|
||||
deny-as-not-found (404).
|
||||
@ -313,57 +251,24 @@ ### Operations / Run Observability Standard
|
||||
even if implemented by multiple jobs/steps (“umbrella run”).
|
||||
- “Single-row” runs MUST still use consistent counters (e.g., `total=1`, `processed=0|1`) and outcome derived from success/failure.
|
||||
- Monitoring pages MUST be DB-only at render time (no external calls).
|
||||
- Start surfaces MUST NOT perform remote work inline and MUST NOT compose OperationRun start UX locally; they only:
|
||||
authorize, create/reuse run (dedupe), enqueue work, and hand queued/start-state feedback to the shared
|
||||
OperationRun Start UX Contract.
|
||||
|
||||
### OperationRun Start UX Contract (OPS-UX-START-001)
|
||||
|
||||
- OperationRun UX MUST be contract-driven, not surface-driven.
|
||||
- Any feature that creates, queues, deduplicates, resumes, blocks, completes, or links to an `OperationRun` MUST use
|
||||
the central OperationRun Start UX Contract.
|
||||
- Filament Pages, Resources, Widgets, Livewire Components, Actions, and Services MUST NOT independently compose
|
||||
OperationRun start UX from local pieces.
|
||||
- The shared OperationRun UX layer MUST own:
|
||||
- local start notification / toast
|
||||
- `Open operation` / `View run` link
|
||||
- artifact link such as `View snapshot`, `View pack`, or `View restore`
|
||||
- run-enqueued browser event
|
||||
- queued DB-notification decision
|
||||
- dedupe / already-available / already-running messaging
|
||||
- blocked / failed-to-start messaging
|
||||
- tenant/workspace-safe operation URL resolution
|
||||
- Feature surfaces MAY initiate `OperationRun`s, but they MUST NOT define their own OperationRun UX semantics.
|
||||
- `OperationRun` lifecycle state remains the canonical execution truth.
|
||||
- Queued DB notifications MUST remain explicit opt-in unless the active spec defines a different policy.
|
||||
- Terminal `OperationRun` notifications MUST be emitted through the central OperationRun lifecycle mechanism.
|
||||
- Any exception MUST include:
|
||||
1. an explicit spec decision,
|
||||
2. a documented architecture note,
|
||||
3. a test or guard-test exception with rationale,
|
||||
4. a follow-up migration decision if the exception is temporary.
|
||||
- New OperationRun-starting features MUST include an `OperationRun UX Impact` section in the active spec or plan.
|
||||
- Start surfaces MUST NOT perform remote work inline; they only: authorize, create/reuse run (dedupe), enqueue work,
|
||||
confirm + “View run”.
|
||||
|
||||
### Operations UX — 3-Surface Feedback (OPS-UX-055) (NON-NEGOTIABLE)
|
||||
|
||||
If a feature creates/reuses `OperationRun`, its default feedback contract is exactly three surfaces.
|
||||
Queued DB notifications are forbidden by default and MAY exist only when the active spec explicitly opts into them
|
||||
through the OperationRun Start UX Contract:
|
||||
If a feature creates/reuses `OperationRun`, it MUST use exactly three feedback surfaces — no others:
|
||||
|
||||
1) Toast (intent only / queued-only)
|
||||
- A toast MAY be shown only when the run is accepted/queued (intent feedback).
|
||||
- The toast MUST use `OperationUxPresenter::queuedToast($operationType)->send()`.
|
||||
- Feature code MUST NOT craft ad-hoc operation toasts.
|
||||
- A dedicated dedupe message MUST use the presenter (e.g., `alreadyQueuedToast(...)`), not `Notification::make()`.
|
||||
- Queued toast copy, action links, artifact links, start-state browser events, and dedupe/start-failure messaging MUST be
|
||||
produced by the shared OperationRun Start UX Contract, not by local surface code.
|
||||
|
||||
2) Progress (active awareness only)
|
||||
- Live progress MUST exist only in:
|
||||
- the global active-ops widget, and
|
||||
- Monitoring → Operation Run Detail.
|
||||
- These surfaces MUST show only active runs (`queued|running`) and MUST never show terminal runs.
|
||||
- Running DB notifications are forbidden.
|
||||
- Determinate progress is allowed ONLY when `summary_counts.total` and `summary_counts.processed` are valid numeric values.
|
||||
- Determinate progress MUST be clamped to 0–100. Otherwise render indeterminate + elapsed time.
|
||||
- The widget MUST NOT show percentage text (optional `processed/total` is allowed).
|
||||
@ -404,10 +309,6 @@ ### Ops-UX regression guards are mandatory (OPS-UX-GUARD-001)
|
||||
|
||||
The repo MUST include automated guards (Pest) that fail CI if:
|
||||
- any direct `OperationRun` status/outcome transition occurs outside `OperationRunService`,
|
||||
- feature code bypasses the central OperationRun Start UX Contract for queued/start-state operation UX where the repo's
|
||||
guardable patterns can detect it,
|
||||
- feature code emits queued DB notifications for operations without explicit spec-driven opt-in through the shared
|
||||
OperationRun UX layer,
|
||||
- jobs emit DB notifications for operation completion/abort (`OperationRunCompleted` is the single terminal notification),
|
||||
- deprecated legacy operation notification classes are referenced again.
|
||||
|
||||
@ -593,114 +494,11 @@ ##### Review gate
|
||||
5. Is this a Primary Decision Surface, Secondary Context Surface, or
|
||||
Tertiary Evidence / Diagnostics Surface?
|
||||
6. If it is primary, why can it not live inside an existing decision
|
||||
context?
|
||||
context?
|
||||
7. Does the navigation reflect a workflow or only storage structure?
|
||||
8. Does this reduce search, review, or click work?
|
||||
9. Does this make the product calmer and clearer instead of louder?
|
||||
|
||||
#### Audience-Aware Decision Surfaces & Disclosure Ladder (DECIDE-AUD-001)
|
||||
|
||||
Goal: every operational, governance, evidence, onboarding, review, and
|
||||
support-facing detail or status surface MUST keep customer-readable
|
||||
decision content, operator diagnostics, and support/raw evidence
|
||||
intentionally separated while preserving full depth through progressive
|
||||
disclosure.
|
||||
|
||||
##### Audience ladder is explicit
|
||||
|
||||
- In-scope detail and status surfaces MUST define their content using
|
||||
this three-tier hierarchy when applicable:
|
||||
- decision content
|
||||
- operator diagnostics
|
||||
- support / raw evidence
|
||||
- Surfaces that are reachable by more than one audience class MUST
|
||||
define their default-visible content for at least these layers when
|
||||
applicable:
|
||||
- customer / read-only default
|
||||
- operator / MSP diagnostics
|
||||
- platform / support raw evidence
|
||||
- The surface contract MUST state which capabilities unlock each deeper
|
||||
layer.
|
||||
- Support/raw evidence MUST NOT become the default first-read
|
||||
experience on customer-readable or ordinary operator-facing
|
||||
surfaces.
|
||||
|
||||
##### Customer-readable default path
|
||||
|
||||
- The default reading path for customer/read-only users MUST optimize
|
||||
for status, reason, impact, one dominant next action, and a short
|
||||
result or artifact summary.
|
||||
- Internal lifecycle wording, debug semantics, implementation field
|
||||
names, raw payload fragments, and support-oriented context MUST NOT
|
||||
appear in the default customer-readable path unless they are the only
|
||||
way to understand the first decision.
|
||||
- Default-visible customer/read-only content is responsible for status,
|
||||
reason, impact, the dominant next action, and a concise supporting
|
||||
summary only.
|
||||
|
||||
##### Diagnostics are secondary by default
|
||||
|
||||
- Diagnostics such as lifecycle, timings, verification detail, drift
|
||||
detail, permission detail, provider summaries, or related-operation
|
||||
context MUST be lower-priority than the decision surface and MUST be
|
||||
collapsed, tabbed, grouped, or otherwise progressively disclosed when
|
||||
the first decision does not require them.
|
||||
- Authorized operators MAY expand diagnostics, but diagnostics MUST NOT
|
||||
visually compete with the primary decision block.
|
||||
- Where no support/raw tier is exposed, diagnostics still remain below
|
||||
the decision tier and MUST NOT restate the same decision summary at
|
||||
equal weight.
|
||||
|
||||
##### Raw/support evidence is gated
|
||||
|
||||
- Raw/support evidence such as JSON, raw context payloads,
|
||||
fingerprints, internal reason ownership, platform reason families,
|
||||
monitoring detail, viewer context, or copy/show-raw actions MUST NOT
|
||||
appear in the default decision path.
|
||||
- These details MUST live behind explicit reveal affordances and MUST
|
||||
be capability-gated wherever the audience model distinguishes support
|
||||
or platform users from ordinary operators.
|
||||
- Capability-gated support/raw disclosure MUST fail closed when the
|
||||
actor lacks the required scope or capability.
|
||||
|
||||
##### One dominant next action
|
||||
|
||||
- A decision surface MUST expose exactly one dominant next action in
|
||||
the default-visible region.
|
||||
- Optional secondary actions MAY exist, but they MUST NOT compete with
|
||||
the primary remediation or decision action in prominence.
|
||||
- Contextual navigation such as opening a related run, tenant, report,
|
||||
or technical detail remains secondary.
|
||||
|
||||
##### No duplicate truth across equal-priority cards
|
||||
|
||||
- The same blocker, reason, or next action MUST NOT be repeated across
|
||||
multiple equal-priority cards, sections, or summary blocks on the
|
||||
same default-visible surface.
|
||||
- Supporting evidence MAY restate the underlying proof, but the
|
||||
dominant decision message appears once and diagnostics elaborate
|
||||
beneath it.
|
||||
|
||||
##### Required tests
|
||||
|
||||
- New or materially changed customer/operator-facing detail surfaces
|
||||
MUST include focused tests proving:
|
||||
- default-visible content shows status, reason, impact, and next
|
||||
action,
|
||||
- exactly one dominant next action is primary,
|
||||
- diagnostics are secondary or collapsed,
|
||||
- raw/support evidence is not default-visible,
|
||||
- support/raw sections are capability-gated where applicable,
|
||||
- and duplicate visible decision summaries are absent.
|
||||
|
||||
##### Stored evidence wins over fallback diagnostics
|
||||
|
||||
- When a stored verification or report artifact exists, fallback
|
||||
technical diagnostics SHOULD demote behind supporting evidence or
|
||||
technical details instead of remaining peer-level default content.
|
||||
- Fallback diagnostics MAY become temporarily prominent only when the
|
||||
higher-level artifact does not yet exist or is unavailable.
|
||||
|
||||
#### Surface Taxonomy (UI-SURF-001)
|
||||
|
||||
Every new admin surface MUST be assigned exactly one broad action-surface
|
||||
@ -768,24 +566,6 @@ ##### Detail-first Operational Surface
|
||||
- Destructive actions: detail header or grouped header actions only, always with confirmation.
|
||||
- Row click and explicit View/Inspect: not applicable.
|
||||
|
||||
##### Native vs custom and shared-family classification
|
||||
- Every operator-facing surface MUST also classify whether it is a
|
||||
`Native Surface`, a `Custom Surface`, or a `Shared Detail Micro-UI`
|
||||
embedded inside a `Host`.
|
||||
- `Native Surface` means the primary interaction contract is expressed
|
||||
through Filament-native components or approved shared primitives.
|
||||
- `Custom Surface` means the operator need is materially richer than
|
||||
standard CRUD, overview, or report semantics and the deviation is
|
||||
justified through UI-EX-001.
|
||||
- `Shared Detail Micro-UI` means a repeated embedded review, evidence,
|
||||
or detail surface that appears in more than one host and must read as
|
||||
the same family wherever it appears.
|
||||
- `Host` means the page, resource, workbench, or detail surface that
|
||||
embeds a shared detail micro-UI and owns routing, authorization, the
|
||||
outer inspect/open model, and host-only actions.
|
||||
- A `Fake-Native Surface` is never an allowed classification. It is a
|
||||
violation class defined by UI-HARD-001 and UI-FIL-001.
|
||||
|
||||
#### Action Surface Discipline (ACTSURF-001)
|
||||
|
||||
Goal: actions across all surfaces MUST make the next sensible operator
|
||||
@ -856,22 +636,6 @@ ##### Utility / System surfaces
|
||||
- System or recovery status does not justify casual placement of
|
||||
destructive or governance-changing actions.
|
||||
|
||||
##### Shared detail families and one primary interaction model
|
||||
|
||||
- A shared detail micro-UI MUST define one family-level core
|
||||
interaction model before a second host extends it.
|
||||
- Hosts MAY vary framing, assist entry, surrounding navigation, or
|
||||
optional diagnostics only when the shared core remains recognizable
|
||||
and the variation is explicit.
|
||||
- The host owns page-level navigation, authorization, surrounding
|
||||
mutations, and dangerous actions. The shared family owns only the
|
||||
repeated read/inspect/view semantics that are intentionally common.
|
||||
- One user concern MUST NOT be split across two peer interaction
|
||||
models on the same page.
|
||||
- `Parallel Inspect Worlds` means the same concern is driven by two
|
||||
competing inspect, selected-record, or view-state owners. It is
|
||||
forbidden.
|
||||
|
||||
##### Action grouping and order
|
||||
|
||||
- Actions MUST be ordered by meaning, frequency, and risk.
|
||||
@ -941,12 +705,7 @@ ##### Review gate
|
||||
3. Is navigation cleanly separated from mutation?
|
||||
4. Are rare or risky actions removed from the primary plane?
|
||||
5. Is the hierarchy scanable in a few seconds?
|
||||
6. If this is a repeated detail family, what is shared core vs
|
||||
host-owned variation?
|
||||
7. Does one concern still have exactly one primary interaction model?
|
||||
8. Which layer owns the relevant truth: shell, page, or detail?
|
||||
9. Is any exception real, bounded, and named, or is it a hidden
|
||||
exception?
|
||||
6. Is this a real special type or just an unordered exception?
|
||||
|
||||
If those answers are not clear, the surface is non-conformant.
|
||||
|
||||
@ -965,11 +724,6 @@ ##### Primary inspect model
|
||||
- A surface MUST NOT offer row click, identifier click, and explicit View/Inspect for the same destination as parallel primary models.
|
||||
- CRUD / List-first and Read-only Registry / Report surfaces MUST provide an obvious one-click open path.
|
||||
- Queue / Review and History / Audit surfaces MUST use explicit Inspect rather than row-click navigation.
|
||||
- Inline detail, summary, or sidebar inspect MAY exist only as
|
||||
subordinate presentations of the same selected-record truth, not as a
|
||||
second inspect contract.
|
||||
- `Parallel Inspect Worlds` are forbidden even when each local variant
|
||||
looks individually reasonable.
|
||||
|
||||
##### Row-click semantics
|
||||
- Full-row click is the default for CRUD / List-first and Read-only Registry / Report surfaces.
|
||||
@ -988,29 +742,6 @@ ##### Action hierarchy
|
||||
- All other secondary actions MUST move to overflow.
|
||||
- Long-running workflow launches such as sync, compare, verify, generate, consent, setup, or retry SHOULD live in list headers or detail headers rather than in every row.
|
||||
|
||||
##### Native-by-default and fake-native drift
|
||||
- Standard form, filter, table, action, tab, badge, link, and overview
|
||||
work is `Native Surface` work by default when Filament or an existing
|
||||
shared primitive can express it.
|
||||
- A `Fake-Native Surface` is any surface that visually lives inside
|
||||
Filament but keeps a second HTML, GET, query, or Blade-request
|
||||
interaction contract for its primary behavior.
|
||||
- Simple report or overview pages with ordinary columns, filters, empty
|
||||
states, and navigation default to native table semantics.
|
||||
- `Filament Costume` means locally assembled markup imitates native
|
||||
Filament controls, badges, or actions even though native or shared
|
||||
primitives fit. It is forbidden.
|
||||
- `Blade Request UI` means the primary body-state contract depends on
|
||||
`request()`, GET forms, or manual query parsing inside an active
|
||||
Filament surface. It is forbidden unless a documented exception limits
|
||||
request input to initialization-only behavior.
|
||||
- `Hand-Rolled Simple Overview` means a simple report or overview is
|
||||
rebuilt as bespoke markup where native table/list/report semantics
|
||||
fit. It is forbidden.
|
||||
- `Hidden Exception` means a surface behaves like a custom or special
|
||||
case without naming an exception type and reason block. It is
|
||||
forbidden.
|
||||
|
||||
##### Destructive actions
|
||||
- Destructive actions MUST NOT appear inline beside the primary inspect interaction on standard CRUD, Config-lite, or Read-only Registry surfaces.
|
||||
- Destructive actions MUST live in overflow or the detail header.
|
||||
@ -1058,16 +789,6 @@ ##### Row density and scanability
|
||||
- Standard CRUD rows MUST NOT carry more than one sentence of flowing prose.
|
||||
- Next-step prose belongs in detail, inspect, or queue surfaces, not in ordinary CRUD rows.
|
||||
|
||||
##### Shared-family and state-layer violations
|
||||
- `Host Drift` occurs when a host silently redefines a shared detail
|
||||
micro-UI's core zones, diagnostics contract, or primary view/inspect
|
||||
model. Host Drift is forbidden.
|
||||
- `State Layer Collapse` occurs when shell, page, or detail layers each
|
||||
claim the same active truth or restoration responsibility. It is
|
||||
forbidden.
|
||||
- A lower layer MAY format or reveal a higher-layer truth, but it MUST
|
||||
NOT quietly become the higher layer's authority.
|
||||
|
||||
##### Custom abstractions
|
||||
- Custom UI abstractions MAY document and validate, but they MUST NOT create declaration-only safety that diverges from real behavior.
|
||||
- Contract systems MUST NOT force placeholder UI.
|
||||
@ -1078,11 +799,6 @@ #### Exception Model (UI-EX-001)
|
||||
|
||||
Only catalogued exception types are allowed. Every exception MUST be named in the spec, reference its exception type, include a reason block, be called out explicitly in the PR, and carry at least one dedicated test.
|
||||
|
||||
- A `Legitimate Exception` is a named, bounded deviation that states the
|
||||
product reason, the smallest custom behavior required, what remains
|
||||
standardized, which layer owns the relevant state, and what proof or
|
||||
review evidence keeps it from turning into a general permission slip.
|
||||
|
||||
##### Queue Decision Exception
|
||||
- Allowed when per-item decision-making is the real queue work.
|
||||
- Guardrails: Inspect remains available unless detail is already inline; irreversible decisions require confirmation; unrelated maintenance actions do not join the row.
|
||||
@ -1103,38 +819,6 @@ ##### Cross-panel Canonical Route Exception
|
||||
- Allowed when only one canonical surface makes sense.
|
||||
- Guardrails: nouns stay stable; shell transition is explicit; back navigation is clear; scope signals remain truthful.
|
||||
|
||||
##### Legitimate Custom Surface Exception
|
||||
- Allowed when the operator need is materially richer than ordinary
|
||||
CRUD, overview, or simple report semantics, such as richer
|
||||
visualization, high-value diagnostic or review work, multi-zone shared
|
||||
detail micro-UI, shell-context-specific UI, or domain presentation
|
||||
that native primitives do not express cleanly.
|
||||
- Guardrails: the spec MUST state the product reason, the smallest
|
||||
custom behavior required, which native/shared primitives still apply,
|
||||
which layer owns the relevant state, and what remains standardized.
|
||||
|
||||
##### Nativity Exception
|
||||
- Allowed only when Filament-native or shared primitives cannot express
|
||||
the required semantics cleanly.
|
||||
- Guardrails: the exception MUST name the missing semantic, reject
|
||||
`Filament Costume`, `Blade Request UI`, and `Hand-Rolled Simple
|
||||
Overview` shortcuts, keep native/shared surrounding controls where
|
||||
they still fit, and MUST NOT invent a local status language.
|
||||
|
||||
##### Shared Detail Host Variation Exception
|
||||
- Allowed when a known shared detail micro-UI needs bounded host
|
||||
framing, assist entry, or optional-zone variation.
|
||||
- Guardrails: the host MUST NOT redefine the family core zones,
|
||||
next-step contract, diagnostics contract, or primary view/inspect
|
||||
model. Differences stay visibly host-scoped.
|
||||
|
||||
##### State-Layer Special-case Exception
|
||||
- Allowed when a page legitimately needs explicit requested, active,
|
||||
draft, inspect, or restorable roles beyond the simple default.
|
||||
- Guardrails: the owner layer MUST be explicit, the restorable subset
|
||||
MUST be explicit, any query role MUST be documented, and no lower
|
||||
layer may silently take over shell or page truth.
|
||||
|
||||
#### Filament UI — Action Surface Contract (NON-NEGOTIABLE)
|
||||
|
||||
For every new or modified Filament Resource, RelationManager, or Page:
|
||||
@ -1145,10 +829,6 @@ #### Filament UI — Action Surface Contract (NON-NEGOTIABLE)
|
||||
- Accepted forms are `recordUrl()` row click, a primary linked column, or an explicit row action when the taxonomy requires Inspect.
|
||||
- CRUD / List-first, Config-lite, and Read-only Registry surfaces MUST NOT render a redundant View action when the same destination is already available through row click or identifier click.
|
||||
- Queue / Review and History / Audit surfaces MAY use a lone explicit Inspect action because context-preserving inspect is the primary interaction.
|
||||
- Simple report or overview pages with ordinary columns, filters,
|
||||
empty-state behavior, and navigation MUST be implemented as native
|
||||
table surfaces unless UI-EX-001 documents why a richer custom surface
|
||||
is required.
|
||||
- View/Detail MUST define header actions and MUST keep destructive actions grouped and confirmed.
|
||||
- View/Detail MUST be sectioned using Infolists, Sections, Cards, Tabs, or equivalent composable structure.
|
||||
- Create/Edit MUST provide consistent Save and Cancel UX.
|
||||
@ -1157,9 +837,6 @@ #### Filament UI — Action Surface Contract (NON-NEGOTIABLE)
|
||||
- Standard CRUD and Read-only Registry rows MUST NOT exceed inspect/open plus one inline safe shortcut.
|
||||
- Queue / Review rows MAY expose inline decision actions only when allowed by UI-EX-001.
|
||||
- Everything else MUST move to `ActionGroup::make()` or the detail header.
|
||||
- Repeated embedded detail/evidence families MUST declare one shared
|
||||
core contract. Host-specific navigation, mutations, and destructive
|
||||
actions stay outside that shared core.
|
||||
- Bulk actions MUST be grouped via `BulkActionGroup` only when the surface has a real bulk use case.
|
||||
- Empty `ActionGroup` and `BulkActionGroup` are forbidden.
|
||||
- Destructive actions MUST NOT be primary and MUST require confirmation; typed confirmation MAY be required for large or high-risk bulk changes.
|
||||
@ -1174,12 +851,6 @@ #### Filament UI — Action Surface Contract (NON-NEGOTIABLE)
|
||||
- Every spec MUST include both a UI/UX Surface Classification and a UI Action Matrix.
|
||||
- Every changed operator-facing surface MUST declare its broad
|
||||
action-surface class and the one most likely next operator action.
|
||||
- Every changed operator-facing surface MUST also declare whether it is
|
||||
a `Native Surface`, `Custom Surface`, or `Shared Detail Micro-UI`, and
|
||||
MUST name any exception type it relies on.
|
||||
- If a surface uses shell, page, or detail state beyond simple static
|
||||
rendering, the governing spec MUST name which layer owns the relevant
|
||||
requested, active, draft, inspect, and restorable state.
|
||||
- Custom action-surface contracts are legitimate only when they validate rendered behavior, not only declarations or slot counts.
|
||||
- A change is not Done unless the implemented interaction semantics conform to the declared surface type or an approved exception documents and tests the deviation.
|
||||
|
||||
@ -1215,26 +886,6 @@ #### Filament UI — Layout & Information Architecture Standards (UX-001)
|
||||
- Standard CRUD tables MUST stay scanable and MUST NOT rely on row prose to communicate next steps.
|
||||
- Critical operational truth that informs list decisions MUST be default-visible.
|
||||
|
||||
State ownership
|
||||
- `Global Context State` is shell-owned workspace, tenant, or tenantless
|
||||
truth. Context bars and shell partials may display it, but they MUST
|
||||
NOT invent second precedence or fallback logic.
|
||||
- `Page State` is page-owned filter, tab, mode, or selected-record truth
|
||||
that changes the current page result or workflow.
|
||||
- `Detail State` is embedded viewer or shared-family state inside one
|
||||
detail surface and remains subordinate to shell and page truth unless
|
||||
an approved exception says otherwise.
|
||||
- `Requested State` is route, query, or upstream input before validation.
|
||||
- `Active State` is the currently governing validated state.
|
||||
- `Draft State` is local pending state that is intentionally separate
|
||||
from the currently applied result state.
|
||||
- `Inspect State` is the selected-record or selected-detail focus that
|
||||
drives inline or same-page inspect.
|
||||
- `Restorable State` is the subset intentionally recreated by refresh,
|
||||
back, bookmark, or shared link.
|
||||
- `State Layer Collapse` is forbidden. Shell, page, and detail layers
|
||||
MUST NOT silently overwrite one another's authority.
|
||||
|
||||
Enforcement
|
||||
- Shared layout builders such as `MainAsideForm`, `MainAsideInfolist`, and `StandardTableDefaults` SHOULD be reused where available.
|
||||
- A change is not Done unless UX-001 is satisfied or an approved exception documents why not.
|
||||
@ -1256,16 +907,6 @@ ##### Core rule
|
||||
contextual references do not belong in the header; they belong directly
|
||||
at the affected field, status indicator, or relation.
|
||||
|
||||
##### Shared-family host discipline
|
||||
|
||||
- If a record or detail page embeds a shared detail micro-UI, the
|
||||
header remains host-owned.
|
||||
- Family-level view switches, tabs, diagnostics reveals, and inner
|
||||
assist controls belong inside the family, not as copied header
|
||||
buttons.
|
||||
- Host-specific navigation or mutations MAY appear in the header only
|
||||
when they are truly host-critical and do not redefine the family core.
|
||||
|
||||
##### Maximum one primary visible header action
|
||||
|
||||
- Each record/detail page MUST expose at most one clearly prioritized
|
||||
@ -1368,9 +1009,6 @@ ##### Reviewer heuristics
|
||||
- Pure navigation buttons in the header.
|
||||
- Danger actions beside normal actions without clear separation.
|
||||
- Rarely used administrative actions as visible standard buttons.
|
||||
- Shared-family view switches or host-only forks are exported into the
|
||||
main header instead of staying inside the family or contextual
|
||||
placement.
|
||||
- The header resembles an action stockpile instead of a focused
|
||||
workflow entry point.
|
||||
|
||||
@ -1424,22 +1062,11 @@ #### Operator Surface Principles (OPSURF-001)
|
||||
- Diagnostic detail MAY exist, but it MUST be secondary and explicitly revealed.
|
||||
- JSON payloads, raw IDs, internal field names, provider error details, and low-level technical metadata belong in diagnostics surfaces rather than the primary content region.
|
||||
- Operators MUST NOT need to parse raw payloads to understand current state or next action.
|
||||
- Detail/status surfaces MUST satisfy DECIDE-AUD-001: decision content
|
||||
first, operator diagnostics second, support/raw evidence third.
|
||||
|
||||
Distinct truth dimensions
|
||||
- When the domain has execution outcome, data completeness, governance result, lifecycle or readiness state, operability truth, health truth, trust/confidence, or next action semantics, the surface MUST keep them explicit instead of collapsing them into one ambiguous status.
|
||||
- If multiple truth dimensions are summarized, the default-visible UI MUST label each dimension clearly.
|
||||
|
||||
Dominant next action and duplicate-truth control
|
||||
- Default-visible decision content MUST include status, reason,
|
||||
impact, and one dominant next action where those concepts exist.
|
||||
- Secondary navigation or debug helpers MUST remain lower-priority
|
||||
than the dominant decision action.
|
||||
- The same blocker, reason, impact, or next action MUST NOT be
|
||||
repeated across multiple default-visible cards, sections, tabs, or
|
||||
summaries.
|
||||
|
||||
Explicit mutation scope
|
||||
- Every state-changing action MUST communicate before execution whether it affects TenantPilot only, the Microsoft tenant, or simulation only.
|
||||
- Mutation scope MUST be understandable from nearby action copy, helper text, preview, or confirmation.
|
||||
@ -1460,13 +1087,6 @@ #### Operator Surface Principles (OPSURF-001)
|
||||
Page contract requirement
|
||||
- Every new or materially refactored operator-facing page MUST define the primary persona, surface type, primary operator question, default-visible information, diagnostics-only information, status dimensions used, mutation scope, primary actions, and dangerous actions.
|
||||
- The page contract MUST live in the governing spec and stay in sync with implementation.
|
||||
- Where multiple audience classes share the page, the contract MUST
|
||||
explicitly define the customer/read-only default path, operator
|
||||
diagnostics path, support/raw-evidence path, and the capabilities
|
||||
that unlock each layer.
|
||||
- The page contract MUST also make the dominant next action,
|
||||
duplicate-truth prevention, and raw/support gating explicit for
|
||||
changed detail/status surfaces.
|
||||
|
||||
#### Spec Scope Fields (SCOPE-002)
|
||||
|
||||
@ -1487,14 +1107,7 @@ #### Enforcement Model (UI-REVIEW-001)
|
||||
actions are ordered, canonical collection route, canonical detail
|
||||
route, scope signals and their exact meaning, canonical noun,
|
||||
critical truth visible by default, workflow-vs-storage IA
|
||||
justification, attention-load reduction, whether the surface is
|
||||
native, custom, or a shared detail family, what shared core vs host
|
||||
variation exists if relevant, which layer owns the relevant shell,
|
||||
page, and detail truth, which requested/active/draft/inspect/
|
||||
restorable roles exist, which audience ladder and disclosure
|
||||
boundaries exist, what the dominant next action is, how raw/support
|
||||
evidence is gated, how duplicate truth is prevented, whether any
|
||||
fake-native or host-drift risk is present, and whether an exception
|
||||
justification, attention-load reduction, and whether an exception
|
||||
type is used.
|
||||
- Missing any of those answers makes the spec incomplete.
|
||||
|
||||
@ -1510,15 +1123,8 @@ #### Enforcement Model (UI-REVIEW-001)
|
||||
promoted into primary navigation without justification, one case
|
||||
fragmented across multiple equal-rank pages, new automation that adds
|
||||
attention surfaces without reducing operator work, noisy default
|
||||
surfaces with no action/watch/reference hierarchy, duplicate visible
|
||||
blocker/reason/next-action summaries, customer/operator default paths
|
||||
that expose raw JSON, fingerprints, reason ownership, platform reason
|
||||
families, or monitoring detail, helper actions such as `Open
|
||||
operation`, `Technical details`, or `Show JSON` competing with the
|
||||
dominant decision action, `Filament Costume`,
|
||||
`Blade Request UI`, `Hand-Rolled Simple Overview`, `Hidden Exception`,
|
||||
`Host Drift`, `State Layer Collapse`, `Parallel Inspect Worlds`, or
|
||||
undocumented exceptions without dedicated tests.
|
||||
surfaces with no action/watch/reference hierarchy, or undocumented
|
||||
exceptions without dedicated tests.
|
||||
|
||||
Guard tests
|
||||
- Repository guards SHOULD validate: declared surface type, declared
|
||||
@ -1527,15 +1133,8 @@ #### Enforcement Model (UI-REVIEW-001)
|
||||
presence of explicit Inspect on Queue / Review and History / Audit
|
||||
surfaces, absence of empty `ActionGroup` or `BulkActionGroup`,
|
||||
correct placement of destructive actions, truthful scope signals,
|
||||
stable canonical nouns across shells, presence of a single dominant
|
||||
next action where surface metadata exposes one, absence of duplicate
|
||||
visible decision summaries, explicit raw/support gating or secondary
|
||||
placement where the surface serves multiple audience classes,
|
||||
absence of fake-native primary controls where metadata says the
|
||||
surface is native, bounded shared family contracts where metadata
|
||||
says a family is reused, explicit state ownership where specs or
|
||||
metadata expose it, and dedicated tests for every approved
|
||||
exception.
|
||||
stable canonical nouns across shells, and dedicated tests for every
|
||||
approved exception.
|
||||
|
||||
#### Immediate Retrofit Priorities
|
||||
|
||||
@ -1588,13 +1187,6 @@ #### Appendix A - One-page Condensed Constitution
|
||||
- Destructive actions never sit openly beside inspect on standard lists.
|
||||
- Overflow is standardized per surface class and is never empty.
|
||||
- Bulk exists only when it is genuinely useful.
|
||||
- Standard forms, filters, tables, tabs, badges, links, and simple
|
||||
overviews are native-by-default.
|
||||
- Fake-native surfaces, hidden exceptions, host drift, and state-layer
|
||||
collapse do not ship.
|
||||
- Repeated detail micro-UIs define shared core and bounded host
|
||||
variation before a second host forks them.
|
||||
- Shell, page, and detail truth each have one owner.
|
||||
- Navigation and mutation do not share equal visual weight without
|
||||
explicit hierarchy.
|
||||
- Monitoring and workbench surfaces separate scope/context, selection,
|
||||
@ -1602,10 +1194,6 @@ #### Appendix A - One-page Condensed Constitution
|
||||
- Scope chips must be truthful.
|
||||
- Domain nouns are canonical and stable.
|
||||
- Critical operational truth is default-visible.
|
||||
- Multi-audience detail/status surfaces keep customer-readable decision
|
||||
content above operator diagnostics and support/raw evidence.
|
||||
- One dominant next action stays visually primary.
|
||||
- Duplicate visible decision truth is forbidden.
|
||||
- Semantic truth dimensions are not collapsed into a generic status.
|
||||
- Standard lists stay scanable.
|
||||
- Exceptions are catalogued, justified, and tested.
|
||||
@ -1618,8 +1206,6 @@ #### Appendix B - Feature Review Checklist
|
||||
- The human-in-the-loop moment is explicit.
|
||||
- Immediate-visible decision information is explicit.
|
||||
- On-demand evidence / diagnostics boundaries are explicit.
|
||||
- Audience-aware default visibility and raw-evidence boundaries are
|
||||
explicit where the page serves more than one audience class.
|
||||
- Any new primary surface is justified against an existing decision
|
||||
context.
|
||||
- Navigation reflects a workflow rather than storage structure.
|
||||
@ -1629,16 +1215,10 @@ #### Appendix B - Feature Review Checklist
|
||||
- Broad action-surface class is declared.
|
||||
- Detailed surface type is declared.
|
||||
- The one most likely next operator action is explicit.
|
||||
- One dominant next action stays primary.
|
||||
- Duplicate visible decision truth is absent.
|
||||
- The surface is classified correctly as native, custom, or shared
|
||||
family.
|
||||
- Primary inspect/open model is defined.
|
||||
- Row-click rule is decided.
|
||||
- View/Inspect is correctly present or correctly forbidden.
|
||||
- Edit-as-inspect is used only when allowed.
|
||||
- Fake-native shortcuts are absent or explicitly exception-gated.
|
||||
- Shared-family core vs host-owned variation is explicit where relevant.
|
||||
- Navigation and mutation are separated intentionally.
|
||||
- Secondary actions are grouped correctly.
|
||||
- Destructive actions are placed correctly.
|
||||
@ -1649,9 +1229,6 @@ #### Appendix B - Feature Review Checklist
|
||||
- Canonical nouns stay consistent.
|
||||
- Critical truth is visible.
|
||||
- Scanability is preserved.
|
||||
- Shell, page, and detail state owners are explicit.
|
||||
- Requested, active, draft, inspect, and restorable roles are explicit
|
||||
when the surface uses them.
|
||||
- Exceptions are documented and tested.
|
||||
- Header passes the 5-second scan rule (HDR-001).
|
||||
- No pure navigation in the header.
|
||||
@ -1671,11 +1248,6 @@ #### Appendix C - Red Flags for Future PRs
|
||||
attention load.
|
||||
- The surface creates more noise than priority.
|
||||
- Row click and View open the same destination.
|
||||
- A Filament-looking surface keeps its real primary contract in raw HTML,
|
||||
GET, or Blade request state.
|
||||
- A simple overview is rebuilt as bespoke markup without a real product
|
||||
reason.
|
||||
- A special surface exists only by history and lacks a named exception.
|
||||
- A row becomes a control center.
|
||||
- Archive or Delete sits openly beside View or Inspect on a standard list.
|
||||
- More menus or bulk menus are empty.
|
||||
@ -1689,10 +1261,6 @@ #### Appendix C - Red Flags for Future PRs
|
||||
actions as one flat header rail.
|
||||
- Critical health or operability truth is hidden by default.
|
||||
- A contract claims conformance while the rendered UI behaves differently.
|
||||
- A repeated detail surface quietly changes family core structure from
|
||||
one host to another.
|
||||
- Shell, page, and detail layers each claim the same truth.
|
||||
- One concern has two competing inspect or view-state owners.
|
||||
- Header has multiple equally weighted buttons without clear prioritization.
|
||||
- "Open X" navigation links placed in the header instead of at the related field.
|
||||
- Governance-changing actions sit casually beside the primary action without friction.
|
||||
@ -1712,43 +1280,15 @@ ### Filament Native First / No Ad-hoc Styling (UI-FIL-001)
|
||||
- Admin and operator-facing surfaces MUST use native Filament components, existing shared UI primitives, and centralized design patterns first.
|
||||
- If Filament already provides the required semantic element, feature code MUST use the Filament-native component instead of a locally assembled replacement.
|
||||
- Preferred native elements include `x-filament::badge`, `x-filament::button`, `x-filament::icon`, and Filament Forms, Infolists, Tables, Sections, Tabs, Grids, and Actions.
|
||||
- Local Blade/Tailwind cards are allowed only when they preserve dark
|
||||
mode correctness, spacing consistency, badge semantics, action
|
||||
hierarchy, progressive disclosure, accessibility, and overall
|
||||
Filament visual language.
|
||||
|
||||
Native-by-default classification
|
||||
- `Native Surface` means the primary interaction contract is built from
|
||||
Filament-native components or approved shared primitives.
|
||||
- Standard forms, filters, tables, tabs, badges, links, and simple
|
||||
overviews default to `Native Surface` status.
|
||||
- `Custom Surface` is allowed only through UI-EX-001 when the operator
|
||||
need is richer than standard CRUD, overview, or report semantics.
|
||||
- `Fake-Native Surface` is forbidden: a surface that looks native but
|
||||
keeps a second HTML, GET, query, or Blade-request contract for the
|
||||
same primary interaction.
|
||||
|
||||
Forbidden local replacements
|
||||
- Feature code MUST NOT hand-build badges, pills, status chips, alert cards, or action buttons from raw `<span>`, `<div>`, or `<button>` markup plus Tailwind classes when Filament-native or shared project primitives can express the same meaning.
|
||||
- Feature code MUST NOT introduce page-local visual status languages for status, risk, outcome, drift, trust, importance, or severity.
|
||||
- Feature code MUST NOT make local color, border, rounding, or emphasis decisions for semantic UI states using ad-hoc classes such as `bg-danger-100`, `text-warning-900`, `border-dashed`, or `rounded-full` when the same state can be expressed through Filament props or shared primitives.
|
||||
- `Filament Costume` is forbidden: locally assembled markup that merely
|
||||
imitates native Filament controls, badges, or actions.
|
||||
- `Blade Request UI` is forbidden: request-driven body state or GET-form
|
||||
control rails as the primary interaction contract inside an active
|
||||
Filament surface.
|
||||
- `Hand-Rolled Simple Overview` is forbidden: bespoke overview/report
|
||||
shells where a native table/list surface fits the job.
|
||||
|
||||
Shared primitive before local override
|
||||
- If the same UI pattern can recur, it MUST use an existing shared primitive or introduce a new central primitive instead of reassembling the pattern inside a Blade view.
|
||||
- Central badge and status catalogs remain the canonical source for status semantics; local views MUST consume them rather than re-map them.
|
||||
- If the same custom detail, evidence, or review surface appears in
|
||||
more than one host, it becomes a `Shared Detail Micro-UI` and MUST
|
||||
define shared core vs host variation before another host reassembles
|
||||
it locally.
|
||||
- Local one-off markup MUST NOT recreate decision/diagnostics/raw
|
||||
layering when an existing shared detail family is sufficient.
|
||||
|
||||
Upgrade-safe preference
|
||||
- Update-safe, framework-native implementations take priority over page-local styling shortcuts.
|
||||
@ -1760,23 +1300,13 @@ ### Filament Native First / No Ad-hoc Styling (UI-FIL-001)
|
||||
- native Filament components cannot express the required semantics,
|
||||
- no suitable shared primitive exists,
|
||||
- and the deviation is justified briefly in code and in the governing spec or PR.
|
||||
- Approved exceptions MUST stay layout-neutral, use the minimum local
|
||||
classes necessary, MUST NOT invent a new page-local status language,
|
||||
MUST preserve dark mode correctness, spacing consistency,
|
||||
badge semantics, action hierarchy, progressive disclosure,
|
||||
accessibility, and MUST say what remains standardized.
|
||||
- `Hidden Exception` is forbidden. Historical accident or local
|
||||
implementation convenience is not a valid substitute for UI-EX-001.
|
||||
- Approved exceptions MUST stay layout-neutral, use the minimum local classes necessary, and MUST NOT invent a new page-local status language.
|
||||
|
||||
Review and enforcement
|
||||
- Every UI review MUST answer:
|
||||
- which native Filament element or shared primitive was used,
|
||||
- why an existing component was insufficient if an exception was taken,
|
||||
- whether the surface is native, custom, or a shared detail family,
|
||||
- whether any local Blade/Tailwind card still preserves Filament
|
||||
visual language and disclosure semantics,
|
||||
- and whether any ad-hoc status, emphasis styling, or fake-native
|
||||
contract was introduced.
|
||||
- and whether any ad-hoc status or emphasis styling was introduced.
|
||||
- UI work is not Done if it introduces ad-hoc status styling or framework-foreign replacement components where a native Filament or shared UI solution was viable.
|
||||
|
||||
### Incremental UI Standards Enforcement (UI-STD-001)
|
||||
@ -1796,7 +1326,6 @@ ### Spec-First Workflow
|
||||
|
||||
## Quality Gates
|
||||
- Changes MUST be programmatically tested (Pest) and run via targeted `php artisan test ...`.
|
||||
- Runtime changes MUST validate the narrowest relevant lane and document any material budget, baseline, or trend follow-up in the active spec or PR.
|
||||
- Run `./vendor/bin/sail bin pint --dirty` before finalizing.
|
||||
|
||||
## Governance
|
||||
@ -1805,23 +1334,9 @@ ### Scope, Compliance, and Review Expectations
|
||||
- This constitution applies across the repo. Feature specs may add stricter constraints but not weaker ones.
|
||||
- Restore semantics changes require: spec update, checklist update (if applicable), and tests proving safety.
|
||||
- Specs and PRs that introduce new persisted truth, abstractions, states, DTO/presenter layers, or taxonomies MUST include the proportionality review required by BLOAT-001.
|
||||
- Runtime-changing or test-affecting specs and PRs MUST include testing/lane/runtime impact covering actual test-purpose classification, affected lanes, fixture/helper/factory/seed/context cost changes, any heavy-family expansion, expected budget/baseline/trend effect, escalation decisions, and the minimal validation commands.
|
||||
- Specs, plans, task lists, and review checklists MUST surface the test-governance questions needed to catch lane drift, hidden defaults, and runtime-cost escalation before merge.
|
||||
- Specs and PRs that touch shared provider/platform seams MUST classify the touched boundary as provider-owned or platform-core, keep provider-specific semantics out of platform-core contracts and vocabulary unless explicitly justified, and record whether any remaining hotspot is resolved in-feature or escalated as a follow-up spec.
|
||||
- Specs and PRs that create, queue, deduplicate, resume, block, complete, or deep-link to an `OperationRun` MUST reuse the
|
||||
central OperationRun Start UX Contract, keep queued DB notifications explicit opt-in unless the active spec states a
|
||||
different policy, route terminal notifications through the lifecycle mechanism, include an `OperationRun UX Impact`
|
||||
section in the active spec or plan, and document any temporary exception with an architecture note, test rationale,
|
||||
and migration decision.
|
||||
- Specs and PRs that change detail or status surfaces MUST explicitly
|
||||
document how they satisfy customer-readable decision-first content,
|
||||
diagnostics-secondary disclosure, support/raw-evidence gating, one
|
||||
dominant next action, duplicate-truth prevention, and shared-pattern
|
||||
reuse.
|
||||
- Specs and PRs that change operator-facing surfaces MUST classify each
|
||||
affected surface under DECIDE-001 and justify any new Primary
|
||||
Decision Surface or workflow-first navigation change.
|
||||
- Reviews MUST reject runtime or test changes when lane classification is missing, fast-lane work quietly absorbs heavy cost, expensive defaults are introduced silently, or material CI/runtime drift is left undocumented.
|
||||
- Review and approval MUST favor simplification, replacement, and absorption over additive semantic layering.
|
||||
- Future-release preparation alone is not sufficient justification for new persistence or frameworkization unless security, tenant isolation, auditability, compliance evidence, or queue correctness already require it.
|
||||
|
||||
@ -1835,4 +1350,4 @@ ### Versioning Policy (SemVer)
|
||||
- **MINOR**: new principle/section or materially expanded guidance.
|
||||
- **MAJOR**: removing/redefining principles in a backward-incompatible way.
|
||||
|
||||
**Version**: 2.11.0 | **Ratified**: 2026-01-03 | **Last Amended**: 2026-04-27
|
||||
**Version**: 2.3.0 | **Ratified**: 2026-01-03 | **Last Amended**: 2026-04-12
|
||||
|
||||
@ -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,30 +41,22 @@ ## 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,
|
||||
@ -128,13 +66,6 @@ ## Constitution Check
|
||||
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
|
||||
@ -156,31 +87,6 @@ ## Constitution Check
|
||||
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]
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
@ -35,78 +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.
|
||||
Action Surface Class / Surface Type below.
|
||||
|
||||
| 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.
|
||||
with the Decision-First Surface Role section above.
|
||||
|
||||
| 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 |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
@ -144,32 +88,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 +175,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 +204,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,
|
||||
@ -386,7 +261,6 @@ ## Requirements *(mandatory)*
|
||||
**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,23 +38,6 @@ # 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
|
||||
@ -78,21 +48,9 @@ # Tasks: [FEATURE NAME]
|
||||
- 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,
|
||||
@ -140,12 +98,6 @@ # Tasks: [FEATURE NAME]
|
||||
- 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),
|
||||
@ -171,18 +123,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 +268,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)
|
||||
@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/gel-core/query-builders/raw.ts"],"sourcesContent":["import { entityKind } from '~/entity.ts';\nimport { QueryPromise } from '~/query-promise.ts';\nimport type { RunnableQuery } from '~/runnable-query.ts';\nimport type { PreparedQuery } from '~/session.ts';\nimport type { Query, SQL, SQLWrapper } from '~/sql/sql.ts';\n\nexport interface GelRaw<TResult> extends QueryPromise<TResult>, RunnableQuery<TResult, 'gel'>, SQLWrapper {}\n\nexport class GelRaw<TResult> extends QueryPromise<TResult>\n\timplements RunnableQuery<TResult, 'gel'>, SQLWrapper, PreparedQuery\n{\n\tstatic override readonly [entityKind]: string = 'GelRaw';\n\n\tdeclare readonly _: {\n\t\treadonly dialect: 'gel';\n\t\treadonly result: TResult;\n\t};\n\n\tconstructor(\n\t\tpublic execute: () => Promise<TResult>,\n\t\tprivate sql: SQL,\n\t\tprivate query: Query,\n\t\tprivate mapBatchResult: (result: unknown) => unknown,\n\t) {\n\t\tsuper();\n\t}\n\n\t/** @internal */\n\tgetSQL() {\n\t\treturn this.sql;\n\t}\n\n\tgetQuery() {\n\t\treturn this.query;\n\t}\n\n\tmapResult(result: unknown, isFromBatch?: boolean) {\n\t\treturn isFromBatch ? this.mapBatchResult(result) : result;\n\t}\n\n\t_prepare(): PreparedQuery {\n\t\treturn this;\n\t}\n\n\t/** @internal */\n\tisResponseInArrayMode() {\n\t\treturn false;\n\t}\n}\n"],"mappings":"AAAA,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAOtB,MAAM,eAAwB,aAErC;AAAA,EAQC,YACQ,SACC,KACA,OACA,gBACP;AACD,UAAM;AALC;AACC;AACA;AACA;AAAA,EAGT;AAAA,EAdA,QAA0B,UAAU,IAAY;AAAA;AAAA,EAiBhD,SAAS;AACR,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,WAAW;AACV,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,UAAU,QAAiB,aAAuB;AACjD,WAAO,cAAc,KAAK,eAAe,MAAM,IAAI;AAAA,EACpD;AAAA,EAEA,WAA0B;AACzB,WAAO;AAAA,EACR;AAAA;AAAA,EAGA,wBAAwB;AACvB,WAAO;AAAA,EACR;AACD;","names":[]}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user