Add enterprise appliance experience Phase 1 foundation
This commit is contained in:
parent
b3eec34158
commit
ebaa2e7219
259
.agents/skills/speckit-analyze/SKILL.md
Normal file
259
.agents/skills/speckit-analyze/SKILL.md
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
---
|
||||||
|
name: "speckit-analyze"
|
||||||
|
description: "Perform a non-destructive cross-artifact consistency and quality analysis across spec.md, plan.md, and tasks.md after task generation."
|
||||||
|
compatibility: "Requires spec-kit project structure with .specify/ directory"
|
||||||
|
metadata:
|
||||||
|
author: "github-spec-kit"
|
||||||
|
source: "templates/commands/analyze.md"
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## User Input
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ARGUMENTS
|
||||||
|
```
|
||||||
|
|
||||||
|
You **MUST** consider the user input before proceeding (if not empty).
|
||||||
|
|
||||||
|
## Pre-Execution Checks
|
||||||
|
|
||||||
|
**Check for extension hooks (before analysis)**:
|
||||||
|
- Check if `.specify/extensions.yml` exists in the project root.
|
||||||
|
- If it exists, read it and look for entries under the `hooks.before_analyze` key
|
||||||
|
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||||
|
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||||
|
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||||
|
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||||
|
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||||
|
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||||
|
- For each executable hook, output the following based on its `optional` flag:
|
||||||
|
- **Optional hook** (`optional: true`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Pre-Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
- **Mandatory hook** (`optional: false`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Automatic Pre-Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
|
||||||
|
Wait for the result of the hook command before proceeding to the Goal.
|
||||||
|
```
|
||||||
|
After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook.
|
||||||
|
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Identify inconsistencies, duplications, ambiguities, and underspecified items across the three core artifacts (`spec.md`, `plan.md`, `tasks.md`) before implementation. This command MUST run only after `/speckit-tasks` has successfully produced a complete `tasks.md`.
|
||||||
|
|
||||||
|
## Operating Constraints
|
||||||
|
|
||||||
|
**STRICTLY READ-ONLY**: Do **not** modify any files. Output a structured analysis report. Offer an optional remediation plan (user must explicitly approve before any follow-up editing commands would be invoked manually).
|
||||||
|
|
||||||
|
**Constitution Authority**: The project constitution (`.specify/memory/constitution.md`) is **non-negotiable** within this analysis scope. Constitution conflicts are automatically CRITICAL and require adjustment of the spec, plan, or tasks—not dilution, reinterpretation, or silent ignoring of the principle. If a principle itself needs to change, that must occur in a separate, explicit constitution update outside `/speckit-analyze`.
|
||||||
|
|
||||||
|
## Execution Steps
|
||||||
|
|
||||||
|
### 1. Initialize Analysis Context
|
||||||
|
|
||||||
|
Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` once from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS. Derive absolute paths:
|
||||||
|
|
||||||
|
- SPEC = FEATURE_DIR/spec.md
|
||||||
|
- PLAN = FEATURE_DIR/plan.md
|
||||||
|
- TASKS = FEATURE_DIR/tasks.md
|
||||||
|
|
||||||
|
Abort with an error message if any required file is missing (instruct the user to run missing prerequisite command).
|
||||||
|
For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||||
|
|
||||||
|
### 2. Load Artifacts (Progressive Disclosure)
|
||||||
|
|
||||||
|
Load only the minimal necessary context from each artifact:
|
||||||
|
|
||||||
|
**From spec.md:**
|
||||||
|
|
||||||
|
- Overview/Context
|
||||||
|
- Functional Requirements
|
||||||
|
- Success Criteria (measurable outcomes — e.g., performance, security, availability, user success, business impact)
|
||||||
|
- User Stories
|
||||||
|
- Edge Cases (if present)
|
||||||
|
|
||||||
|
**From plan.md:**
|
||||||
|
|
||||||
|
- Architecture/stack choices
|
||||||
|
- Data Model references
|
||||||
|
- Phases
|
||||||
|
- Technical constraints
|
||||||
|
|
||||||
|
**From tasks.md:**
|
||||||
|
|
||||||
|
- Task IDs
|
||||||
|
- Descriptions
|
||||||
|
- Phase grouping
|
||||||
|
- Parallel markers [P]
|
||||||
|
- Referenced file paths
|
||||||
|
|
||||||
|
**From constitution:**
|
||||||
|
|
||||||
|
- Load `.specify/memory/constitution.md` for principle validation
|
||||||
|
|
||||||
|
### 3. Build Semantic Models
|
||||||
|
|
||||||
|
Create internal representations (do not include raw artifacts in output):
|
||||||
|
|
||||||
|
- **Requirements inventory**: For each Functional Requirement (FR-###) and Success Criterion (SC-###), record a stable key. Use the explicit FR-/SC- identifier as the primary key when present, and optionally also derive an imperative-phrase slug for readability (e.g., "User can upload file" → `user-can-upload-file`). Include only Success Criteria items that require buildable work (e.g., load-testing infrastructure, security audit tooling), and exclude post-launch outcome metrics and business KPIs (e.g., "Reduce support tickets by 50%").
|
||||||
|
- **User story/action inventory**: Discrete user actions with acceptance criteria
|
||||||
|
- **Task coverage mapping**: Map each task to one or more requirements or stories (inference by keyword / explicit reference patterns like IDs or key phrases)
|
||||||
|
- **Constitution rule set**: Extract principle names and MUST/SHOULD normative statements
|
||||||
|
|
||||||
|
### 4. Detection Passes (Token-Efficient Analysis)
|
||||||
|
|
||||||
|
Focus on high-signal findings. Limit to 50 findings total; aggregate remainder in overflow summary.
|
||||||
|
|
||||||
|
#### A. Duplication Detection
|
||||||
|
|
||||||
|
- Identify near-duplicate requirements
|
||||||
|
- Mark lower-quality phrasing for consolidation
|
||||||
|
|
||||||
|
#### B. Ambiguity Detection
|
||||||
|
|
||||||
|
- Flag vague adjectives (fast, scalable, secure, intuitive, robust) lacking measurable criteria
|
||||||
|
- Flag unresolved placeholders (TODO, TKTK, ???, `<placeholder>`, etc.)
|
||||||
|
|
||||||
|
#### C. Underspecification
|
||||||
|
|
||||||
|
- Requirements with verbs but missing object or measurable outcome
|
||||||
|
- User stories missing acceptance criteria alignment
|
||||||
|
- Tasks referencing files or components not defined in spec/plan
|
||||||
|
|
||||||
|
#### D. Constitution Alignment
|
||||||
|
|
||||||
|
- Any requirement or plan element conflicting with a MUST principle
|
||||||
|
- Missing mandated sections or quality gates from constitution
|
||||||
|
|
||||||
|
#### E. Coverage Gaps
|
||||||
|
|
||||||
|
- Requirements with zero associated tasks
|
||||||
|
- Tasks with no mapped requirement/story
|
||||||
|
- Success Criteria requiring buildable work (performance, security, availability) not reflected in tasks
|
||||||
|
|
||||||
|
#### F. Inconsistency
|
||||||
|
|
||||||
|
- Terminology drift (same concept named differently across files)
|
||||||
|
- Data entities referenced in plan but absent in spec (or vice versa)
|
||||||
|
- Task ordering contradictions (e.g., integration tasks before foundational setup tasks without dependency note)
|
||||||
|
- Conflicting requirements (e.g., one requires Next.js while other specifies Vue)
|
||||||
|
|
||||||
|
### 5. Severity Assignment
|
||||||
|
|
||||||
|
Use this heuristic to prioritize findings:
|
||||||
|
|
||||||
|
- **CRITICAL**: Violates constitution MUST, missing core spec artifact, or requirement with zero coverage that blocks baseline functionality
|
||||||
|
- **HIGH**: Duplicate or conflicting requirement, ambiguous security/performance attribute, untestable acceptance criterion
|
||||||
|
- **MEDIUM**: Terminology drift, missing non-functional task coverage, underspecified edge case
|
||||||
|
- **LOW**: Style/wording improvements, minor redundancy not affecting execution order
|
||||||
|
|
||||||
|
### 6. Produce Compact Analysis Report
|
||||||
|
|
||||||
|
Output a Markdown report (no file writes) with the following structure:
|
||||||
|
|
||||||
|
## Specification Analysis Report
|
||||||
|
|
||||||
|
| ID | Category | Severity | Location(s) | Summary | Recommendation |
|
||||||
|
|----|----------|----------|-------------|---------|----------------|
|
||||||
|
| A1 | Duplication | HIGH | spec.md:L120-134 | Two similar requirements ... | Merge phrasing; keep clearer version |
|
||||||
|
|
||||||
|
(Add one row per finding; generate stable IDs prefixed by category initial.)
|
||||||
|
|
||||||
|
**Coverage Summary Table:**
|
||||||
|
|
||||||
|
| Requirement Key | Has Task? | Task IDs | Notes |
|
||||||
|
|-----------------|-----------|----------|-------|
|
||||||
|
|
||||||
|
**Constitution Alignment Issues:** (if any)
|
||||||
|
|
||||||
|
**Unmapped Tasks:** (if any)
|
||||||
|
|
||||||
|
**Metrics:**
|
||||||
|
|
||||||
|
- Total Requirements
|
||||||
|
- Total Tasks
|
||||||
|
- Coverage % (requirements with >=1 task)
|
||||||
|
- Ambiguity Count
|
||||||
|
- Duplication Count
|
||||||
|
- Critical Issues Count
|
||||||
|
|
||||||
|
### 7. Provide Next Actions
|
||||||
|
|
||||||
|
At end of report, output a concise Next Actions block:
|
||||||
|
|
||||||
|
- If CRITICAL issues exist: Recommend resolving before `/speckit-implement`
|
||||||
|
- If only LOW/MEDIUM: User may proceed, but provide improvement suggestions
|
||||||
|
- Provide explicit command suggestions: e.g., "Run /speckit-specify with refinement", "Run /speckit-plan to adjust architecture", "Manually edit tasks.md to add coverage for 'performance-metrics'"
|
||||||
|
|
||||||
|
### 8. Offer Remediation
|
||||||
|
|
||||||
|
Ask the user: "Would you like me to suggest concrete remediation edits for the top N issues?" (Do NOT apply them automatically.)
|
||||||
|
|
||||||
|
### 9. Check for extension hooks
|
||||||
|
|
||||||
|
After reporting, check if `.specify/extensions.yml` exists in the project root.
|
||||||
|
- If it exists, read it and look for entries under the `hooks.after_analyze` key
|
||||||
|
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||||
|
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||||
|
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||||
|
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||||
|
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||||
|
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||||
|
- For each executable hook, output the following based on its `optional` flag:
|
||||||
|
- **Optional hook** (`optional: true`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
- **Mandatory hook** (`optional: false`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Automatic Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
```
|
||||||
|
After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook.
|
||||||
|
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||||
|
|
||||||
|
## Operating Principles
|
||||||
|
|
||||||
|
### Context Efficiency
|
||||||
|
|
||||||
|
- **Minimal high-signal tokens**: Focus on actionable findings, not exhaustive documentation
|
||||||
|
- **Progressive disclosure**: Load artifacts incrementally; don't dump all content into analysis
|
||||||
|
- **Token-efficient output**: Limit findings table to 50 rows; summarize overflow
|
||||||
|
- **Deterministic results**: Rerunning without changes should produce consistent IDs and counts
|
||||||
|
|
||||||
|
### Analysis Guidelines
|
||||||
|
|
||||||
|
- **NEVER modify files** (this is read-only analysis)
|
||||||
|
- **NEVER hallucinate missing sections** (if absent, report them accurately)
|
||||||
|
- **Prioritize constitution violations** (these are always CRITICAL)
|
||||||
|
- **Use examples over exhaustive rules** (cite specific instances, not generic patterns)
|
||||||
|
- **Report zero issues gracefully** (emit success report with coverage statistics)
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
$ARGUMENTS
|
||||||
373
.agents/skills/speckit-checklist/SKILL.md
Normal file
373
.agents/skills/speckit-checklist/SKILL.md
Normal file
@ -0,0 +1,373 @@
|
|||||||
|
---
|
||||||
|
name: "speckit-checklist"
|
||||||
|
description: "Generate a custom checklist for the current feature based on user requirements."
|
||||||
|
compatibility: "Requires spec-kit project structure with .specify/ directory"
|
||||||
|
metadata:
|
||||||
|
author: "github-spec-kit"
|
||||||
|
source: "templates/commands/checklist.md"
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## Checklist Purpose: "Unit Tests for English"
|
||||||
|
|
||||||
|
**CRITICAL CONCEPT**: Checklists are **UNIT TESTS FOR REQUIREMENTS WRITING** - they validate the quality, clarity, and completeness of requirements in a given domain.
|
||||||
|
|
||||||
|
**NOT for verification/testing**:
|
||||||
|
|
||||||
|
- ❌ NOT "Verify the button clicks correctly"
|
||||||
|
- ❌ NOT "Test error handling works"
|
||||||
|
- ❌ NOT "Confirm the API returns 200"
|
||||||
|
- ❌ NOT checking if code/implementation matches the spec
|
||||||
|
|
||||||
|
**FOR requirements quality validation**:
|
||||||
|
|
||||||
|
- ✅ "Are visual hierarchy requirements defined for all card types?" (completeness)
|
||||||
|
- ✅ "Is 'prominent display' quantified with specific sizing/positioning?" (clarity)
|
||||||
|
- ✅ "Are hover state requirements consistent across all interactive elements?" (consistency)
|
||||||
|
- ✅ "Are accessibility requirements defined for keyboard navigation?" (coverage)
|
||||||
|
- ✅ "Does the spec define what happens when logo image fails to load?" (edge cases)
|
||||||
|
|
||||||
|
**Metaphor**: If your spec is code written in English, the checklist is its unit test suite. You're testing whether the requirements are well-written, complete, unambiguous, and ready for implementation - NOT whether the implementation works.
|
||||||
|
|
||||||
|
## User Input
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ARGUMENTS
|
||||||
|
```
|
||||||
|
|
||||||
|
You **MUST** consider the user input before proceeding (if not empty).
|
||||||
|
|
||||||
|
## Pre-Execution Checks
|
||||||
|
|
||||||
|
**Check for extension hooks (before checklist generation)**:
|
||||||
|
- Check if `.specify/extensions.yml` exists in the project root.
|
||||||
|
- If it exists, read it and look for entries under the `hooks.before_checklist` key
|
||||||
|
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||||
|
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||||
|
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||||
|
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||||
|
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||||
|
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||||
|
- For each executable hook, output the following based on its `optional` flag:
|
||||||
|
- **Optional hook** (`optional: true`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Pre-Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
- **Mandatory hook** (`optional: false`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Automatic Pre-Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
|
||||||
|
Wait for the result of the hook command before proceeding to the Execution Steps.
|
||||||
|
```
|
||||||
|
After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook.
|
||||||
|
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||||
|
|
||||||
|
## Execution Steps
|
||||||
|
|
||||||
|
1. **Setup**: Run `.specify/scripts/bash/check-prerequisites.sh --json` from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS list.
|
||||||
|
- All file paths must be absolute.
|
||||||
|
- For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||||
|
|
||||||
|
2. **IF EXISTS**: Load `.specify/memory/constitution.md` for project principles and governance constraints.
|
||||||
|
|
||||||
|
3. **Clarify intent (dynamic)**: Derive up to THREE initial contextual clarifying questions (no pre-baked catalog). They MUST:
|
||||||
|
- Be generated from the user's phrasing + extracted signals from spec/plan/tasks
|
||||||
|
- Only ask about information that materially changes checklist content
|
||||||
|
- Be skipped individually if already unambiguous in `$ARGUMENTS`
|
||||||
|
- Prefer precision over breadth
|
||||||
|
|
||||||
|
Generation algorithm:
|
||||||
|
1. Extract signals: feature domain keywords (e.g., auth, latency, UX, API), risk indicators ("critical", "must", "compliance"), stakeholder hints ("QA", "review", "security team"), and explicit deliverables ("a11y", "rollback", "contracts").
|
||||||
|
2. Cluster signals into candidate focus areas (max 4) ranked by relevance.
|
||||||
|
3. Identify probable audience & timing (author, reviewer, QA, release) if not explicit.
|
||||||
|
4. Detect missing dimensions: scope breadth, depth/rigor, risk emphasis, exclusion boundaries, measurable acceptance criteria.
|
||||||
|
5. Formulate questions chosen from these archetypes:
|
||||||
|
- Scope refinement (e.g., "Should this include integration touchpoints with X and Y or stay limited to local module correctness?")
|
||||||
|
- Risk prioritization (e.g., "Which of these potential risk areas should receive mandatory gating checks?")
|
||||||
|
- Depth calibration (e.g., "Is this a lightweight pre-commit sanity list or a formal release gate?")
|
||||||
|
- Audience framing (e.g., "Will this be used by the author only or peers during PR review?")
|
||||||
|
- Boundary exclusion (e.g., "Should we explicitly exclude performance tuning items this round?")
|
||||||
|
- Scenario class gap (e.g., "No recovery flows detected—are rollback / partial failure paths in scope?")
|
||||||
|
|
||||||
|
Question formatting rules:
|
||||||
|
- If presenting options, generate a compact table with columns: Option | Candidate | Why It Matters
|
||||||
|
- Limit to A–E options maximum; omit table if a free-form answer is clearer
|
||||||
|
- Never ask the user to restate what they already said
|
||||||
|
- Avoid speculative categories (no hallucination). If uncertain, ask explicitly: "Confirm whether X belongs in scope."
|
||||||
|
|
||||||
|
Defaults when interaction impossible:
|
||||||
|
- Depth: Standard
|
||||||
|
- Audience: Reviewer (PR) if code-related; Author otherwise
|
||||||
|
- Focus: Top 2 relevance clusters
|
||||||
|
|
||||||
|
Output the questions (label Q1/Q2/Q3). After answers: if ≥2 scenario classes (Alternate / Exception / Recovery / Non-Functional domain) remain unclear, you MAY ask up to TWO more targeted follow‑ups (Q4/Q5) with a one-line justification each (e.g., "Unresolved recovery path risk"). Do not exceed five total questions. Skip escalation if user explicitly declines more.
|
||||||
|
|
||||||
|
4. **Understand user request**: Combine `$ARGUMENTS` + clarifying answers:
|
||||||
|
- Derive checklist theme (e.g., security, review, deploy, ux)
|
||||||
|
- Consolidate explicit must-have items mentioned by user
|
||||||
|
- Map focus selections to category scaffolding
|
||||||
|
- Infer any missing context from spec/plan/tasks (do NOT hallucinate)
|
||||||
|
|
||||||
|
5. **Load feature context**: Read from FEATURE_DIR:
|
||||||
|
- spec.md: Feature requirements and scope
|
||||||
|
- plan.md (if exists): Technical details, dependencies
|
||||||
|
- tasks.md (if exists): Implementation tasks
|
||||||
|
|
||||||
|
**Context Loading Strategy**:
|
||||||
|
- Load only necessary portions relevant to active focus areas (avoid full-file dumping)
|
||||||
|
- Prefer summarizing long sections into concise scenario/requirement bullets
|
||||||
|
- Use progressive disclosure: add follow-on retrieval only if gaps detected
|
||||||
|
- If source docs are large, generate interim summary items instead of embedding raw text
|
||||||
|
|
||||||
|
6. **Generate checklist** - Create "Unit Tests for Requirements":
|
||||||
|
- Create `FEATURE_DIR/checklists/` directory if it doesn't exist
|
||||||
|
- Generate unique checklist filename:
|
||||||
|
- Use short, descriptive name based on domain (e.g., `ux.md`, `api.md`, `security.md`)
|
||||||
|
- Format: `[domain].md`
|
||||||
|
- File handling behavior:
|
||||||
|
- If file does NOT exist: Create new file and number items starting from CHK001
|
||||||
|
- If file exists: Append new items to existing file, continuing from the last CHK ID (e.g., if last item is CHK015, start new items at CHK016)
|
||||||
|
- Never delete or replace existing checklist content - always preserve and append
|
||||||
|
|
||||||
|
**CORE PRINCIPLE - Test the Requirements, Not the Implementation**:
|
||||||
|
Every checklist item MUST evaluate the REQUIREMENTS THEMSELVES for:
|
||||||
|
- **Completeness**: Are all necessary requirements present?
|
||||||
|
- **Clarity**: Are requirements unambiguous and specific?
|
||||||
|
- **Consistency**: Do requirements align with each other?
|
||||||
|
- **Measurability**: Can requirements be objectively verified?
|
||||||
|
- **Coverage**: Are all scenarios/edge cases addressed?
|
||||||
|
|
||||||
|
**Category Structure** - Group items by requirement quality dimensions:
|
||||||
|
- **Requirement Completeness** (Are all necessary requirements documented?)
|
||||||
|
- **Requirement Clarity** (Are requirements specific and unambiguous?)
|
||||||
|
- **Requirement Consistency** (Do requirements align without conflicts?)
|
||||||
|
- **Acceptance Criteria Quality** (Are success criteria measurable?)
|
||||||
|
- **Scenario Coverage** (Are all flows/cases addressed?)
|
||||||
|
- **Edge Case Coverage** (Are boundary conditions defined?)
|
||||||
|
- **Non-Functional Requirements** (Performance, Security, Accessibility, etc. - are they specified?)
|
||||||
|
- **Dependencies & Assumptions** (Are they documented and validated?)
|
||||||
|
- **Ambiguities & Conflicts** (What needs clarification?)
|
||||||
|
|
||||||
|
**HOW TO WRITE CHECKLIST ITEMS - "Unit Tests for English"**:
|
||||||
|
|
||||||
|
❌ **WRONG** (Testing implementation):
|
||||||
|
- "Verify landing page displays 3 episode cards"
|
||||||
|
- "Test hover states work on desktop"
|
||||||
|
- "Confirm logo click navigates home"
|
||||||
|
|
||||||
|
✅ **CORRECT** (Testing requirements quality):
|
||||||
|
- "Are the exact number and layout of featured episodes specified?" [Completeness]
|
||||||
|
- "Is 'prominent display' quantified with specific sizing/positioning?" [Clarity]
|
||||||
|
- "Are hover state requirements consistent across all interactive elements?" [Consistency]
|
||||||
|
- "Are keyboard navigation requirements defined for all interactive UI?" [Coverage]
|
||||||
|
- "Is the fallback behavior specified when logo image fails to load?" [Edge Cases]
|
||||||
|
- "Are loading states defined for asynchronous episode data?" [Completeness]
|
||||||
|
- "Does the spec define visual hierarchy for competing UI elements?" [Clarity]
|
||||||
|
|
||||||
|
**ITEM STRUCTURE**:
|
||||||
|
Each item should follow this pattern:
|
||||||
|
- Question format asking about requirement quality
|
||||||
|
- Focus on what's WRITTEN (or not written) in the spec/plan
|
||||||
|
- Include quality dimension in brackets [Completeness/Clarity/Consistency/etc.]
|
||||||
|
- Reference spec section `[Spec §X.Y]` when checking existing requirements
|
||||||
|
- Use `[Gap]` marker when checking for missing requirements
|
||||||
|
|
||||||
|
**EXAMPLES BY QUALITY DIMENSION**:
|
||||||
|
|
||||||
|
Completeness:
|
||||||
|
- "Are error handling requirements defined for all API failure modes? [Gap]"
|
||||||
|
- "Are accessibility requirements specified for all interactive elements? [Completeness]"
|
||||||
|
- "Are mobile breakpoint requirements defined for responsive layouts? [Gap]"
|
||||||
|
|
||||||
|
Clarity:
|
||||||
|
- "Is 'fast loading' quantified with specific timing thresholds? [Clarity, Spec §NFR-2]"
|
||||||
|
- "Are 'related episodes' selection criteria explicitly defined? [Clarity, Spec §FR-5]"
|
||||||
|
- "Is 'prominent' defined with measurable visual properties? [Ambiguity, Spec §FR-4]"
|
||||||
|
|
||||||
|
Consistency:
|
||||||
|
- "Do navigation requirements align across all pages? [Consistency, Spec §FR-10]"
|
||||||
|
- "Are card component requirements consistent between landing and detail pages? [Consistency]"
|
||||||
|
|
||||||
|
Coverage:
|
||||||
|
- "Are requirements defined for zero-state scenarios (no episodes)? [Coverage, Edge Case]"
|
||||||
|
- "Are concurrent user interaction scenarios addressed? [Coverage, Gap]"
|
||||||
|
- "Are requirements specified for partial data loading failures? [Coverage, Exception Flow]"
|
||||||
|
|
||||||
|
Measurability:
|
||||||
|
- "Are visual hierarchy requirements measurable/testable? [Acceptance Criteria, Spec §FR-1]"
|
||||||
|
- "Can 'balanced visual weight' be objectively verified? [Measurability, Spec §FR-2]"
|
||||||
|
|
||||||
|
**Scenario Classification & Coverage** (Requirements Quality Focus):
|
||||||
|
- Check if requirements exist for: Primary, Alternate, Exception/Error, Recovery, Non-Functional scenarios
|
||||||
|
- For each scenario class, ask: "Are [scenario type] requirements complete, clear, and consistent?"
|
||||||
|
- If scenario class missing: "Are [scenario type] requirements intentionally excluded or missing? [Gap]"
|
||||||
|
- Include resilience/rollback when state mutation occurs: "Are rollback requirements defined for migration failures? [Gap]"
|
||||||
|
|
||||||
|
**Traceability Requirements**:
|
||||||
|
- MINIMUM: ≥80% of items MUST include at least one traceability reference
|
||||||
|
- Each item should reference: spec section `[Spec §X.Y]`, or use markers: `[Gap]`, `[Ambiguity]`, `[Conflict]`, `[Assumption]`
|
||||||
|
- If no ID system exists: "Is a requirement & acceptance criteria ID scheme established? [Traceability]"
|
||||||
|
|
||||||
|
**Surface & Resolve Issues** (Requirements Quality Problems):
|
||||||
|
Ask questions about the requirements themselves:
|
||||||
|
- Ambiguities: "Is the term 'fast' quantified with specific metrics? [Ambiguity, Spec §NFR-1]"
|
||||||
|
- Conflicts: "Do navigation requirements conflict between §FR-10 and §FR-10a? [Conflict]"
|
||||||
|
- Assumptions: "Is the assumption of 'always available podcast API' validated? [Assumption]"
|
||||||
|
- Dependencies: "Are external podcast API requirements documented? [Dependency, Gap]"
|
||||||
|
- Missing definitions: "Is 'visual hierarchy' defined with measurable criteria? [Gap]"
|
||||||
|
|
||||||
|
**Content Consolidation**:
|
||||||
|
- Soft cap: If raw candidate items > 40, prioritize by risk/impact
|
||||||
|
- Merge near-duplicates checking the same requirement aspect
|
||||||
|
- If >5 low-impact edge cases, create one item: "Are edge cases X, Y, Z addressed in requirements? [Coverage]"
|
||||||
|
|
||||||
|
**🚫 ABSOLUTELY PROHIBITED** - These make it an implementation test, not a requirements test:
|
||||||
|
- ❌ Any item starting with "Verify", "Test", "Confirm", "Check" + implementation behavior
|
||||||
|
- ❌ References to code execution, user actions, system behavior
|
||||||
|
- ❌ "Displays correctly", "works properly", "functions as expected"
|
||||||
|
- ❌ "Click", "navigate", "render", "load", "execute"
|
||||||
|
- ❌ Test cases, test plans, QA procedures
|
||||||
|
- ❌ Implementation details (frameworks, APIs, algorithms)
|
||||||
|
|
||||||
|
**✅ REQUIRED PATTERNS** - These test requirements quality:
|
||||||
|
- ✅ "Are [requirement type] defined/specified/documented for [scenario]?"
|
||||||
|
- ✅ "Is [vague term] quantified/clarified with specific criteria?"
|
||||||
|
- ✅ "Are requirements consistent between [section A] and [section B]?"
|
||||||
|
- ✅ "Can [requirement] be objectively measured/verified?"
|
||||||
|
- ✅ "Are [edge cases/scenarios] addressed in requirements?"
|
||||||
|
- ✅ "Does the spec define [missing aspect]?"
|
||||||
|
|
||||||
|
7. **Structure Reference**: Generate the checklist following the canonical template in `.specify/templates/checklist-template.md` for title, meta section, category headings, and ID formatting. If template is unavailable, use: H1 title, purpose/created meta lines, `##` category sections containing `- [ ] CHK### <requirement item>` lines with globally incrementing IDs starting at CHK001.
|
||||||
|
|
||||||
|
8. **Report**: Output full path to checklist file, item count, and summarize whether the run created a new file or appended to an existing one. Summarize:
|
||||||
|
- Focus areas selected
|
||||||
|
- Depth level
|
||||||
|
- Actor/timing
|
||||||
|
- Any explicit user-specified must-have items incorporated
|
||||||
|
|
||||||
|
**Important**: Each `/speckit-checklist` command invocation uses a short, descriptive checklist filename and either creates a new file or appends to an existing one. This allows:
|
||||||
|
|
||||||
|
- Multiple checklists of different types (e.g., `ux.md`, `test.md`, `security.md`)
|
||||||
|
- Simple, memorable filenames that indicate checklist purpose
|
||||||
|
- Easy identification and navigation in the `checklists/` folder
|
||||||
|
|
||||||
|
To avoid clutter, use descriptive types and clean up obsolete checklists when done.
|
||||||
|
|
||||||
|
## Example Checklist Types & Sample Items
|
||||||
|
|
||||||
|
**UX Requirements Quality:** `ux.md`
|
||||||
|
|
||||||
|
Sample items (testing the requirements, NOT the implementation):
|
||||||
|
|
||||||
|
- "Are visual hierarchy requirements defined with measurable criteria? [Clarity, Spec §FR-1]"
|
||||||
|
- "Is the number and positioning of UI elements explicitly specified? [Completeness, Spec §FR-1]"
|
||||||
|
- "Are interaction state requirements (hover, focus, active) consistently defined? [Consistency]"
|
||||||
|
- "Are accessibility requirements specified for all interactive elements? [Coverage, Gap]"
|
||||||
|
- "Is fallback behavior defined when images fail to load? [Edge Case, Gap]"
|
||||||
|
- "Can 'prominent display' be objectively measured? [Measurability, Spec §FR-4]"
|
||||||
|
|
||||||
|
**API Requirements Quality:** `api.md`
|
||||||
|
|
||||||
|
Sample items:
|
||||||
|
|
||||||
|
- "Are error response formats specified for all failure scenarios? [Completeness]"
|
||||||
|
- "Are rate limiting requirements quantified with specific thresholds? [Clarity]"
|
||||||
|
- "Are authentication requirements consistent across all endpoints? [Consistency]"
|
||||||
|
- "Are retry/timeout requirements defined for external dependencies? [Coverage, Gap]"
|
||||||
|
- "Is versioning strategy documented in requirements? [Gap]"
|
||||||
|
|
||||||
|
**Performance Requirements Quality:** `performance.md`
|
||||||
|
|
||||||
|
Sample items:
|
||||||
|
|
||||||
|
- "Are performance requirements quantified with specific metrics? [Clarity]"
|
||||||
|
- "Are performance targets defined for all critical user journeys? [Coverage]"
|
||||||
|
- "Are performance requirements under different load conditions specified? [Completeness]"
|
||||||
|
- "Can performance requirements be objectively measured? [Measurability]"
|
||||||
|
- "Are degradation requirements defined for high-load scenarios? [Edge Case, Gap]"
|
||||||
|
|
||||||
|
**Security Requirements Quality:** `security.md`
|
||||||
|
|
||||||
|
Sample items:
|
||||||
|
|
||||||
|
- "Are authentication requirements specified for all protected resources? [Coverage]"
|
||||||
|
- "Are data protection requirements defined for sensitive information? [Completeness]"
|
||||||
|
- "Is the threat model documented and requirements aligned to it? [Traceability]"
|
||||||
|
- "Are security requirements consistent with compliance obligations? [Consistency]"
|
||||||
|
- "Are security failure/breach response requirements defined? [Gap, Exception Flow]"
|
||||||
|
|
||||||
|
## Anti-Examples: What NOT To Do
|
||||||
|
|
||||||
|
**❌ WRONG - These test implementation, not requirements:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
- [ ] CHK001 - Verify landing page displays 3 episode cards [Spec §FR-001]
|
||||||
|
- [ ] CHK002 - Test hover states work correctly on desktop [Spec §FR-003]
|
||||||
|
- [ ] CHK003 - Confirm logo click navigates to home page [Spec §FR-010]
|
||||||
|
- [ ] CHK004 - Check that related episodes section shows 3-5 items [Spec §FR-005]
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ CORRECT - These test requirements quality:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
- [ ] CHK001 - Are the number and layout of featured episodes explicitly specified? [Completeness, Spec §FR-001]
|
||||||
|
- [ ] CHK002 - Are hover state requirements consistently defined for all interactive elements? [Consistency, Spec §FR-003]
|
||||||
|
- [ ] CHK003 - Are navigation requirements clear for all clickable brand elements? [Clarity, Spec §FR-010]
|
||||||
|
- [ ] CHK004 - Is the selection criteria for related episodes documented? [Gap, Spec §FR-005]
|
||||||
|
- [ ] CHK005 - Are loading state requirements defined for asynchronous episode data? [Gap]
|
||||||
|
- [ ] CHK006 - Can "visual hierarchy" requirements be objectively measured? [Measurability, Spec §FR-001]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Differences:**
|
||||||
|
|
||||||
|
- Wrong: Tests if the system works correctly
|
||||||
|
- Correct: Tests if the requirements are written correctly
|
||||||
|
- Wrong: Verification of behavior
|
||||||
|
- Correct: Validation of requirement quality
|
||||||
|
- Wrong: "Does it do X?"
|
||||||
|
- Correct: "Is X clearly specified?"
|
||||||
|
|
||||||
|
## Post-Execution Checks
|
||||||
|
|
||||||
|
**Check for extension hooks (after checklist generation)**:
|
||||||
|
Check if `.specify/extensions.yml` exists in the project root.
|
||||||
|
- If it exists, read it and look for entries under the `hooks.after_checklist` key
|
||||||
|
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||||
|
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||||
|
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||||
|
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||||
|
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||||
|
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||||
|
- For each executable hook, output the following based on its `optional` flag:
|
||||||
|
- **Optional hook** (`optional: true`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
- **Mandatory hook** (`optional: false`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Automatic Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
```
|
||||||
|
After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook.
|
||||||
|
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||||
285
.agents/skills/speckit-clarify/SKILL.md
Normal file
285
.agents/skills/speckit-clarify/SKILL.md
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
---
|
||||||
|
name: "speckit-clarify"
|
||||||
|
description: "Identify underspecified areas in the current feature spec by asking up to 5 highly targeted clarification questions and encoding answers back into the spec."
|
||||||
|
compatibility: "Requires spec-kit project structure with .specify/ directory"
|
||||||
|
metadata:
|
||||||
|
author: "github-spec-kit"
|
||||||
|
source: "templates/commands/clarify.md"
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## User Input
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ARGUMENTS
|
||||||
|
```
|
||||||
|
|
||||||
|
You **MUST** consider the user input before proceeding (if not empty).
|
||||||
|
|
||||||
|
## Pre-Execution Checks
|
||||||
|
|
||||||
|
**Check for extension hooks (before clarification)**:
|
||||||
|
- Check if `.specify/extensions.yml` exists in the project root.
|
||||||
|
- If it exists, read it and look for entries under the `hooks.before_clarify` key
|
||||||
|
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||||
|
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||||
|
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||||
|
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||||
|
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||||
|
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||||
|
- For each executable hook, output the following based on its `optional` flag:
|
||||||
|
- **Optional hook** (`optional: true`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Pre-Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
- **Mandatory hook** (`optional: false`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Automatic Pre-Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
|
||||||
|
Wait for the result of the hook command before proceeding to the Outline.
|
||||||
|
```
|
||||||
|
After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook.
|
||||||
|
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||||
|
|
||||||
|
## Outline
|
||||||
|
|
||||||
|
Goal: Detect and reduce ambiguity or missing decision points in the active feature specification and record the clarifications directly in the spec file.
|
||||||
|
|
||||||
|
Note: This clarification workflow is expected to run (and be completed) BEFORE invoking `/speckit-plan`. If the user explicitly states they are skipping clarification (e.g., exploratory spike), you may proceed, but must warn that downstream rework risk increases.
|
||||||
|
|
||||||
|
Execution steps:
|
||||||
|
|
||||||
|
1. Run `.specify/scripts/bash/check-prerequisites.sh --json --paths-only` from repo root **once** (combined `--json --paths-only` mode / `-Json -PathsOnly`). Parse minimal JSON payload fields:
|
||||||
|
- `FEATURE_DIR`
|
||||||
|
- `FEATURE_SPEC`
|
||||||
|
- (Optionally capture `IMPL_PLAN`, `TASKS` for future chained flows.)
|
||||||
|
- If JSON parsing fails, abort and instruct user to re-run `/speckit-specify` or verify feature branch environment.
|
||||||
|
- For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||||
|
|
||||||
|
2. **IF EXISTS**: Load `.specify/memory/constitution.md` for project principles and governance constraints.
|
||||||
|
|
||||||
|
3. Load the current spec file. Perform a structured ambiguity & coverage scan using this taxonomy. For each category, mark status: Clear / Partial / Missing. Produce an internal coverage map used for prioritization (do not output raw map unless no questions will be asked).
|
||||||
|
|
||||||
|
Functional Scope & Behavior:
|
||||||
|
- Core user goals & success criteria
|
||||||
|
- Explicit out-of-scope declarations
|
||||||
|
- User roles / personas differentiation
|
||||||
|
|
||||||
|
Domain & Data Model:
|
||||||
|
- Entities, attributes, relationships
|
||||||
|
- Identity & uniqueness rules
|
||||||
|
- Lifecycle/state transitions
|
||||||
|
- Data volume / scale assumptions
|
||||||
|
|
||||||
|
Interaction & UX Flow:
|
||||||
|
- Critical user journeys / sequences
|
||||||
|
- Error/empty/loading states
|
||||||
|
- Accessibility or localization notes
|
||||||
|
|
||||||
|
Non-Functional Quality Attributes:
|
||||||
|
- Performance (latency, throughput targets)
|
||||||
|
- Scalability (horizontal/vertical, limits)
|
||||||
|
- Reliability & availability (uptime, recovery expectations)
|
||||||
|
- Observability (logging, metrics, tracing signals)
|
||||||
|
- Security & privacy (authN/Z, data protection, threat assumptions)
|
||||||
|
- Compliance / regulatory constraints (if any)
|
||||||
|
|
||||||
|
Integration & External Dependencies:
|
||||||
|
- External services/APIs and failure modes
|
||||||
|
- Data import/export formats
|
||||||
|
- Protocol/versioning assumptions
|
||||||
|
|
||||||
|
Edge Cases & Failure Handling:
|
||||||
|
- Negative scenarios
|
||||||
|
- Rate limiting / throttling
|
||||||
|
- Conflict resolution (e.g., concurrent edits)
|
||||||
|
|
||||||
|
Constraints & Tradeoffs:
|
||||||
|
- Technical constraints (language, storage, hosting)
|
||||||
|
- Explicit tradeoffs or rejected alternatives
|
||||||
|
|
||||||
|
Terminology & Consistency:
|
||||||
|
- Canonical glossary terms
|
||||||
|
- Avoided synonyms / deprecated terms
|
||||||
|
|
||||||
|
Completion Signals:
|
||||||
|
- Acceptance criteria testability
|
||||||
|
- Measurable Definition of Done style indicators
|
||||||
|
|
||||||
|
Misc / Placeholders:
|
||||||
|
- TODO markers / unresolved decisions
|
||||||
|
- Ambiguous adjectives ("robust", "intuitive") lacking quantification
|
||||||
|
|
||||||
|
For each category with Partial or Missing status, add a candidate question opportunity unless:
|
||||||
|
- Clarification would not materially change implementation or validation strategy
|
||||||
|
- Information is better deferred to planning phase (note internally)
|
||||||
|
|
||||||
|
4. Generate (internally) a prioritized queue of candidate clarification questions (maximum 5). Do NOT output them all at once. Apply these constraints:
|
||||||
|
- Maximum of 5 total questions across the whole session.
|
||||||
|
- Each question must be answerable with EITHER:
|
||||||
|
- A short multiple‑choice selection (2–5 distinct, mutually exclusive options), OR
|
||||||
|
- A one-word / short‑phrase answer (explicitly constrain: "Answer in <=5 words").
|
||||||
|
- Only include questions whose answers materially impact architecture, data modeling, task decomposition, test design, UX behavior, operational readiness, or compliance validation.
|
||||||
|
- Ensure category coverage balance: attempt to cover the highest impact unresolved categories first; avoid asking two low-impact questions when a single high-impact area (e.g., security posture) is unresolved.
|
||||||
|
- Exclude questions already answered, trivial stylistic preferences, or plan-level execution details (unless blocking correctness).
|
||||||
|
- Favor clarifications that reduce downstream rework risk or prevent misaligned acceptance tests.
|
||||||
|
- If more than 5 categories remain unresolved, select the top 5 by (Impact * Uncertainty) heuristic.
|
||||||
|
|
||||||
|
5. Sequential questioning loop (interactive):
|
||||||
|
- Present EXACTLY ONE question at a time.
|
||||||
|
- For multiple‑choice questions:
|
||||||
|
- **Analyze all options** and determine the **most suitable option** based on:
|
||||||
|
- Best practices for the project type
|
||||||
|
- Common patterns in similar implementations
|
||||||
|
- Risk reduction (security, performance, maintainability)
|
||||||
|
- Alignment with any explicit project goals or constraints visible in the spec
|
||||||
|
- Present your **recommended option prominently** at the top with clear reasoning (1-2 sentences explaining why this is the best choice).
|
||||||
|
- Format as: `**Recommended:** Option [X] - <reasoning>`
|
||||||
|
- Then render all options as a Markdown table:
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| A | <Option A description> |
|
||||||
|
| B | <Option B description> |
|
||||||
|
| C | <Option C description> (add D/E as needed up to 5) |
|
||||||
|
| Short | Provide a different short answer (<=5 words) (Include only if free-form alternative is appropriate) |
|
||||||
|
|
||||||
|
- After the table, add: `You can reply with the option letter (e.g., "A"), accept the recommendation by saying "yes" or "recommended", or provide your own short answer.`
|
||||||
|
- For short‑answer style (no meaningful discrete options):
|
||||||
|
- Provide your **suggested answer** based on best practices and context.
|
||||||
|
- Format as: `**Suggested:** <your proposed answer> - <brief reasoning>`
|
||||||
|
- Then output: `Format: Short answer (<=5 words). You can accept the suggestion by saying "yes" or "suggested", or provide your own answer.`
|
||||||
|
- After the user answers:
|
||||||
|
- If the user replies with "yes", "recommended", or "suggested", use your previously stated recommendation/suggestion as the answer.
|
||||||
|
- Otherwise, validate the answer maps to one option or fits the <=5 word constraint.
|
||||||
|
- If ambiguous, ask for a quick disambiguation (count still belongs to same question; do not advance).
|
||||||
|
- Once satisfactory, record it in working memory (do not yet write to disk) and move to the next queued question.
|
||||||
|
- Stop asking further questions when:
|
||||||
|
- All critical ambiguities resolved early (remaining queued items become unnecessary), OR
|
||||||
|
- User signals completion ("done", "good", "no more"), OR
|
||||||
|
- You reach 5 asked questions.
|
||||||
|
- Never reveal future queued questions in advance.
|
||||||
|
- If no valid questions exist at start, immediately report no critical ambiguities.
|
||||||
|
|
||||||
|
6. Integration after EACH accepted answer (incremental update approach):
|
||||||
|
- Maintain in-memory representation of the spec (loaded once at start) plus the raw file contents.
|
||||||
|
- For the first integrated answer in this session:
|
||||||
|
- Ensure a `## Clarifications` section exists (create it just after the highest-level contextual/overview section per the spec template if missing).
|
||||||
|
- Under it, create (if not present) a `### Session YYYY-MM-DD` subheading for today.
|
||||||
|
- Append a bullet line immediately after acceptance: `- Q: <question> → A: <final answer>`.
|
||||||
|
- Then immediately apply the clarification to the most appropriate section(s):
|
||||||
|
- Functional ambiguity → Update or add a bullet in Functional Requirements.
|
||||||
|
- User interaction / actor distinction → Update User Stories or Actors subsection (if present) with clarified role, constraint, or scenario.
|
||||||
|
- Data shape / entities → Update Data Model (add fields, types, relationships) preserving ordering; note added constraints succinctly.
|
||||||
|
- Non-functional constraint → Add/modify measurable criteria in Success Criteria > Measurable Outcomes (convert vague adjective to metric or explicit target).
|
||||||
|
- Edge case / negative flow → Add a new bullet under Edge Cases / Error Handling (or create such subsection if template provides placeholder for it).
|
||||||
|
- Terminology conflict → Normalize term across spec; retain original only if necessary by adding `(formerly referred to as "X")` once.
|
||||||
|
- If the clarification invalidates an earlier ambiguous statement, replace that statement instead of duplicating; leave no obsolete contradictory text.
|
||||||
|
- Save the spec file AFTER each integration to minimize risk of context loss (atomic overwrite).
|
||||||
|
- Preserve formatting: do not reorder unrelated sections; keep heading hierarchy intact.
|
||||||
|
- Keep each inserted clarification minimal and testable (avoid narrative drift).
|
||||||
|
|
||||||
|
7. Validation (performed after EACH write plus final pass):
|
||||||
|
- Clarifications session contains exactly one bullet per accepted answer (no duplicates).
|
||||||
|
- Total asked (accepted) questions ≤ 5.
|
||||||
|
- Updated sections contain no lingering vague placeholders the new answer was meant to resolve.
|
||||||
|
- No contradictory earlier statement remains (scan for now-invalid alternative choices removed).
|
||||||
|
- Markdown structure valid; only allowed new headings: `## Clarifications`, `### Session YYYY-MM-DD`.
|
||||||
|
- Terminology consistency: same canonical term used across all updated sections.
|
||||||
|
|
||||||
|
8. Write the updated spec back to `FEATURE_SPEC`.
|
||||||
|
|
||||||
|
9. **Re-validate Spec Quality Checklist** (if it exists):
|
||||||
|
- Check if `FEATURE_DIR/checklists/requirements.md` exists.
|
||||||
|
- If it does NOT exist, skip this step silently.
|
||||||
|
- If it exists:
|
||||||
|
1. Read the checklist file.
|
||||||
|
2. Identify all GitHub task-list checkbox lines — lines matching `- [ ]`, `- [x]`, or `- [X]` (case-insensitive, tolerant of leading whitespace for nested items) outside of code fences. Ignore all other content (headings, notes, non-checkbox bullets, metadata).
|
||||||
|
3. For each checkbox line, record its current marker state (checked or unchecked) and item text into a before-snapshot list.
|
||||||
|
4. Re-evaluate each checkbox item against the **updated** spec (the version just saved in step 7).
|
||||||
|
5. For each checkbox item, update only if the checked/unchecked state actually changes:
|
||||||
|
- If the item now passes and was unchecked: change `[ ]` to `[x]`.
|
||||||
|
- If the item now fails and was checked: change `[x]`/`[X]` to `[ ]`.
|
||||||
|
- If the state is unchanged: leave the marker as-is (preserve existing case to avoid cosmetic diffs).
|
||||||
|
6. Save the updated checklist file. **Only toggle the `[ ]`/`[x]` marker portion of checkbox lines whose state changed.** All other file content — headings, metadata, notes, line ordering, whitespace — must remain unchanged to avoid noisy diffs.
|
||||||
|
7. Compare the before-snapshot with the current state to compute three lists for the Completion Report:
|
||||||
|
- **Newly passing**: items that changed from unchecked to checked.
|
||||||
|
- **Regressions**: items that changed from checked to unchecked.
|
||||||
|
- **Still unchecked**: items that remain unchecked.
|
||||||
|
8. Record the before/after pass counts as checked/total checkbox items (e.g., "12/16 → 15/16 items passing").
|
||||||
|
|
||||||
|
Behavior rules:
|
||||||
|
|
||||||
|
- If no meaningful ambiguities found (or all potential questions would be low-impact), respond: "No critical ambiguities detected worth formal clarification." and suggest proceeding.
|
||||||
|
- If spec file missing, instruct user to run `/speckit-specify` first (do not create a new spec here).
|
||||||
|
- Never exceed 5 total asked questions (clarification retries for a single question do not count as new questions).
|
||||||
|
- Avoid speculative tech stack questions unless the absence blocks functional clarity.
|
||||||
|
- Respect user early termination signals ("stop", "done", "proceed").
|
||||||
|
- If no questions asked due to full coverage, output a compact coverage summary (all categories Clear) then suggest advancing.
|
||||||
|
- If quota reached with unresolved high-impact categories remaining, explicitly flag them under Deferred with rationale.
|
||||||
|
|
||||||
|
Context for prioritization: $ARGUMENTS
|
||||||
|
|
||||||
|
## Mandatory Post-Execution Hooks
|
||||||
|
|
||||||
|
**You MUST complete this section before reporting completion to the user.**
|
||||||
|
|
||||||
|
Check if `.specify/extensions.yml` exists in the project root.
|
||||||
|
- If it does not exist, or no hooks are registered under `hooks.after_clarify`, skip to the Completion Report.
|
||||||
|
- If it exists, read it and look for entries under the `hooks.after_clarify` key.
|
||||||
|
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue to the Completion Report.
|
||||||
|
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||||
|
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||||
|
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||||
|
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||||
|
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||||
|
- For each executable hook, output the following based on its `optional` flag:
|
||||||
|
- **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**:
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Automatic Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
```
|
||||||
|
After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook.
|
||||||
|
- **Optional hook** (`optional: true`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
|
||||||
|
## Completion Report
|
||||||
|
|
||||||
|
Report completion (after questioning loop ends or early termination):
|
||||||
|
- Number of questions asked & answered.
|
||||||
|
- Path to updated spec.
|
||||||
|
- Sections touched (list names).
|
||||||
|
- Spec quality checklist status (if `FEATURE_DIR/checklists/requirements.md` was re-validated): show before/after pass counts (e.g., "Spec Quality Checklist: 12/16 → 15/16 items passing") and list any items that changed state — both newly checked (unchecked → checked) and any regressions (checked → unchecked). If any items remain unchecked, list them as areas needing attention.
|
||||||
|
- Coverage summary table listing each taxonomy category with Status: Resolved (was Partial/Missing and addressed), Deferred (exceeds question quota or better suited for planning), Clear (already sufficient), Outstanding (still Partial/Missing but low impact).
|
||||||
|
- If any Outstanding or Deferred remain, recommend whether to proceed to `/speckit-plan` or run `/speckit-clarify` again later post-plan.
|
||||||
|
- Suggested next command.
|
||||||
|
|
||||||
|
## Done When
|
||||||
|
|
||||||
|
- [ ] Spec ambiguities identified and clarifications integrated into spec file
|
||||||
|
- [ ] Spec quality checklist re-validated against updated spec (if `FEATURE_DIR/checklists/requirements.md` exists)
|
||||||
|
- [ ] Extension hooks dispatched or skipped according to the rules in Mandatory Post-Execution Hooks above
|
||||||
|
- [ ] Completion reported to user with questions answered, sections touched, checklist status, and coverage summary
|
||||||
156
.agents/skills/speckit-constitution/SKILL.md
Normal file
156
.agents/skills/speckit-constitution/SKILL.md
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
---
|
||||||
|
name: "speckit-constitution"
|
||||||
|
description: "Create or update the project constitution from interactive or provided principle inputs, ensuring all dependent templates stay in sync."
|
||||||
|
compatibility: "Requires spec-kit project structure with .specify/ directory"
|
||||||
|
metadata:
|
||||||
|
author: "github-spec-kit"
|
||||||
|
source: "templates/commands/constitution.md"
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## User Input
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ARGUMENTS
|
||||||
|
```
|
||||||
|
|
||||||
|
You **MUST** consider the user input before proceeding (if not empty).
|
||||||
|
|
||||||
|
## Pre-Execution Checks
|
||||||
|
|
||||||
|
**Check for extension hooks (before constitution update)**:
|
||||||
|
- Check if `.specify/extensions.yml` exists in the project root.
|
||||||
|
- If it exists, read it and look for entries under the `hooks.before_constitution` key
|
||||||
|
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||||
|
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||||
|
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||||
|
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||||
|
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||||
|
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||||
|
- For each executable hook, output the following based on its `optional` flag:
|
||||||
|
- **Optional hook** (`optional: true`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Pre-Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
- **Mandatory hook** (`optional: false`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Automatic Pre-Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
|
||||||
|
Wait for the result of the hook command before proceeding to the Outline.
|
||||||
|
```
|
||||||
|
After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook.
|
||||||
|
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||||
|
|
||||||
|
## Outline
|
||||||
|
|
||||||
|
You are updating the project constitution at `.specify/memory/constitution.md`. This file is a TEMPLATE containing placeholder tokens in square brackets (e.g. `[PROJECT_NAME]`, `[PRINCIPLE_1_NAME]`). Your job is to (a) collect/derive concrete values, (b) fill the template precisely, and (c) propagate any amendments across dependent artifacts.
|
||||||
|
|
||||||
|
**Note**: If `.specify/memory/constitution.md` does not exist yet, it should have been initialized from `.specify/templates/constitution-template.md` during project setup. If it's missing, copy the template first.
|
||||||
|
|
||||||
|
Follow this execution flow:
|
||||||
|
|
||||||
|
1. Load the existing constitution at `.specify/memory/constitution.md`.
|
||||||
|
- Identify every placeholder token of the form `[ALL_CAPS_IDENTIFIER]`.
|
||||||
|
**IMPORTANT**: The user might require less or more principles than the ones used in the template. If a number is specified, respect that - follow the general template. You will update the doc accordingly.
|
||||||
|
|
||||||
|
2. Collect/derive values for placeholders:
|
||||||
|
- If user input (conversation) supplies a value, use it.
|
||||||
|
- Otherwise infer from existing repo context (README, docs, prior constitution versions if embedded).
|
||||||
|
- For governance dates: `RATIFICATION_DATE` is the original adoption date (if unknown ask or mark TODO), `LAST_AMENDED_DATE` is today if changes are made, otherwise keep previous.
|
||||||
|
- `CONSTITUTION_VERSION` must increment according to semantic versioning rules:
|
||||||
|
- MAJOR: Backward incompatible governance/principle removals or redefinitions.
|
||||||
|
- MINOR: New principle/section added or materially expanded guidance.
|
||||||
|
- PATCH: Clarifications, wording, typo fixes, non-semantic refinements.
|
||||||
|
- If version bump type ambiguous, propose reasoning before finalizing.
|
||||||
|
|
||||||
|
3. Draft the updated constitution content:
|
||||||
|
- Replace every placeholder with concrete text (no bracketed tokens left except intentionally retained template slots that the project has chosen not to define yet—explicitly justify any left).
|
||||||
|
- Preserve heading hierarchy and comments can be removed once replaced unless they still add clarifying guidance.
|
||||||
|
- Ensure each Principle section: succinct name line, paragraph (or bullet list) capturing non‑negotiable rules, explicit rationale if not obvious.
|
||||||
|
- Ensure Governance section lists amendment procedure, versioning policy, and compliance review expectations.
|
||||||
|
|
||||||
|
4. Consistency propagation checklist (convert prior checklist into active validations):
|
||||||
|
- Read `.specify/templates/plan-template.md` and ensure any "Constitution Check" or rules align with updated principles.
|
||||||
|
- Read `.specify/templates/spec-template.md` for scope/requirements alignment—update if constitution adds/removes mandatory sections or constraints.
|
||||||
|
- Read `.specify/templates/tasks-template.md` and ensure task categorization reflects new or removed principle-driven task types (e.g., observability, versioning, testing discipline).
|
||||||
|
- Read each command file in `.specify/templates/commands/*.md` (including this one) to verify no outdated references (agent-specific names like CLAUDE only) remain when generic guidance is required.
|
||||||
|
- Read any runtime guidance docs (e.g., `README.md`, `docs/quickstart.md`, or agent-specific guidance files if present). Update references to principles changed.
|
||||||
|
|
||||||
|
5. Produce a Sync Impact Report (prepend as an HTML comment at top of the constitution file after update):
|
||||||
|
- Version change: old → new
|
||||||
|
- List of modified principles (old title → new title if renamed)
|
||||||
|
- Added sections
|
||||||
|
- Removed sections
|
||||||
|
- Templates requiring updates (✅ updated / ⚠ pending) with file paths
|
||||||
|
- Follow-up TODOs if any placeholders intentionally deferred.
|
||||||
|
|
||||||
|
6. Validation before final output:
|
||||||
|
- No remaining unexplained bracket tokens.
|
||||||
|
- Version line matches report.
|
||||||
|
- Dates ISO format YYYY-MM-DD.
|
||||||
|
- Principles are declarative, testable, and free of vague language ("should" → replace with MUST/SHOULD rationale where appropriate).
|
||||||
|
|
||||||
|
7. Write the completed constitution back to `.specify/memory/constitution.md` (overwrite).
|
||||||
|
|
||||||
|
8. Output a final summary to the user with:
|
||||||
|
- New version and bump rationale.
|
||||||
|
- Any files flagged for manual follow-up.
|
||||||
|
- Suggested commit message (e.g., `docs: amend constitution to vX.Y.Z (principle additions + governance update)`).
|
||||||
|
|
||||||
|
Formatting & Style Requirements:
|
||||||
|
|
||||||
|
- Use Markdown headings exactly as in the template (do not demote/promote levels).
|
||||||
|
- Wrap long rationale lines to keep readability (<100 chars ideally) but do not hard enforce with awkward breaks.
|
||||||
|
- Keep a single blank line between sections.
|
||||||
|
- Avoid trailing whitespace.
|
||||||
|
|
||||||
|
If the user supplies partial updates (e.g., only one principle revision), still perform validation and version decision steps.
|
||||||
|
|
||||||
|
If critical info missing (e.g., ratification date truly unknown), insert `TODO(<FIELD_NAME>): explanation` and include in the Sync Impact Report under deferred items.
|
||||||
|
|
||||||
|
Do not create a new template; always operate on the existing `.specify/memory/constitution.md` file.
|
||||||
|
|
||||||
|
## Post-Execution Checks
|
||||||
|
|
||||||
|
**Check for extension hooks (after constitution update)**:
|
||||||
|
Check if `.specify/extensions.yml` exists in the project root.
|
||||||
|
- If it exists, read it and look for entries under the `hooks.after_constitution` key
|
||||||
|
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||||
|
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||||
|
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||||
|
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||||
|
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||||
|
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||||
|
- For each executable hook, output the following based on its `optional` flag:
|
||||||
|
- **Optional hook** (`optional: true`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
- **Mandatory hook** (`optional: false`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Automatic Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
```
|
||||||
|
After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook.
|
||||||
|
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||||
277
.agents/skills/speckit-converge/SKILL.md
Normal file
277
.agents/skills/speckit-converge/SKILL.md
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
---
|
||||||
|
name: "speckit-converge"
|
||||||
|
description: "Assess the current codebase against the feature's spec, plan, and tasks, then append any remaining unbuilt work as new tasks to tasks.md so implement can complete it."
|
||||||
|
compatibility: "Requires spec-kit project structure with .specify/ directory"
|
||||||
|
metadata:
|
||||||
|
author: "github-spec-kit"
|
||||||
|
source: "templates/commands/converge.md"
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## User Input
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ARGUMENTS
|
||||||
|
```
|
||||||
|
|
||||||
|
You **MUST** consider the user input before proceeding (if not empty).
|
||||||
|
|
||||||
|
## Pre-Execution Checks
|
||||||
|
|
||||||
|
**Check for extension hooks (before convergence)**:
|
||||||
|
|
||||||
|
- Check if `.specify/extensions.yml` exists in the project root.
|
||||||
|
- If it exists, read it and look for entries under the `hooks.before_converge` key
|
||||||
|
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||||
|
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||||
|
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||||
|
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||||
|
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||||
|
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||||
|
- For each executable hook, output the following based on its `optional` flag:
|
||||||
|
- **Optional hook** (`optional: true`):
|
||||||
|
|
||||||
|
```text
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Pre-Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Mandatory hook** (`optional: false`):
|
||||||
|
|
||||||
|
```text
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Automatic Pre-Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
|
||||||
|
Wait for the result of the hook command before proceeding to the Goal.
|
||||||
|
```
|
||||||
|
After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook.
|
||||||
|
|
||||||
|
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Close the gap between what a feature's specification, plan, and tasks call for and what the
|
||||||
|
codebase currently implements. Read `spec.md`, `plan.md`, and `tasks.md` as the **sole
|
||||||
|
source of intent** (with the constitution as governing constraints), assess the current
|
||||||
|
state of the code, determine which requirements, acceptance criteria, plan decisions, and
|
||||||
|
existing tasks are unmet, incomplete, or only partially satisfied, and **append each piece
|
||||||
|
of remaining work as a new, traceable task** at the bottom of `tasks.md` so that
|
||||||
|
`/speckit-implement` can complete it. This command MUST run only after
|
||||||
|
`/speckit-implement` has run on the current `tasks.md`, and after `/speckit-tasks` has produced a complete `tasks.md`.
|
||||||
|
|
||||||
|
This is **not** a diff tool and does **not** track changes. It assesses the present state
|
||||||
|
of the code relative to the feature's artifacts — no git, no branch comparison, no history.
|
||||||
|
|
||||||
|
## Operating Constraints
|
||||||
|
|
||||||
|
**APPEND-ONLY, NEVER REWRITE**: The command's **only** write is appending a new
|
||||||
|
`## Phase N: Convergence` section to `tasks.md`. It MUST NOT:
|
||||||
|
|
||||||
|
- modify `spec.md` or `plan.md` in any way;
|
||||||
|
- rewrite, renumber, reorder, or delete any existing task (including tasks from a prior
|
||||||
|
Convergence phase);
|
||||||
|
- modify, create, or delete any application code — completing the appended tasks is the
|
||||||
|
job of `/speckit-implement`.
|
||||||
|
|
||||||
|
When the codebase already satisfies everything, the command MUST leave `tasks.md`
|
||||||
|
**byte-for-byte unchanged** (no empty Convergence header) and report a clean result.
|
||||||
|
|
||||||
|
**Constitution Authority**: The project constitution (`.specify/memory/constitution.md`) is
|
||||||
|
**non-negotiable**. Code that violates a MUST principle is the highest-severity finding and
|
||||||
|
produces a corresponding remediation task. If the constitution is an unfilled template,
|
||||||
|
skip constitution checks gracefully rather than failing.
|
||||||
|
|
||||||
|
## Execution Steps
|
||||||
|
|
||||||
|
### 1. Initialize Convergence Context
|
||||||
|
|
||||||
|
Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` once from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS. Derive absolute paths:
|
||||||
|
|
||||||
|
- SPEC = FEATURE_DIR/spec.md
|
||||||
|
- PLAN = FEATURE_DIR/plan.md
|
||||||
|
- TASKS = FEATURE_DIR/tasks.md
|
||||||
|
- CONSTITUTION = `.specify/memory/constitution.md` (if present)
|
||||||
|
If `spec.md`, `plan.md`, or `tasks.md` is missing, STOP with a clear, actionable message naming the
|
||||||
|
prerequisite command to run (`/speckit-specify` for a missing spec, `/speckit-plan` for a missing plan,
|
||||||
|
`/speckit-tasks` for missing tasks). Do not produce partial output.
|
||||||
|
For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||||
|
|
||||||
|
### 2. Load Artifacts (Progressive Disclosure)
|
||||||
|
|
||||||
|
Load only the minimal necessary context from each artifact:
|
||||||
|
|
||||||
|
**From spec.md:**
|
||||||
|
|
||||||
|
- Functional Requirements (FR-###)
|
||||||
|
- Success Criteria (SC-###) — include only items requiring buildable work; exclude
|
||||||
|
post-launch outcome metrics and business KPIs
|
||||||
|
- User Stories and their Acceptance Scenarios
|
||||||
|
- Edge Cases (if present)
|
||||||
|
|
||||||
|
**From plan.md:**
|
||||||
|
|
||||||
|
- Architecture/stack choices and technical decisions
|
||||||
|
- Data Model references
|
||||||
|
- Phases and named touch-points (files/components the plan says will be created or edited)
|
||||||
|
- Technical constraints
|
||||||
|
|
||||||
|
**From tasks.md:**
|
||||||
|
|
||||||
|
- Task IDs (to compute the next ID and next phase number)
|
||||||
|
- Descriptions, phase grouping, and referenced file paths
|
||||||
|
|
||||||
|
**From constitution (if not an unfilled template):**
|
||||||
|
|
||||||
|
- Principle names and MUST/SHOULD normative statements
|
||||||
|
|
||||||
|
### 3. Build the Intent Inventory
|
||||||
|
|
||||||
|
Create an internal model (do not echo raw artifacts):
|
||||||
|
|
||||||
|
- **Requirements inventory**: one stable key per FR-### / SC-### / user-story acceptance
|
||||||
|
scenario (e.g. `US1/AC2`), plus the plan decisions and constitution principles that
|
||||||
|
impose buildable obligations.
|
||||||
|
- **Code-scope map**: from the file paths named in `plan.md` and `tasks.md`, plus a keyword
|
||||||
|
search for the concepts each requirement describes, derive the set of source files and
|
||||||
|
components in scope for assessment. Bound the assessment to these — do **not** infer
|
||||||
|
scope beyond what the artifacts define.
|
||||||
|
|
||||||
|
### 4. Assess the Codebase and Classify Findings
|
||||||
|
|
||||||
|
For each item in the intent inventory, inspect the current code in scope and produce a
|
||||||
|
`Finding` only where there is a gap. Classify every finding by **gap type**:
|
||||||
|
|
||||||
|
- **`missing`**: the required work is absent from the code entirely.
|
||||||
|
- **`partial`**: the work exists but does not yet fully satisfy the requirement /
|
||||||
|
acceptance criterion / plan decision.
|
||||||
|
- **`contradicts`**: the code does something that conflicts with stated intent or a
|
||||||
|
constitution MUST principle.
|
||||||
|
- **`unrequested`**: the code contains work not called for by the spec, plan, or tasks
|
||||||
|
(surfaced for awareness — converge does **not** delete code, it only appends a task to
|
||||||
|
review/justify or remove it).
|
||||||
|
|
||||||
|
Each `Finding` records: a stable id, the `source-ref` it traces to, the `gap-type`, a
|
||||||
|
severity, and a short human-readable description with the evidence (the file/area observed).
|
||||||
|
|
||||||
|
**Edge cases:**
|
||||||
|
|
||||||
|
- **Little or no code yet**: treat the entire specified scope as `missing` remaining work
|
||||||
|
rather than failing.
|
||||||
|
- **Nothing remains**: produce zero findings and follow the converged branch in Step 7.
|
||||||
|
|
||||||
|
### 5. Assign Severity
|
||||||
|
|
||||||
|
- **CRITICAL**: violates a constitution MUST principle, or a `missing`/`contradicts` gap
|
||||||
|
that blocks baseline functionality of a P1 user story.
|
||||||
|
- **HIGH**: a `missing` or `partial` gap on a core functional requirement or acceptance
|
||||||
|
criterion.
|
||||||
|
- **MEDIUM**: a `partial` gap on a secondary requirement, or an `unrequested` addition with
|
||||||
|
unclear justification.
|
||||||
|
- **LOW**: minor partial gaps, polish, or low-risk `unrequested` additions.
|
||||||
|
|
||||||
|
### 6. Present the In-Session Findings Summary
|
||||||
|
|
||||||
|
Before appending anything, output a compact, severity-graded summary (no file writes yet):
|
||||||
|
|
||||||
|
## Convergence Findings
|
||||||
|
|
||||||
|
| ID | Gap Type | Severity | Source | Evidence | Remaining Work |
|
||||||
|
|----|----------|----------|--------|----------|----------------|
|
||||||
|
| F1 | missing | HIGH | FR-008 | Example: no append-only guard detected in path/to/module.py when writing tasks.md | Add append-only enforcement |
|
||||||
|
|
||||||
|
**Summary metrics:**
|
||||||
|
|
||||||
|
- Requirements / acceptance criteria checked
|
||||||
|
- Plan decisions checked
|
||||||
|
- Constitution principles checked (or "skipped — template")
|
||||||
|
- Findings by gap type (missing / partial / contradicts / unrequested)
|
||||||
|
- Findings by severity
|
||||||
|
|
||||||
|
### 7. Append Convergence Tasks (or report converged)
|
||||||
|
|
||||||
|
**If there are one or more actionable findings** (`tasks_appended` outcome):
|
||||||
|
|
||||||
|
Append to the **end** of `tasks.md`, per the append contract:
|
||||||
|
|
||||||
|
1. Scan all existing task IDs; let `M` be the maximum. Determine the next phase number `N`
|
||||||
|
(highest existing phase + 1).
|
||||||
|
2. Write a single new section header `## Phase N: Convergence`.
|
||||||
|
3. Emit one checklist item per actionable finding, ordered CRITICAL/HIGH first, assigning
|
||||||
|
zero-padded IDs `T{M+1:03d}, T{M+2:03d}, …`:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
- [ ] T042 <imperative description> per <source-ref> (<gap-type>)
|
||||||
|
```
|
||||||
|
|
||||||
|
`<source-ref>` traces the task to its origin: e.g. `FR-003`, `SC-002`,
|
||||||
|
`US1/AC2`, `plan: storage decision`, `Constitution II`.
|
||||||
|
|
||||||
|
`<gap-type>` is one of `missing`, `partial`, `contradicts`, `unrequested`.
|
||||||
|
|
||||||
|
Constitution-violation tasks MUST be emitted first and described as
|
||||||
|
`CRITICAL`.
|
||||||
|
4. Never reuse or renumber existing IDs. If a prior Convergence phase exists, add a new,
|
||||||
|
separately-numbered one below it — do not touch the old one.
|
||||||
|
|
||||||
|
**If there are no actionable findings** (`converged` outcome):
|
||||||
|
|
||||||
|
- Do **not** modify `tasks.md` at all — no empty phase header.
|
||||||
|
- Report: **"✅ Converged — the implementation satisfies the spec, plan, and tasks."**
|
||||||
|
- Include the summary counts of what was checked.
|
||||||
|
|
||||||
|
### 8. Provide Next Actions (Handoff)
|
||||||
|
|
||||||
|
- On `tasks_appended`: state how many tasks were appended under which phase, and recommend
|
||||||
|
running `/speckit-implement` to complete them; note that a follow-up converge
|
||||||
|
run will find fewer or no remaining items.
|
||||||
|
- On `converged`: recommend proceeding to review / opening a PR. No further implement pass
|
||||||
|
is needed for this feature's specified scope.
|
||||||
|
|
||||||
|
### 9. Check for extension hooks
|
||||||
|
|
||||||
|
After producing the result, check if `.specify/extensions.yml` exists in the project root.
|
||||||
|
|
||||||
|
- If it exists, read it and look for entries under the `hooks.after_converge` key
|
||||||
|
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||||
|
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||||
|
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||||
|
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||||
|
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||||
|
- Report the convergence outcome (`converged` or `tasks_appended`) in-session before listing
|
||||||
|
any hooks, so users can decide whether to run optional follow-up commands.
|
||||||
|
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||||
|
- For each executable hook, output the following based on its `optional` flag:
|
||||||
|
- **Optional hook** (`optional: true`):
|
||||||
|
|
||||||
|
```text
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Mandatory hook** (`optional: false`):
|
||||||
|
|
||||||
|
```text
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Automatic Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
```
|
||||||
|
After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook.
|
||||||
|
|
||||||
|
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||||
223
.agents/skills/speckit-implement/SKILL.md
Normal file
223
.agents/skills/speckit-implement/SKILL.md
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
---
|
||||||
|
name: "speckit-implement"
|
||||||
|
description: "Execute the implementation plan by processing and executing all tasks defined in tasks.md"
|
||||||
|
compatibility: "Requires spec-kit project structure with .specify/ directory"
|
||||||
|
metadata:
|
||||||
|
author: "github-spec-kit"
|
||||||
|
source: "templates/commands/implement.md"
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## User Input
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ARGUMENTS
|
||||||
|
```
|
||||||
|
|
||||||
|
You **MUST** consider the user input before proceeding (if not empty).
|
||||||
|
|
||||||
|
## Pre-Execution Checks
|
||||||
|
|
||||||
|
**Check for extension hooks (before implementation)**:
|
||||||
|
- Check if `.specify/extensions.yml` exists in the project root.
|
||||||
|
- If it exists, read it and look for entries under the `hooks.before_implement` key
|
||||||
|
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||||
|
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||||
|
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||||
|
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||||
|
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||||
|
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||||
|
- For each executable hook, output the following based on its `optional` flag:
|
||||||
|
- **Optional hook** (`optional: true`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Pre-Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
- **Mandatory hook** (`optional: false`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Automatic Pre-Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
|
||||||
|
Wait for the result of the hook command before proceeding to the Outline.
|
||||||
|
```
|
||||||
|
After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook.
|
||||||
|
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||||
|
|
||||||
|
## Outline
|
||||||
|
|
||||||
|
1. Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||||
|
|
||||||
|
2. **Check checklists status** (if FEATURE_DIR/checklists/ exists):
|
||||||
|
- Scan all checklist files in the checklists/ directory
|
||||||
|
- For each checklist, count:
|
||||||
|
- Total items: All lines matching `- [ ]` or `- [X]` or `- [x]`
|
||||||
|
- Completed items: Lines matching `- [X]` or `- [x]`
|
||||||
|
- Incomplete items: Lines matching `- [ ]`
|
||||||
|
- Create a status table:
|
||||||
|
|
||||||
|
```text
|
||||||
|
| Checklist | Total | Completed | Incomplete | Status |
|
||||||
|
|-----------|-------|-----------|------------|--------|
|
||||||
|
| ux.md | 12 | 12 | 0 | ✓ PASS |
|
||||||
|
| test.md | 8 | 5 | 3 | ✗ FAIL |
|
||||||
|
| security.md | 6 | 6 | 0 | ✓ PASS |
|
||||||
|
```
|
||||||
|
|
||||||
|
- Calculate overall status:
|
||||||
|
- **PASS**: All checklists have 0 incomplete items
|
||||||
|
- **FAIL**: One or more checklists have incomplete items
|
||||||
|
|
||||||
|
- **If any checklist is incomplete**:
|
||||||
|
- Display the table with incomplete item counts
|
||||||
|
- **STOP** and ask: "Some checklists are incomplete. Do you want to proceed with implementation anyway? (yes/no)"
|
||||||
|
- Wait for user response before continuing
|
||||||
|
- If user says "no" or "wait" or "stop", halt execution
|
||||||
|
- If user says "yes" or "proceed" or "continue", proceed to step 3
|
||||||
|
|
||||||
|
- **If all checklists are complete**:
|
||||||
|
- Display the table showing all checklists passed
|
||||||
|
- Automatically proceed to step 3
|
||||||
|
|
||||||
|
3. Load and analyze the implementation context:
|
||||||
|
- **REQUIRED**: Read tasks.md for the complete task list and execution plan
|
||||||
|
- **REQUIRED**: Read plan.md for tech stack, architecture, and file structure
|
||||||
|
- **IF EXISTS**: Read data-model.md for entities and relationships
|
||||||
|
- **IF EXISTS**: Read contracts/ for API specifications and test requirements
|
||||||
|
- **IF EXISTS**: Read research.md for technical decisions and constraints
|
||||||
|
- **IF EXISTS**: Read .specify/memory/constitution.md for governance constraints
|
||||||
|
- **IF EXISTS**: Read quickstart.md for integration scenarios
|
||||||
|
|
||||||
|
4. **Project Setup Verification**:
|
||||||
|
- **REQUIRED**: Create/verify ignore files based on actual project setup:
|
||||||
|
|
||||||
|
**Detection & Creation Logic**:
|
||||||
|
- Check if the following command succeeds to determine if the repository is a git repo (create/verify .gitignore if so):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git rev-parse --git-dir 2>/dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
- Check if Dockerfile* exists or Docker in plan.md → create/verify .dockerignore
|
||||||
|
- Check if .eslintrc* exists → create/verify .eslintignore
|
||||||
|
- Check if eslint.config.* exists → ensure the config's `ignores` entries cover required patterns
|
||||||
|
- Check if .prettierrc* exists → create/verify .prettierignore
|
||||||
|
- Check if .npmrc or package.json exists → create/verify .npmignore (if publishing)
|
||||||
|
- Check if terraform files (*.tf) exist → create/verify .terraformignore
|
||||||
|
- Check if .helmignore needed (helm charts present) → create/verify .helmignore
|
||||||
|
|
||||||
|
**If ignore file already exists**: Verify it contains essential patterns, append missing critical patterns only
|
||||||
|
**If ignore file missing**: Create with full pattern set for detected technology
|
||||||
|
|
||||||
|
**Common Patterns by Technology** (from plan.md tech stack):
|
||||||
|
- **Node.js/JavaScript/TypeScript**: `node_modules/`, `dist/`, `build/`, `*.log`, `.env*`
|
||||||
|
- **Python**: `__pycache__/`, `*.pyc`, `.venv/`, `venv/`, `dist/`, `*.egg-info/`
|
||||||
|
- **Java**: `target/`, `*.class`, `*.jar`, `.gradle/`, `build/`
|
||||||
|
- **C#/.NET**: `bin/`, `obj/`, `*.user`, `*.suo`, `packages/`
|
||||||
|
- **Go**: `*.exe`, `*.test`, `vendor/`, `*.out`
|
||||||
|
- **Ruby**: `.bundle/`, `log/`, `tmp/`, `*.gem`, `vendor/bundle/`
|
||||||
|
- **PHP**: `vendor/`, `*.log`, `*.cache`, `*.env`
|
||||||
|
- **Rust**: `target/`, `debug/`, `release/`, `*.rs.bk`, `*.rlib`, `*.prof*`, `.idea/`, `*.log`, `.env*`
|
||||||
|
- **Kotlin**: `build/`, `out/`, `.gradle/`, `.idea/`, `*.class`, `*.jar`, `*.iml`, `*.log`, `.env*`
|
||||||
|
- **C++**: `build/`, `bin/`, `obj/`, `out/`, `*.o`, `*.so`, `*.a`, `*.exe`, `*.dll`, `.idea/`, `*.log`, `.env*`
|
||||||
|
- **C**: `build/`, `bin/`, `obj/`, `out/`, `*.o`, `*.a`, `*.so`, `*.exe`, `*.dll`, `autom4te.cache/`, `config.status`, `config.log`, `.idea/`, `*.log`, `.env*`
|
||||||
|
- **Swift**: `.build/`, `DerivedData/`, `*.swiftpm/`, `Packages/`
|
||||||
|
- **R**: `.Rproj.user/`, `.Rhistory`, `.RData`, `.Ruserdata`, `*.Rproj`, `packrat/`, `renv/`
|
||||||
|
- **Universal**: `.DS_Store`, `Thumbs.db`, `*.tmp`, `*.swp`, `.vscode/`, `.idea/`
|
||||||
|
|
||||||
|
**Tool-Specific Patterns**:
|
||||||
|
- **Docker**: `node_modules/`, `.git/`, `Dockerfile*`, `.dockerignore`, `*.log*`, `.env*`, `coverage/`
|
||||||
|
- **ESLint**: `node_modules/`, `dist/`, `build/`, `coverage/`, `*.min.js`
|
||||||
|
- **Prettier**: `node_modules/`, `dist/`, `build/`, `coverage/`, `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`
|
||||||
|
- **Terraform**: `.terraform/`, `*.tfstate*`, `*.tfvars`, `.terraform.lock.hcl`
|
||||||
|
- **Kubernetes/k8s**: `*.secret.yaml`, `secrets/`, `.kube/`, `kubeconfig*`, `*.key`, `*.crt`
|
||||||
|
|
||||||
|
5. Parse tasks.md structure and extract:
|
||||||
|
- **Task phases**: Setup, Tests, Core, Integration, Polish
|
||||||
|
- **Task dependencies**: Sequential vs parallel execution rules
|
||||||
|
- **Task details**: ID, description, file paths, parallel markers [P]
|
||||||
|
- **Execution flow**: Order and dependency requirements
|
||||||
|
|
||||||
|
6. Execute implementation following the task plan:
|
||||||
|
- **Phase-by-phase execution**: Complete each phase before moving to the next
|
||||||
|
- **Respect dependencies**: Run sequential tasks in order, parallel tasks [P] can run together
|
||||||
|
- **Follow TDD approach**: Execute test tasks before their corresponding implementation tasks
|
||||||
|
- **File-based coordination**: Tasks affecting the same files must run sequentially
|
||||||
|
- **Validation checkpoints**: Verify each phase completion before proceeding
|
||||||
|
|
||||||
|
7. Implementation execution rules:
|
||||||
|
- **Setup first**: Initialize project structure, dependencies, configuration
|
||||||
|
- **Tests before code**: If you need to write tests for contracts, entities, and integration scenarios
|
||||||
|
- **Core development**: Implement models, services, CLI commands, endpoints
|
||||||
|
- **Integration work**: Database connections, middleware, logging, external services
|
||||||
|
- **Polish and validation**: Unit tests, performance optimization, documentation
|
||||||
|
|
||||||
|
8. Progress tracking and error handling:
|
||||||
|
- Report progress after each completed task
|
||||||
|
- Halt execution if any non-parallel task fails
|
||||||
|
- For parallel tasks [P], continue with successful tasks, report failed ones
|
||||||
|
- Provide clear error messages with context for debugging
|
||||||
|
- Suggest next steps if implementation cannot proceed
|
||||||
|
- **IMPORTANT** For completed tasks, make sure to mark the task off as [X] in the tasks file.
|
||||||
|
|
||||||
|
9. Completion validation:
|
||||||
|
- Verify all required tasks are completed
|
||||||
|
- Check that implemented features match the original specification
|
||||||
|
- Validate that tests pass and coverage meets requirements
|
||||||
|
- Confirm the implementation follows the technical plan
|
||||||
|
|
||||||
|
Note: This command assumes a complete task breakdown exists in tasks.md. If tasks are incomplete or missing, suggest running `/speckit-tasks` first to regenerate the task list.
|
||||||
|
|
||||||
|
## Mandatory Post-Execution Hooks
|
||||||
|
|
||||||
|
**You MUST complete this section before reporting completion to the user.**
|
||||||
|
|
||||||
|
Check if `.specify/extensions.yml` exists in the project root.
|
||||||
|
- If it does not exist, or no hooks are registered under `hooks.after_implement`, skip to the Completion Report.
|
||||||
|
- If it exists, read it and look for entries under the `hooks.after_implement` key.
|
||||||
|
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue to the Completion Report.
|
||||||
|
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||||
|
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||||
|
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||||
|
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||||
|
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||||
|
- For each executable hook, output the following based on its `optional` flag:
|
||||||
|
- **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**:
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Automatic Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
```
|
||||||
|
After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook.
|
||||||
|
- **Optional hook** (`optional: true`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
|
||||||
|
## Completion Report
|
||||||
|
|
||||||
|
Report final status with summary of completed work.
|
||||||
|
|
||||||
|
## Done When
|
||||||
|
|
||||||
|
- [ ] All tasks in tasks.md completed and marked `[X]`
|
||||||
|
- [ ] Implementation validated against specification, plan, and test coverage
|
||||||
|
- [ ] Extension hooks dispatched or skipped according to the rules in Mandatory Post-Execution Hooks above
|
||||||
|
- [ ] Completion reported to user with summary of completed work
|
||||||
166
.agents/skills/speckit-plan/SKILL.md
Normal file
166
.agents/skills/speckit-plan/SKILL.md
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
---
|
||||||
|
name: "speckit-plan"
|
||||||
|
description: "Execute the implementation planning workflow using the plan template to generate design artifacts."
|
||||||
|
compatibility: "Requires spec-kit project structure with .specify/ directory"
|
||||||
|
metadata:
|
||||||
|
author: "github-spec-kit"
|
||||||
|
source: "templates/commands/plan.md"
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## User Input
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ARGUMENTS
|
||||||
|
```
|
||||||
|
|
||||||
|
You **MUST** consider the user input before proceeding (if not empty).
|
||||||
|
|
||||||
|
## Pre-Execution Checks
|
||||||
|
|
||||||
|
**Check for extension hooks (before planning)**:
|
||||||
|
- Check if `.specify/extensions.yml` exists in the project root.
|
||||||
|
- If it exists, read it and look for entries under the `hooks.before_plan` key
|
||||||
|
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||||
|
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||||
|
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||||
|
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||||
|
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||||
|
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||||
|
- For each executable hook, output the following based on its `optional` flag:
|
||||||
|
- **Optional hook** (`optional: true`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Pre-Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
- **Mandatory hook** (`optional: false`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Automatic Pre-Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
|
||||||
|
Wait for the result of the hook command before proceeding to the Outline.
|
||||||
|
```
|
||||||
|
After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook.
|
||||||
|
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||||
|
|
||||||
|
## Outline
|
||||||
|
|
||||||
|
1. **Setup**: Run `.specify/scripts/bash/setup-plan.sh --json` from repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||||
|
|
||||||
|
2. **Load context**: Read FEATURE_SPEC and `.specify/memory/constitution.md`. Load IMPL_PLAN template (already copied).
|
||||||
|
|
||||||
|
3. **Execute plan workflow**: Follow the structure in IMPL_PLAN template to:
|
||||||
|
- Fill Technical Context (mark unknowns as "NEEDS CLARIFICATION")
|
||||||
|
- Fill Constitution Check section from constitution
|
||||||
|
- Evaluate gates (ERROR if violations unjustified)
|
||||||
|
- Phase 0: Generate research.md (resolve all NEEDS CLARIFICATION)
|
||||||
|
- Phase 1: Generate data-model.md, contracts/, quickstart.md
|
||||||
|
- Re-evaluate Constitution Check post-design
|
||||||
|
|
||||||
|
## Mandatory Post-Execution Hooks
|
||||||
|
|
||||||
|
**You MUST complete this section before reporting completion to the user.**
|
||||||
|
|
||||||
|
Check if `.specify/extensions.yml` exists in the project root.
|
||||||
|
- If it does not exist, or no hooks are registered under `hooks.after_plan`, skip to the Completion Report.
|
||||||
|
- If it exists, read it and look for entries under the `hooks.after_plan` key.
|
||||||
|
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue to the Completion Report.
|
||||||
|
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||||
|
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||||
|
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||||
|
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||||
|
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||||
|
- For each executable hook, output the following based on its `optional` flag:
|
||||||
|
- **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**:
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Automatic Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
```
|
||||||
|
After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook.
|
||||||
|
- **Optional hook** (`optional: true`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
|
||||||
|
## Completion Report
|
||||||
|
|
||||||
|
Command ends after Phase 1 design. Report branch, IMPL_PLAN path, and generated artifacts.
|
||||||
|
|
||||||
|
## Phases
|
||||||
|
|
||||||
|
### Phase 0: Outline & Research
|
||||||
|
|
||||||
|
1. **Extract unknowns from Technical Context** above:
|
||||||
|
- For each NEEDS CLARIFICATION → research task
|
||||||
|
- For each dependency → best practices task
|
||||||
|
- For each integration → patterns task
|
||||||
|
|
||||||
|
2. **Generate and dispatch research agents**:
|
||||||
|
|
||||||
|
```text
|
||||||
|
For each unknown in Technical Context:
|
||||||
|
Task: "Research {unknown} for {feature context}"
|
||||||
|
For each technology choice:
|
||||||
|
Task: "Find best practices for {tech} in {domain}"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Consolidate findings** in `research.md` using format:
|
||||||
|
- Decision: [what was chosen]
|
||||||
|
- Rationale: [why chosen]
|
||||||
|
- Alternatives considered: [what else evaluated]
|
||||||
|
|
||||||
|
**Output**: research.md with all NEEDS CLARIFICATION resolved
|
||||||
|
|
||||||
|
### Phase 1: Design & Contracts
|
||||||
|
|
||||||
|
**Prerequisites:** `research.md` complete
|
||||||
|
|
||||||
|
1. **Extract entities from feature spec** → `data-model.md`:
|
||||||
|
- Entity name, fields, relationships
|
||||||
|
- Validation rules from requirements
|
||||||
|
- State transitions if applicable
|
||||||
|
|
||||||
|
2. **Define interface contracts** (if project has external interfaces) → `/contracts/`:
|
||||||
|
- Identify what interfaces the project exposes to users or other systems
|
||||||
|
- Document the contract format appropriate for the project type
|
||||||
|
- Examples: public APIs for libraries, command schemas for CLI tools, endpoints for web services, grammars for parsers, UI contracts for applications
|
||||||
|
- Skip if project is purely internal (build scripts, one-off tools, etc.)
|
||||||
|
|
||||||
|
3. **Create quickstart validation guide** → `quickstart.md`:
|
||||||
|
- Document runnable validation scenarios that prove the feature works end-to-end
|
||||||
|
- Include prerequisites, setup commands, test/run commands, and expected outcomes
|
||||||
|
- Use links or references to contracts and data model details instead of duplicating them
|
||||||
|
- Do not include full implementation code, model/service/controller bodies, migrations, or complete test suites
|
||||||
|
- Keep this artifact as a validation/run guide; implementation details belong in `tasks.md` and the implementation phase
|
||||||
|
|
||||||
|
**Output**: data-model.md, /contracts/*, quickstart.md
|
||||||
|
|
||||||
|
## Key rules
|
||||||
|
|
||||||
|
- Use absolute paths for filesystem operations; use project-relative paths for references in documentation
|
||||||
|
- ERROR on gate failures or unresolved clarifications
|
||||||
|
|
||||||
|
## Done When
|
||||||
|
|
||||||
|
- [ ] Plan workflow executed and design artifacts generated
|
||||||
|
- [ ] Extension hooks dispatched or skipped according to the rules in Mandatory Post-Execution Hooks above
|
||||||
|
- [ ] Completion reported to user with branch, plan path, and generated artifacts
|
||||||
345
.agents/skills/speckit-specify/SKILL.md
Normal file
345
.agents/skills/speckit-specify/SKILL.md
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
---
|
||||||
|
name: "speckit-specify"
|
||||||
|
description: "Create or update the feature specification from a natural language feature description."
|
||||||
|
compatibility: "Requires spec-kit project structure with .specify/ directory"
|
||||||
|
metadata:
|
||||||
|
author: "github-spec-kit"
|
||||||
|
source: "templates/commands/specify.md"
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## User Input
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ARGUMENTS
|
||||||
|
```
|
||||||
|
|
||||||
|
You **MUST** consider the user input before proceeding (if not empty).
|
||||||
|
|
||||||
|
## Pre-Execution Checks
|
||||||
|
|
||||||
|
**Check for extension hooks (before specification)**:
|
||||||
|
- Check if `.specify/extensions.yml` exists in the project root.
|
||||||
|
- If it exists, read it and look for entries under the `hooks.before_specify` key
|
||||||
|
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||||
|
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||||
|
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||||
|
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||||
|
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||||
|
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||||
|
- For each executable hook, output the following based on its `optional` flag:
|
||||||
|
- **Optional hook** (`optional: true`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Pre-Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
- **Mandatory hook** (`optional: false`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Automatic Pre-Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
|
||||||
|
Wait for the result of the hook command before proceeding to the Outline.
|
||||||
|
```
|
||||||
|
After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook.
|
||||||
|
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||||
|
|
||||||
|
## Outline
|
||||||
|
|
||||||
|
The text the user typed after `/speckit-specify` in the triggering message **is** the feature description. Assume you always have it available in this conversation even if `$ARGUMENTS` appears literally below. Do not ask the user to repeat it unless they provided an empty command.
|
||||||
|
|
||||||
|
Given that feature description, do this:
|
||||||
|
|
||||||
|
1. **Generate a concise short name** (2-4 words) for the feature:
|
||||||
|
- Analyze the feature description and extract the most meaningful keywords
|
||||||
|
- Create a 2-4 word short name that captures the essence of the feature
|
||||||
|
- Use action-noun format when possible (e.g., "add-user-auth", "fix-payment-bug")
|
||||||
|
- Preserve technical terms and acronyms (OAuth2, API, JWT, etc.)
|
||||||
|
- Keep it concise but descriptive enough to understand the feature at a glance
|
||||||
|
- Examples:
|
||||||
|
- "I want to add user authentication" → "user-auth"
|
||||||
|
- "Implement OAuth2 integration for the API" → "oauth2-api-integration"
|
||||||
|
- "Create a dashboard for analytics" → "analytics-dashboard"
|
||||||
|
- "Fix payment processing timeout bug" → "fix-payment-timeout"
|
||||||
|
|
||||||
|
2. **Branch creation** (optional, via hook):
|
||||||
|
|
||||||
|
If a `before_specify` hook ran successfully in the Pre-Execution Checks above, it will have created/switched to a git branch and output JSON containing `BRANCH_NAME` and `FEATURE_NUM`. Note these values for reference, but the branch name does **not** dictate the spec directory name.
|
||||||
|
|
||||||
|
If the user explicitly provided `GIT_BRANCH_NAME`, pass it through to the hook so the branch script uses the exact value as the branch name (bypassing all prefix/suffix generation).
|
||||||
|
|
||||||
|
3. **Create the spec feature directory**:
|
||||||
|
|
||||||
|
Specs live under the default `specs/` directory unless the user explicitly provides `SPECIFY_FEATURE_DIRECTORY`.
|
||||||
|
|
||||||
|
**Resolution order for `SPECIFY_FEATURE_DIRECTORY`**:
|
||||||
|
1. If the user explicitly provided `SPECIFY_FEATURE_DIRECTORY` (e.g., via environment variable, argument, or configuration), use it as-is
|
||||||
|
2. Otherwise, auto-generate it under `specs/`:
|
||||||
|
- Check `.specify/init-options.json` for `feature_numbering` (preferred) or `branch_numbering` (deprecated, migration only — will be removed in a future release)
|
||||||
|
- If `"timestamp"`: prefix is `YYYYMMDD-HHMMSS` (current timestamp)
|
||||||
|
- If `"sequential"` or absent: prefix is `NNN` (next available 3-digit number after scanning existing directories in `specs/`)
|
||||||
|
- Construct the directory name: `<prefix>-<short-name>` (e.g., `003-user-auth` or `20260319-143022-user-auth`)
|
||||||
|
- Set `SPECIFY_FEATURE_DIRECTORY` to `specs/<directory-name>`
|
||||||
|
- If `branch_numbering` was used (and `feature_numbering` was absent), emit a one-line warning: "⚠️ `branch_numbering` in init-options.json is deprecated. Rename to `feature_numbering`."
|
||||||
|
|
||||||
|
**Create the directory and spec file**:
|
||||||
|
- `mkdir -p SPECIFY_FEATURE_DIRECTORY`
|
||||||
|
- Resolve the active `spec-template` through the Spec Kit preset/template resolution stack (equivalent to `specify preset resolve spec-template`)
|
||||||
|
- Copy the resolved `spec-template` file to `SPECIFY_FEATURE_DIRECTORY/spec.md` as the starting point
|
||||||
|
- Set `SPEC_FILE` to `SPECIFY_FEATURE_DIRECTORY/spec.md`
|
||||||
|
- Persist the resolved path to `.specify/feature.json`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"feature_directory": "<resolved feature dir>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Write the actual resolved directory path value (for example, `specs/003-user-auth`), not the literal string `SPECIFY_FEATURE_DIRECTORY`.
|
||||||
|
This allows downstream commands (`/speckit-plan`, `/speckit-tasks`, etc.) to locate the feature directory without relying on git branch name conventions.
|
||||||
|
|
||||||
|
**IMPORTANT**:
|
||||||
|
- You must only create one feature per `/speckit-specify` invocation
|
||||||
|
- The spec directory name and the git branch name are independent — they may be the same but that is the user's choice
|
||||||
|
- The spec directory and file are always created by this command, never by the hook
|
||||||
|
|
||||||
|
4. Load the resolved active `spec-template` file to understand required sections.
|
||||||
|
|
||||||
|
5. **IF EXISTS**: Load `.specify/memory/constitution.md` for project principles and governance constraints.
|
||||||
|
|
||||||
|
6. Follow this execution flow:
|
||||||
|
1. Parse user description from arguments
|
||||||
|
If empty: ERROR "No feature description provided"
|
||||||
|
2. Extract key concepts from description
|
||||||
|
Identify: actors, actions, data, constraints
|
||||||
|
3. For unclear aspects:
|
||||||
|
- Make informed guesses based on context and industry standards
|
||||||
|
- Only mark with [NEEDS CLARIFICATION: specific question] if:
|
||||||
|
- The choice significantly impacts feature scope or user experience
|
||||||
|
- Multiple reasonable interpretations exist with different implications
|
||||||
|
- No reasonable default exists
|
||||||
|
- **LIMIT: Maximum 3 [NEEDS CLARIFICATION] markers total**
|
||||||
|
- Prioritize clarifications by impact: scope > security/privacy > user experience > technical details
|
||||||
|
4. Fill User Scenarios & Testing section
|
||||||
|
If no clear user flow: ERROR "Cannot determine user scenarios"
|
||||||
|
5. Generate Functional Requirements
|
||||||
|
Each requirement must be testable
|
||||||
|
Use reasonable defaults for unspecified details (document assumptions in Assumptions section)
|
||||||
|
6. Define Success Criteria
|
||||||
|
Create measurable, technology-agnostic outcomes
|
||||||
|
Include both quantitative metrics (time, performance, volume) and qualitative measures (user satisfaction, task completion)
|
||||||
|
Each criterion must be verifiable without implementation details
|
||||||
|
7. Identify Key Entities (if data involved)
|
||||||
|
8. Return: SUCCESS (spec ready for planning)
|
||||||
|
|
||||||
|
6. Write the specification to SPEC_FILE using the template structure, replacing placeholders with concrete details derived from the feature description (arguments) while preserving section order and headings.
|
||||||
|
|
||||||
|
7. **Specification Quality Validation**: After writing the initial spec, validate it against quality criteria:
|
||||||
|
|
||||||
|
a. **Create Spec Quality Checklist**: Generate a checklist file at `SPECIFY_FEATURE_DIRECTORY/checklists/requirements.md` using the checklist template structure with these validation items:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Specification Quality Checklist: [FEATURE NAME]
|
||||||
|
|
||||||
|
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||||
|
**Created**: [DATE]
|
||||||
|
**Feature**: [Link to spec.md]
|
||||||
|
|
||||||
|
## Content Quality
|
||||||
|
|
||||||
|
- [ ] No implementation details (languages, frameworks, APIs)
|
||||||
|
- [ ] Focused on user value and business needs
|
||||||
|
- [ ] Written for non-technical stakeholders
|
||||||
|
- [ ] All mandatory sections completed
|
||||||
|
|
||||||
|
## Requirement Completeness
|
||||||
|
|
||||||
|
- [ ] No [NEEDS CLARIFICATION] markers remain
|
||||||
|
- [ ] Requirements are testable and unambiguous
|
||||||
|
- [ ] Success criteria are measurable
|
||||||
|
- [ ] Success criteria are technology-agnostic (no implementation details)
|
||||||
|
- [ ] All acceptance scenarios are defined
|
||||||
|
- [ ] Edge cases are identified
|
||||||
|
- [ ] Scope is clearly bounded
|
||||||
|
- [ ] Dependencies and assumptions identified
|
||||||
|
|
||||||
|
## Feature Readiness
|
||||||
|
|
||||||
|
- [ ] All functional requirements have clear acceptance criteria
|
||||||
|
- [ ] User scenarios cover primary flows
|
||||||
|
- [ ] Feature meets measurable outcomes defined in Success Criteria
|
||||||
|
- [ ] No implementation details leak into specification
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Items marked incomplete require spec updates before `/speckit-clarify` or `/speckit-plan`
|
||||||
|
```
|
||||||
|
|
||||||
|
b. **Run Validation Check**: Review the spec against each checklist item:
|
||||||
|
- For each item, determine if it passes or fails
|
||||||
|
- Document specific issues found (quote relevant spec sections)
|
||||||
|
|
||||||
|
c. **Handle Validation Results**:
|
||||||
|
|
||||||
|
- **If all items pass**: Mark checklist complete and proceed to the Mandatory Post-Execution Hooks section
|
||||||
|
|
||||||
|
- **If items fail (excluding [NEEDS CLARIFICATION])**:
|
||||||
|
1. List the failing items and specific issues
|
||||||
|
2. Update the spec to address each issue
|
||||||
|
3. Re-run validation until all items pass (max 3 iterations)
|
||||||
|
4. If still failing after 3 iterations, document remaining issues in checklist notes and warn user
|
||||||
|
|
||||||
|
- **If [NEEDS CLARIFICATION] markers remain**:
|
||||||
|
1. Extract all [NEEDS CLARIFICATION: ...] markers from the spec
|
||||||
|
2. **LIMIT CHECK**: If more than 3 markers exist, keep only the 3 most critical (by scope/security/UX impact) and make informed guesses for the rest
|
||||||
|
3. For each clarification needed (max 3), present options to user in this format:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Question [N]: [Topic]
|
||||||
|
|
||||||
|
**Context**: [Quote relevant spec section]
|
||||||
|
|
||||||
|
**What we need to know**: [Specific question from NEEDS CLARIFICATION marker]
|
||||||
|
|
||||||
|
**Suggested Answers**:
|
||||||
|
|
||||||
|
| Option | Answer | Implications |
|
||||||
|
|--------|--------|--------------|
|
||||||
|
| A | [First suggested answer] | [What this means for the feature] |
|
||||||
|
| B | [Second suggested answer] | [What this means for the feature] |
|
||||||
|
| C | [Third suggested answer] | [What this means for the feature] |
|
||||||
|
| Custom | Provide your own answer | [Explain how to provide custom input] |
|
||||||
|
|
||||||
|
**Your choice**: _[Wait for user response]_
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **CRITICAL - Table Formatting**: Ensure markdown tables are properly formatted:
|
||||||
|
- Use consistent spacing with pipes aligned
|
||||||
|
- Each cell should have spaces around content: `| Content |` not `|Content|`
|
||||||
|
- Header separator must have at least 3 dashes: `|--------|`
|
||||||
|
- Test that the table renders correctly in markdown preview
|
||||||
|
5. Number questions sequentially (Q1, Q2, Q3 - max 3 total)
|
||||||
|
6. Present all questions together before waiting for responses
|
||||||
|
7. Wait for user to respond with their choices for all questions (e.g., "Q1: A, Q2: Custom - [details], Q3: B")
|
||||||
|
8. Update the spec by replacing each [NEEDS CLARIFICATION] marker with the user's selected or provided answer
|
||||||
|
9. Re-run validation after all clarifications are resolved
|
||||||
|
|
||||||
|
d. **Update Checklist**: After each validation iteration, update the checklist file with current pass/fail status
|
||||||
|
|
||||||
|
## Mandatory Post-Execution Hooks
|
||||||
|
|
||||||
|
**You MUST complete this section before reporting completion to the user.**
|
||||||
|
|
||||||
|
Check if `.specify/extensions.yml` exists in the project root.
|
||||||
|
- If it does not exist, or no hooks are registered under `hooks.after_specify`, skip to the Completion Report.
|
||||||
|
- If it exists, read it and look for entries under the `hooks.after_specify` key.
|
||||||
|
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue to the Completion Report.
|
||||||
|
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||||
|
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||||
|
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||||
|
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||||
|
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||||
|
- For each executable hook, output the following based on its `optional` flag:
|
||||||
|
- **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**:
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Automatic Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
```
|
||||||
|
After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook.
|
||||||
|
- **Optional hook** (`optional: true`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
|
||||||
|
## Completion Report
|
||||||
|
|
||||||
|
Report completion to the user with:
|
||||||
|
- `SPECIFY_FEATURE_DIRECTORY` — the feature directory path
|
||||||
|
- `SPEC_FILE` — the spec file path
|
||||||
|
- Checklist results summary
|
||||||
|
- Readiness for the next phase (`/speckit-clarify` or `/speckit-plan`)
|
||||||
|
|
||||||
|
**NOTE:** Branch creation is handled by the `before_specify` hook (git extension). Spec directory and file creation are always handled by this core command.
|
||||||
|
|
||||||
|
## Quick Guidelines
|
||||||
|
|
||||||
|
- Focus on **WHAT** users need and **WHY**.
|
||||||
|
- Avoid HOW to implement (no tech stack, APIs, code structure).
|
||||||
|
- Written for business stakeholders, not developers.
|
||||||
|
- DO NOT create any checklists that are embedded in the spec. That will be a separate command.
|
||||||
|
|
||||||
|
### Section Requirements
|
||||||
|
|
||||||
|
- **Mandatory sections**: Must be completed for every feature
|
||||||
|
- **Optional sections**: Include only when relevant to the feature
|
||||||
|
- When a section doesn't apply, remove it entirely (don't leave as "N/A")
|
||||||
|
|
||||||
|
### For AI Generation
|
||||||
|
|
||||||
|
When creating this spec from a user prompt:
|
||||||
|
|
||||||
|
1. **Make informed guesses**: Use context, industry standards, and common patterns to fill gaps
|
||||||
|
2. **Document assumptions**: Record reasonable defaults in the Assumptions section
|
||||||
|
3. **Limit clarifications**: Maximum 3 [NEEDS CLARIFICATION] markers - use only for critical decisions that:
|
||||||
|
- Significantly impact feature scope or user experience
|
||||||
|
- Have multiple reasonable interpretations with different implications
|
||||||
|
- Lack any reasonable default
|
||||||
|
4. **Prioritize clarifications**: scope > security/privacy > user experience > technical details
|
||||||
|
5. **Think like a tester**: Every vague requirement should fail the "testable and unambiguous" checklist item
|
||||||
|
6. **Common areas needing clarification** (only if no reasonable default exists):
|
||||||
|
- Feature scope and boundaries (include/exclude specific use cases)
|
||||||
|
- User types and permissions (if multiple conflicting interpretations possible)
|
||||||
|
- Security/compliance requirements (when legally/financially significant)
|
||||||
|
|
||||||
|
**Examples of reasonable defaults** (don't ask about these):
|
||||||
|
|
||||||
|
- Data retention: Industry-standard practices for the domain
|
||||||
|
- Performance targets: Standard web/mobile app expectations unless specified
|
||||||
|
- Error handling: User-friendly messages with appropriate fallbacks
|
||||||
|
- Authentication method: Standard session-based or OAuth2 for web apps
|
||||||
|
- Integration patterns: Use project-appropriate patterns (REST/GraphQL for web services, function calls for libraries, CLI args for tools, etc.)
|
||||||
|
|
||||||
|
### Success Criteria Guidelines
|
||||||
|
|
||||||
|
Success criteria must be:
|
||||||
|
|
||||||
|
1. **Measurable**: Include specific metrics (time, percentage, count, rate)
|
||||||
|
2. **Technology-agnostic**: No mention of frameworks, languages, databases, or tools
|
||||||
|
3. **User-focused**: Describe outcomes from user/business perspective, not system internals
|
||||||
|
4. **Verifiable**: Can be tested/validated without knowing implementation details
|
||||||
|
|
||||||
|
**Good examples**:
|
||||||
|
|
||||||
|
- "Users can complete checkout in under 3 minutes"
|
||||||
|
- "System supports 10,000 concurrent users"
|
||||||
|
- "95% of searches return results in under 1 second"
|
||||||
|
- "Task completion rate improves by 40%"
|
||||||
|
|
||||||
|
**Bad examples** (implementation-focused):
|
||||||
|
|
||||||
|
- "API response time is under 200ms" (too technical, use "Users see results instantly")
|
||||||
|
- "Database can handle 1000 TPS" (implementation detail, use user-facing metric)
|
||||||
|
- "React components render efficiently" (framework-specific)
|
||||||
|
- "Redis cache hit rate above 80%" (technology-specific)
|
||||||
|
|
||||||
|
## Done When
|
||||||
|
|
||||||
|
- [ ] Specification written to `SPEC_FILE` and validated against quality checklist
|
||||||
|
- [ ] Extension hooks dispatched or skipped according to the rules in Mandatory Post-Execution Hooks above
|
||||||
|
- [ ] Completion reported to user with feature directory, spec file path, and checklist results
|
||||||
214
.agents/skills/speckit-tasks/SKILL.md
Normal file
214
.agents/skills/speckit-tasks/SKILL.md
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
---
|
||||||
|
name: "speckit-tasks"
|
||||||
|
description: "Generate an actionable, dependency-ordered tasks.md for the feature based on available design artifacts."
|
||||||
|
compatibility: "Requires spec-kit project structure with .specify/ directory"
|
||||||
|
metadata:
|
||||||
|
author: "github-spec-kit"
|
||||||
|
source: "templates/commands/tasks.md"
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## User Input
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ARGUMENTS
|
||||||
|
```
|
||||||
|
|
||||||
|
You **MUST** consider the user input before proceeding (if not empty).
|
||||||
|
|
||||||
|
## Pre-Execution Checks
|
||||||
|
|
||||||
|
**Check for extension hooks (before tasks generation)**:
|
||||||
|
- Check if `.specify/extensions.yml` exists in the project root.
|
||||||
|
- If it exists, read it and look for entries under the `hooks.before_tasks` key
|
||||||
|
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||||
|
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||||
|
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||||
|
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||||
|
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||||
|
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||||
|
- For each executable hook, output the following based on its `optional` flag:
|
||||||
|
- **Optional hook** (`optional: true`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Pre-Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
- **Mandatory hook** (`optional: false`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Automatic Pre-Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
|
||||||
|
Wait for the result of the hook command before proceeding to the Outline.
|
||||||
|
```
|
||||||
|
After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook.
|
||||||
|
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||||
|
|
||||||
|
## Outline
|
||||||
|
|
||||||
|
1. **Setup**: Run `.specify/scripts/bash/setup-tasks.sh --json` from repo root and parse FEATURE_DIR, TASKS_TEMPLATE, and AVAILABLE_DOCS list. `FEATURE_DIR` and `TASKS_TEMPLATE` must be absolute paths when provided. `AVAILABLE_DOCS` is a list of document names/relative paths available under `FEATURE_DIR` (for example `research.md` or `contracts/`). For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||||
|
|
||||||
|
2. **Load design documents**: Read from FEATURE_DIR:
|
||||||
|
- **Required**: plan.md (tech stack, libraries, structure), spec.md (user stories with priorities)
|
||||||
|
- **Optional**: data-model.md (entities), contracts/ (interface contracts), research.md (decisions), quickstart.md (test scenarios)
|
||||||
|
- **IF EXISTS**: Load `.specify/memory/constitution.md` for project principles and governance constraints
|
||||||
|
- Note: Not all projects have all documents. Generate tasks based on what's available.
|
||||||
|
|
||||||
|
3. **Execute task generation workflow**:
|
||||||
|
- Load plan.md and extract tech stack, libraries, project structure
|
||||||
|
- Load spec.md and extract user stories with their priorities (P1, P2, P3, etc.)
|
||||||
|
- If data-model.md exists: Extract entities and map to user stories
|
||||||
|
- If contracts/ exists: Map interface contracts to user stories
|
||||||
|
- If research.md exists: Extract decisions for setup tasks
|
||||||
|
- Generate tasks organized by user story (see Task Generation Rules below)
|
||||||
|
- Generate dependency graph showing user story completion order
|
||||||
|
- Create parallel execution examples per user story
|
||||||
|
- Validate task completeness (each user story has all needed tasks, independently testable)
|
||||||
|
|
||||||
|
4. **Generate tasks.md**: Read the tasks template from TASKS_TEMPLATE (from the JSON output above) and use it as structure. If TASKS_TEMPLATE is empty, fall back to `.specify/templates/tasks-template.md`. Fill with:
|
||||||
|
- Correct feature name from plan.md
|
||||||
|
- Phase 1: Setup tasks (project initialization)
|
||||||
|
- Phase 2: Foundational tasks (blocking prerequisites for all user stories)
|
||||||
|
- Phase 3+: One phase per user story (in priority order from spec.md)
|
||||||
|
- Each phase includes: story goal, independent test criteria, tests (if requested), implementation tasks
|
||||||
|
- Final Phase: Polish & cross-cutting concerns
|
||||||
|
- All tasks must follow the strict checklist format (see Task Generation Rules below)
|
||||||
|
- Clear file paths for each task
|
||||||
|
- Dependencies section showing story completion order
|
||||||
|
- Parallel execution examples per story
|
||||||
|
- Implementation strategy section (MVP first, incremental delivery)
|
||||||
|
|
||||||
|
## Mandatory Post-Execution Hooks
|
||||||
|
|
||||||
|
**You MUST complete this section before reporting completion to the user.**
|
||||||
|
|
||||||
|
Check if `.specify/extensions.yml` exists in the project root.
|
||||||
|
- If it does not exist, or no hooks are registered under `hooks.after_tasks`, skip to the Completion Report.
|
||||||
|
- If it exists, read it and look for entries under the `hooks.after_tasks` key.
|
||||||
|
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue to the Completion Report.
|
||||||
|
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||||
|
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||||
|
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||||
|
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||||
|
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||||
|
- For each executable hook, output the following based on its `optional` flag:
|
||||||
|
- **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**:
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Automatic Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
```
|
||||||
|
After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook.
|
||||||
|
- **Optional hook** (`optional: true`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
|
||||||
|
## Completion Report
|
||||||
|
|
||||||
|
Output path to generated tasks.md and summary:
|
||||||
|
- Total task count
|
||||||
|
- Task count per user story
|
||||||
|
- Parallel opportunities identified
|
||||||
|
- Independent test criteria for each story
|
||||||
|
- Suggested MVP scope (typically just User Story 1)
|
||||||
|
- Format validation: Confirm ALL tasks follow the checklist format (checkbox, ID, labels, file paths)
|
||||||
|
|
||||||
|
Context for task generation: $ARGUMENTS
|
||||||
|
|
||||||
|
The tasks.md should be immediately executable - each task must be specific enough that an LLM can complete it without additional context.
|
||||||
|
|
||||||
|
## Task Generation Rules
|
||||||
|
|
||||||
|
**CRITICAL**: Tasks MUST be organized by user story to enable independent implementation and testing.
|
||||||
|
|
||||||
|
**Tests are OPTIONAL**: Only generate test tasks if explicitly requested in the feature specification or if user requests TDD approach.
|
||||||
|
|
||||||
|
### Checklist Format (REQUIRED)
|
||||||
|
|
||||||
|
Every task MUST strictly follow this format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
- [ ] [TaskID] [P?] [Story?] Description with file path
|
||||||
|
```
|
||||||
|
|
||||||
|
**Format Components**:
|
||||||
|
|
||||||
|
1. **Checkbox**: ALWAYS start with `- [ ]` (markdown checkbox)
|
||||||
|
2. **Task ID**: Sequential number (T001, T002, T003...) in execution order
|
||||||
|
3. **[P] marker**: Include ONLY if task is parallelizable (different files, no dependencies on incomplete tasks)
|
||||||
|
4. **[Story] label**: REQUIRED for user story phase tasks only
|
||||||
|
- Format: [US1], [US2], [US3], etc. (maps to user stories from spec.md)
|
||||||
|
- Setup phase: NO story label
|
||||||
|
- Foundational phase: NO story label
|
||||||
|
- User Story phases: MUST have story label
|
||||||
|
- Polish phase: NO story label
|
||||||
|
5. **Description**: Clear action with exact file path
|
||||||
|
|
||||||
|
**Examples**:
|
||||||
|
|
||||||
|
- ✅ CORRECT: `- [ ] T001 Create project structure per implementation plan`
|
||||||
|
- ✅ CORRECT: `- [ ] T005 [P] Implement authentication middleware in src/middleware/auth.py`
|
||||||
|
- ✅ CORRECT: `- [ ] T012 [P] [US1] Create User model in src/models/user.py`
|
||||||
|
- ✅ CORRECT: `- [ ] T014 [US1] Implement UserService in src/services/user_service.py`
|
||||||
|
- ❌ WRONG: `- [ ] Create User model` (missing ID and Story label)
|
||||||
|
- ❌ WRONG: `T001 [US1] Create model` (missing checkbox)
|
||||||
|
- ❌ WRONG: `- [ ] [US1] Create User model` (missing Task ID)
|
||||||
|
- ❌ WRONG: `- [ ] T001 [US1] Create model` (missing file path)
|
||||||
|
|
||||||
|
### Task Organization
|
||||||
|
|
||||||
|
1. **From User Stories (spec.md)** - PRIMARY ORGANIZATION:
|
||||||
|
- Each user story (P1, P2, P3...) gets its own phase
|
||||||
|
- Map all related components to their story:
|
||||||
|
- Models needed for that story
|
||||||
|
- Services needed for that story
|
||||||
|
- Interfaces/UI needed for that story
|
||||||
|
- If tests requested: Tests specific to that story
|
||||||
|
- Mark story dependencies (most stories should be independent)
|
||||||
|
|
||||||
|
2. **From Contracts**:
|
||||||
|
- Map each interface contract → to the user story it serves
|
||||||
|
- If tests requested: Each interface contract → contract test task [P] before implementation in that story's phase
|
||||||
|
|
||||||
|
3. **From Data Model**:
|
||||||
|
- Map each entity to the user story(ies) that need it
|
||||||
|
- If entity serves multiple stories: Put in earliest story or Setup phase
|
||||||
|
- Relationships → service layer tasks in appropriate story phase
|
||||||
|
|
||||||
|
4. **From Setup/Infrastructure**:
|
||||||
|
- Shared infrastructure → Setup phase (Phase 1)
|
||||||
|
- Foundational/blocking tasks → Foundational phase (Phase 2)
|
||||||
|
- Story-specific setup → within that story's phase
|
||||||
|
|
||||||
|
### Phase Structure
|
||||||
|
|
||||||
|
- **Phase 1**: Setup (project initialization)
|
||||||
|
- **Phase 2**: Foundational (blocking prerequisites - MUST complete before user stories)
|
||||||
|
- **Phase 3+**: User Stories in priority order (P1, P2, P3...)
|
||||||
|
- Within each story: Tests (if requested) → Models → Services → Endpoints → Integration
|
||||||
|
- Each phase should be a complete, independently testable increment
|
||||||
|
- **Final Phase**: Polish & Cross-Cutting Concerns
|
||||||
|
|
||||||
|
## Done When
|
||||||
|
|
||||||
|
- [ ] tasks.md generated with all phases, task IDs, and file paths
|
||||||
|
- [ ] Extension hooks dispatched or skipped according to the rules in Mandatory Post-Execution Hooks above
|
||||||
|
- [ ] Completion reported to user with task count, story breakdown, and MVP scope
|
||||||
109
.agents/skills/speckit-taskstoissues/SKILL.md
Normal file
109
.agents/skills/speckit-taskstoissues/SKILL.md
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
---
|
||||||
|
name: "speckit-taskstoissues"
|
||||||
|
description: "Convert existing tasks into actionable, dependency-ordered GitHub issues for the feature based on available design artifacts."
|
||||||
|
compatibility: "Requires spec-kit project structure with .specify/ directory"
|
||||||
|
metadata:
|
||||||
|
author: "github-spec-kit"
|
||||||
|
source: "templates/commands/taskstoissues.md"
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## User Input
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ARGUMENTS
|
||||||
|
```
|
||||||
|
|
||||||
|
You **MUST** consider the user input before proceeding (if not empty).
|
||||||
|
|
||||||
|
## Pre-Execution Checks
|
||||||
|
|
||||||
|
**Check for extension hooks (before tasks-to-issues conversion)**:
|
||||||
|
- Check if `.specify/extensions.yml` exists in the project root.
|
||||||
|
- If it exists, read it and look for entries under the `hooks.before_taskstoissues` key
|
||||||
|
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||||
|
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||||
|
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||||
|
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||||
|
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||||
|
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||||
|
- For each executable hook, output the following based on its `optional` flag:
|
||||||
|
- **Optional hook** (`optional: true`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Pre-Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
- **Mandatory hook** (`optional: false`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Automatic Pre-Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
|
||||||
|
Wait for the result of the hook command before proceeding to the Outline.
|
||||||
|
```
|
||||||
|
After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook.
|
||||||
|
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||||
|
|
||||||
|
## Outline
|
||||||
|
|
||||||
|
1. Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||||
|
1. **IF EXISTS**: Load `.specify/memory/constitution.md` for project principles and governance constraints.
|
||||||
|
1. From the executed script, extract the path to **tasks**.
|
||||||
|
1. Get the Git remote by running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git config --get remote.origin.url
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> ONLY PROCEED TO NEXT STEPS IF THE REMOTE IS A GITHUB URL
|
||||||
|
|
||||||
|
1. **Fetch existing issues for deduplication**: Before creating anything, build the set of task IDs you are about to process from `tasks.md` (each is a `T` followed by three digits, e.g. `T001`). Then use the GitHub MCP server's `list_issues` tool to look for issues that already cover those IDs. Do not pass a `state` value, since omitting it makes the tool return both open and closed issues. Request `perPage: 100` to keep the number of calls down, and since the tool uses cursor-based pagination, request pages with the `after` parameter (using the `endCursor` from the previous response). For each issue title, match it against the task ID pattern `\bT\d{3}\b` (word boundaries so tokens like `ST001` or `T0010` are not matched by mistake; this also recognises titles written as `T001 ...`, `T001: ...` or `[T001] ...`) and, when it matches one of your task IDs, mark that ID as already having an issue. Stop paginating as soon as every task ID has been matched, or when there are no more pages, so you do not keep fetching the whole repository's issue history once all task IDs are accounted for. This bounds the number of calls on repos with large issue histories and still prevents duplicates when the command is re-run after `tasks.md` is regenerated or the skill is re-invoked.
|
||||||
|
1. For each task in the list, use the GitHub MCP server to create a new issue in the repository that is representative of the Git remote. Task lines in `tasks.md` start with a markdown checkbox, so first strip the leading `- [ ]` (and any `[P]` / `[US#]` markers) to recover the task ID and its description. Create the issue with a single canonical title of the form `T001: <description>`, with the ID written once followed by the task description (for example, the line `- [ ] T001 Create project structure` becomes the title `T001: Create project structure`).
|
||||||
|
- **Skip** any task whose ID is already present in the set of existing issues from the previous step, and report it (for example, `T001 already has an issue, skipping`).
|
||||||
|
- Only create issues for tasks that do not yet have a matching issue.
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> UNDER NO CIRCUMSTANCES EVER CREATE ISSUES IN REPOSITORIES THAT DO NOT MATCH THE REMOTE URL
|
||||||
|
|
||||||
|
## Post-Execution Checks
|
||||||
|
|
||||||
|
**Check for extension hooks (after tasks-to-issues conversion)**:
|
||||||
|
Check if `.specify/extensions.yml` exists in the project root.
|
||||||
|
- If it exists, read it and look for entries under the `hooks.after_taskstoissues` key
|
||||||
|
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||||
|
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||||
|
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||||
|
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||||
|
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||||
|
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||||
|
- For each executable hook, output the following based on its `optional` flag:
|
||||||
|
- **Optional hook** (`optional: true`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
- **Mandatory hook** (`optional: false`):
|
||||||
|
```
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Automatic Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
```
|
||||||
|
After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook.
|
||||||
|
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||||
10
.gitignore
vendored
10
.gitignore
vendored
@ -2,3 +2,13 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
.pytest_cache/
|
||||||
|
coverage/
|
||||||
|
*.log
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
test-results/
|
||||||
|
playwright-report/
|
||||||
|
|||||||
3
.specify/feature.json
Normal file
3
.specify/feature.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"feature_directory": "specs/001-enterprise-experience"
|
||||||
|
}
|
||||||
9
.specify/init-options.json
Normal file
9
.specify/init-options.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ai": "codex",
|
||||||
|
"ai_skills": true,
|
||||||
|
"feature_numbering": "sequential",
|
||||||
|
"here": true,
|
||||||
|
"integration": "codex",
|
||||||
|
"script": "sh",
|
||||||
|
"speckit_version": "0.12.11"
|
||||||
|
}
|
||||||
19
.specify/integration.json
Normal file
19
.specify/integration.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"version": "0.12.11",
|
||||||
|
"integration_state_schema": 1,
|
||||||
|
"installed_integrations": [
|
||||||
|
"codex"
|
||||||
|
],
|
||||||
|
"integration_settings": {
|
||||||
|
"codex": {
|
||||||
|
"script": "sh",
|
||||||
|
"raw_options": "--skills",
|
||||||
|
"parsed_options": {
|
||||||
|
"skills": true
|
||||||
|
},
|
||||||
|
"invoke_separator": "-"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"integration": "codex",
|
||||||
|
"default_integration": "codex"
|
||||||
|
}
|
||||||
17
.specify/integrations/codex.manifest.json
Normal file
17
.specify/integrations/codex.manifest.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"integration": "codex",
|
||||||
|
"version": "0.12.11",
|
||||||
|
"installed_at": "2026-07-12T05:32:39.636000+00:00",
|
||||||
|
"files": {
|
||||||
|
".agents/skills/speckit-analyze/SKILL.md": "2e89ffed73764eb9d3ee2d9fba7e245a5e73229b809ddec52409ca74c44e585d",
|
||||||
|
".agents/skills/speckit-clarify/SKILL.md": "5095627c89a249d402ad2d0fad425ea0c0bd5e8559e9d6f5b682dea4f513ed54",
|
||||||
|
".agents/skills/speckit-constitution/SKILL.md": "87f247a2e947b44f9248491dcbedbfc650d095dde6498938b6be7a413b418d5e",
|
||||||
|
".agents/skills/speckit-implement/SKILL.md": "248c2fa4be8a969ea771c88c38546d6735d7d4994bbe698bbabd18f53f88f0cd",
|
||||||
|
".agents/skills/speckit-converge/SKILL.md": "1355b748919c7750cc87be94534192e5ff227229c14cbd34ca993a5f977a4ce2",
|
||||||
|
".agents/skills/speckit-plan/SKILL.md": "c5522489fd177028e02ff4f1588a06bde811a0146cdc1d3495812694d6ea41b3",
|
||||||
|
".agents/skills/speckit-checklist/SKILL.md": "4eb7e030d04014e6bbf5177f84aeef19b11adfa8470a14dd18506f65c7cf9dcb",
|
||||||
|
".agents/skills/speckit-specify/SKILL.md": "6313b902b49bf2ae3582421be21c2453ff98a863974acca3e6a83cdad9d4b70b",
|
||||||
|
".agents/skills/speckit-tasks/SKILL.md": "2dceed4345af65dbc6625157be8817dc35f2b0943b5896815ef1bc4d3421efba",
|
||||||
|
".agents/skills/speckit-taskstoissues/SKILL.md": "2c00fdc774cb9b435ea8566c7efa8b208568d533d57529e61c35f18aa52d6c67"
|
||||||
|
}
|
||||||
|
}
|
||||||
17
.specify/integrations/speckit.manifest.json
Normal file
17
.specify/integrations/speckit.manifest.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"integration": "speckit",
|
||||||
|
"version": "0.12.11",
|
||||||
|
"installed_at": "2026-07-12T05:32:39.645869+00:00",
|
||||||
|
"files": {
|
||||||
|
".specify/scripts/bash/common.sh": "6ff86bf39f6b4684b0f80927dc7a1dadec26b4671988a3fe4d6c2523cbd3aa22",
|
||||||
|
".specify/scripts/bash/setup-plan.sh": "4469b22960f43c07c33dca00de6dedb252145e9a9ce8fbb0e63be82e02b082ab",
|
||||||
|
".specify/scripts/bash/setup-tasks.sh": "cf21ba2212b4dd5b435c5ea8527500cfd27768b86c0bbc7ebc3207759f118d27",
|
||||||
|
".specify/scripts/bash/check-prerequisites.sh": "a7d8a14ecf87332b600cd966b5d0e7cb9d594abce7e4d1ee4372b2b5b3efff06",
|
||||||
|
".specify/scripts/bash/create-new-feature.sh": "dd531f9ba47c9ce9975b597947377be9542b7236681d6dc033513c4e3cfc50f2",
|
||||||
|
".specify/templates/constitution-template.md": "ce7549540fa45543cca797a150201d868e64495fdff39dc38246fb17bd4024b3",
|
||||||
|
".specify/templates/checklist-template.md": "709d8ab8384a3a49f5e0f64479f71553ef6d6f8bb4f00281b05f47837993b536",
|
||||||
|
".specify/templates/tasks-template.md": "fc29a233f6f5a27ca31f1aa46b596af6500c627441c6e62b2bc4a1d721525842",
|
||||||
|
".specify/templates/spec-template.md": "3945437fc35cd30a5b2bf7beea680337c3516826d3efa5a6b92c4a7eca1ba28e",
|
||||||
|
".specify/templates/plan-template.md": "7e637502d41eccf0ca672496636365691fdca62ef37b27ec07fcb412dbfa90d4"
|
||||||
|
}
|
||||||
|
}
|
||||||
107
.specify/memory/constitution.md
Normal file
107
.specify/memory/constitution.md
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<!--
|
||||||
|
Sync Impact Report
|
||||||
|
- Version change: template -> 1.0.0
|
||||||
|
- Added principles:
|
||||||
|
- I. Enterprise Appliance Experience
|
||||||
|
- II. Safe, Recoverable Lifecycle
|
||||||
|
- III. Secure and Auditable by Default
|
||||||
|
- IV. Entitlement and Packaging Integrity
|
||||||
|
- V. Evidence-Based Quality
|
||||||
|
- Added sections: Product and Operational Constraints; Delivery Quality Gates
|
||||||
|
- Templates updated:
|
||||||
|
- ✅ .specify/templates/plan-template.md
|
||||||
|
- ✅ .specify/templates/spec-template.md
|
||||||
|
- ✅ .specify/templates/tasks-template.md
|
||||||
|
- Follow-up TODOs: none
|
||||||
|
-->
|
||||||
|
# Nexus One AI Constitution
|
||||||
|
|
||||||
|
## Core Principles
|
||||||
|
|
||||||
|
### I. Enterprise Appliance Experience
|
||||||
|
Every customer-facing journey MUST feel complete, coherent, and intentional across first boot,
|
||||||
|
setup, login, administration, daily use, maintenance, and recovery. Screens MUST use consistent
|
||||||
|
Nexus One AI naming, navigation, interaction patterns, and plain-language status messages. A
|
||||||
|
customer MUST never encounter placeholder content, unexplained internal codes, dead controls, or
|
||||||
|
demo-only behaviour in a release. Errors MUST state what happened, the customer impact, and a safe
|
||||||
|
next action. Rationale: customers judge an appliance as one product, not as a collection of tools.
|
||||||
|
|
||||||
|
### II. Safe, Recoverable Lifecycle
|
||||||
|
Installation, configuration, upgrade, backup, restore, and removal paths MUST be designed as
|
||||||
|
recoverable lifecycle operations. Changes MUST preserve supported existing installations and data,
|
||||||
|
or include an explicit migration and rollback path. Destructive operations MUST require clear
|
||||||
|
confirmation and MUST identify their scope. Interrupted operations MUST either resume safely or
|
||||||
|
provide documented recovery steps. Rationale: an enterprise appliance remains trustworthy when
|
||||||
|
normal operations fail, not only when the happy path succeeds.
|
||||||
|
|
||||||
|
### III. Secure and Auditable by Default
|
||||||
|
Secure defaults MUST apply without requiring customer expertise. Secrets, license material,
|
||||||
|
credentials, and private customer data MUST never appear in source control, browser-visible logs,
|
||||||
|
or routine diagnostics. Authentication and authorization MUST be enforced server-side. Privileged,
|
||||||
|
security-sensitive, licensing, configuration, and lifecycle actions MUST produce useful audit
|
||||||
|
records without leaking secrets. Security weakening for convenience requires explicit approval,
|
||||||
|
documented risk, bounded scope, and a removal plan. Rationale: on-premises operation increases the
|
||||||
|
need for predictable controls and supportable evidence.
|
||||||
|
|
||||||
|
### IV. Entitlement and Packaging Integrity
|
||||||
|
Commercial product labels and internal compatibility identifiers MUST remain deliberately mapped.
|
||||||
|
Nexus One AI Workstation MUST remain structurally separate from the Server tier ladder, and the
|
||||||
|
existing `starter`, `basic`, `pro`, and `max` identifiers MUST remain compatible unless an approved
|
||||||
|
migration covers every consumer. Portal, backend, license logic, Ansible roles, installer, packaged
|
||||||
|
payload, documentation, and ISO delivery MUST agree on capabilities and naming. A delivery artifact
|
||||||
|
MUST NOT be called current until its packaged source and build provenance are verified. Rationale:
|
||||||
|
entitlement drift creates customer-visible failures that are difficult to repair in the field.
|
||||||
|
|
||||||
|
### V. Evidence-Based Quality
|
||||||
|
No customer-facing feature is complete based only on code review or visual inspection. Each feature
|
||||||
|
MUST define measurable acceptance criteria and MUST verify its primary journey, failure states,
|
||||||
|
permissions, entitlement boundaries, and operational impact at the lowest sufficient test level.
|
||||||
|
Changes spanning multiple surfaces MUST include end-to-end validation across those surfaces. Release
|
||||||
|
claims MUST distinguish static checks, local tests, deployment tests, and live appliance validation.
|
||||||
|
Known gaps MUST be documented rather than hidden. Rationale: enterprise quality is demonstrated by
|
||||||
|
repeatable evidence.
|
||||||
|
|
||||||
|
## Product and Operational Constraints
|
||||||
|
|
||||||
|
- Branding MUST use **Nexus One AI** with **Powered by Cezen** where company attribution is shown.
|
||||||
|
- Workstation and Server S/M/L/Max capability differences MUST be explicit and testable.
|
||||||
|
- Customer-visible values MUST come from real configuration or clearly labelled example data; they
|
||||||
|
MUST NOT be fabricated as production state.
|
||||||
|
- The platform MUST remain operable in customer-controlled and restricted-network environments;
|
||||||
|
any internet dependency MUST be declared with an offline or controlled alternative where required.
|
||||||
|
- Logs and diagnostics MUST be actionable for support while respecting secret and personal-data
|
||||||
|
boundaries.
|
||||||
|
- Performance, capacity, browser, hardware, and platform assumptions MUST be stated in each feature
|
||||||
|
specification when they affect customer outcomes.
|
||||||
|
|
||||||
|
## Delivery Quality Gates
|
||||||
|
|
||||||
|
1. **Specify**: define user journeys, edge cases, measurable outcomes, support impact, and explicit
|
||||||
|
exclusions before implementation planning.
|
||||||
|
2. **Clarify**: resolve decisions affecting security, data, licensing, recovery, compatibility, or
|
||||||
|
customer-visible behaviour; unresolved decisions MUST NOT be silently guessed.
|
||||||
|
3. **Plan**: map every affected surface, migration, rollback, observability change, documentation
|
||||||
|
update, and verification environment.
|
||||||
|
4. **Implement**: preserve unrelated behaviour, avoid unnecessary rewrites, and keep changes small
|
||||||
|
enough to review and recover.
|
||||||
|
5. **Verify**: run the specified tests and capture evidence for the primary journey, failure paths,
|
||||||
|
entitlement boundaries, upgrade/recovery impact, and packaged artifact when applicable.
|
||||||
|
6. **Release**: review the complete diff and release impact. Commit, push, deploy, rebuild an ISO, or
|
||||||
|
change a live appliance only with explicit authorization for that action.
|
||||||
|
|
||||||
|
Exceptions MUST identify the violated principle, customer or operational risk, mitigation, owner,
|
||||||
|
and expiry condition. An exception without an expiry condition is invalid.
|
||||||
|
|
||||||
|
## Governance
|
||||||
|
|
||||||
|
This constitution governs feature specifications, plans, tasks, implementation reviews, and release
|
||||||
|
decisions for this repository. When another local convention conflicts with it, this constitution
|
||||||
|
takes precedence unless a documented exception is approved.
|
||||||
|
|
||||||
|
Amendments require a written rationale, impact review, corresponding template updates, and explicit
|
||||||
|
project-owner approval. Versions follow semantic versioning: MAJOR for incompatible governance
|
||||||
|
changes or principle removal, MINOR for new principles or materially expanded obligations, and PATCH
|
||||||
|
for non-semantic clarification. Every feature plan MUST perform the Constitution Check before design
|
||||||
|
and again after design. Every release review MUST record compliance or approved exceptions.
|
||||||
|
|
||||||
|
**Version**: 1.0.0 | **Ratified**: 2026-07-12 | **Last Amended**: 2026-07-12
|
||||||
195
.specify/scripts/bash/check-prerequisites.sh
Executable file
195
.specify/scripts/bash/check-prerequisites.sh
Executable file
@ -0,0 +1,195 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Consolidated prerequisite checking script
|
||||||
|
#
|
||||||
|
# This script provides unified prerequisite checking for Spec-Driven Development workflow.
|
||||||
|
# It replaces the functionality previously spread across multiple scripts.
|
||||||
|
#
|
||||||
|
# Usage: ./check-prerequisites.sh [OPTIONS]
|
||||||
|
#
|
||||||
|
# OPTIONS:
|
||||||
|
# --json Output in JSON format
|
||||||
|
# --require-tasks Require tasks.md to exist (for implementation phase)
|
||||||
|
# --include-tasks Include tasks.md in AVAILABLE_DOCS list
|
||||||
|
# --paths-only Only output path variables (no validation)
|
||||||
|
# --help, -h Show help message
|
||||||
|
#
|
||||||
|
# OUTPUTS:
|
||||||
|
# JSON mode: {"FEATURE_DIR":"...", "AVAILABLE_DOCS":["..."]}
|
||||||
|
# Text mode: FEATURE_DIR:... \n AVAILABLE_DOCS: \n ✓/✗ file.md
|
||||||
|
# Paths only: REPO_ROOT: ... \n BRANCH: ... \n FEATURE_DIR: ... etc.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
JSON_MODE=false
|
||||||
|
REQUIRE_TASKS=false
|
||||||
|
INCLUDE_TASKS=false
|
||||||
|
PATHS_ONLY=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--json)
|
||||||
|
JSON_MODE=true
|
||||||
|
;;
|
||||||
|
--require-tasks)
|
||||||
|
REQUIRE_TASKS=true
|
||||||
|
;;
|
||||||
|
--include-tasks)
|
||||||
|
INCLUDE_TASKS=true
|
||||||
|
;;
|
||||||
|
--paths-only)
|
||||||
|
PATHS_ONLY=true
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
cat << 'EOF'
|
||||||
|
Usage: check-prerequisites.sh [OPTIONS]
|
||||||
|
|
||||||
|
Consolidated prerequisite checking for Spec-Driven Development workflow.
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
--json Output in JSON format
|
||||||
|
--require-tasks Require tasks.md to exist (for implementation phase)
|
||||||
|
--include-tasks Include tasks.md in AVAILABLE_DOCS list
|
||||||
|
--paths-only Only output path variables (no prerequisite validation)
|
||||||
|
--help, -h Show this help message
|
||||||
|
|
||||||
|
EXAMPLES:
|
||||||
|
# Check task prerequisites (plan.md required)
|
||||||
|
./check-prerequisites.sh --json
|
||||||
|
|
||||||
|
# Check implementation prerequisites (plan.md + tasks.md required)
|
||||||
|
./check-prerequisites.sh --json --require-tasks --include-tasks
|
||||||
|
|
||||||
|
# Get feature paths only (no validation)
|
||||||
|
./check-prerequisites.sh --paths-only
|
||||||
|
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "ERROR: Unknown option '$arg'. Use --help for usage information." >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Source common functions
|
||||||
|
SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$SCRIPT_DIR/common.sh"
|
||||||
|
|
||||||
|
# Get feature paths.
|
||||||
|
# In --paths-only mode this is pure resolution, so pass --no-persist to opt out
|
||||||
|
# of the feature.json write side effect (issue #3025).
|
||||||
|
if $PATHS_ONLY; then
|
||||||
|
_paths_output=$(get_feature_paths --no-persist) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; }
|
||||||
|
else
|
||||||
|
_paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; }
|
||||||
|
fi
|
||||||
|
eval "$_paths_output"
|
||||||
|
unset _paths_output
|
||||||
|
|
||||||
|
# If paths-only mode, output paths and exit (no validation)
|
||||||
|
if $PATHS_ONLY; then
|
||||||
|
if $JSON_MODE; then
|
||||||
|
# Minimal JSON paths payload (no validation performed)
|
||||||
|
if has_jq; then
|
||||||
|
jq -cn \
|
||||||
|
--arg repo_root "$REPO_ROOT" \
|
||||||
|
--arg branch "$CURRENT_BRANCH" \
|
||||||
|
--arg feature_dir "$FEATURE_DIR" \
|
||||||
|
--arg feature_spec "$FEATURE_SPEC" \
|
||||||
|
--arg impl_plan "$IMPL_PLAN" \
|
||||||
|
--arg tasks "$TASKS" \
|
||||||
|
'{REPO_ROOT:$repo_root,BRANCH:$branch,FEATURE_DIR:$feature_dir,FEATURE_SPEC:$feature_spec,IMPL_PLAN:$impl_plan,TASKS:$tasks}'
|
||||||
|
else
|
||||||
|
printf '{"REPO_ROOT":"%s","BRANCH":"%s","FEATURE_DIR":"%s","FEATURE_SPEC":"%s","IMPL_PLAN":"%s","TASKS":"%s"}\n' \
|
||||||
|
"$(json_escape "$REPO_ROOT")" "$(json_escape "$CURRENT_BRANCH")" "$(json_escape "$FEATURE_DIR")" "$(json_escape "$FEATURE_SPEC")" "$(json_escape "$IMPL_PLAN")" "$(json_escape "$TASKS")"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "REPO_ROOT: $REPO_ROOT"
|
||||||
|
echo "BRANCH: $CURRENT_BRANCH"
|
||||||
|
echo "FEATURE_DIR: $FEATURE_DIR"
|
||||||
|
echo "FEATURE_SPEC: $FEATURE_SPEC"
|
||||||
|
echo "IMPL_PLAN: $IMPL_PLAN"
|
||||||
|
echo "TASKS: $TASKS"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate required directories and files
|
||||||
|
if [[ ! -d "$FEATURE_DIR" ]]; then
|
||||||
|
echo "ERROR: Feature directory not found: $FEATURE_DIR" >&2
|
||||||
|
echo "Run /speckit-specify first to create the feature structure." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$IMPL_PLAN" ]]; then
|
||||||
|
echo "ERROR: plan.md not found in $FEATURE_DIR" >&2
|
||||||
|
echo "Run /speckit-plan first to create the implementation plan." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for tasks.md if required
|
||||||
|
if $REQUIRE_TASKS && [[ ! -f "$TASKS" ]]; then
|
||||||
|
echo "ERROR: tasks.md not found in $FEATURE_DIR" >&2
|
||||||
|
echo "Run /speckit-tasks first to create the task list." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build list of available documents
|
||||||
|
docs=()
|
||||||
|
|
||||||
|
# Always check these optional docs
|
||||||
|
[[ -f "$RESEARCH" ]] && docs+=("research.md")
|
||||||
|
[[ -f "$DATA_MODEL" ]] && docs+=("data-model.md")
|
||||||
|
|
||||||
|
# Check contracts directory (only if it exists and has files)
|
||||||
|
if [[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]; then
|
||||||
|
docs+=("contracts/")
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -f "$QUICKSTART" ]] && docs+=("quickstart.md")
|
||||||
|
|
||||||
|
# Include tasks.md if requested and it exists
|
||||||
|
if $INCLUDE_TASKS && [[ -f "$TASKS" ]]; then
|
||||||
|
docs+=("tasks.md")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Output results
|
||||||
|
if $JSON_MODE; then
|
||||||
|
# Build JSON array of documents
|
||||||
|
if has_jq; then
|
||||||
|
if [[ ${#docs[@]} -eq 0 ]]; then
|
||||||
|
json_docs="[]"
|
||||||
|
else
|
||||||
|
json_docs=$(printf '%s\n' "${docs[@]}" | jq -R . | jq -s .)
|
||||||
|
fi
|
||||||
|
jq -cn \
|
||||||
|
--arg feature_dir "$FEATURE_DIR" \
|
||||||
|
--argjson docs "$json_docs" \
|
||||||
|
'{FEATURE_DIR:$feature_dir,AVAILABLE_DOCS:$docs}'
|
||||||
|
else
|
||||||
|
if [[ ${#docs[@]} -eq 0 ]]; then
|
||||||
|
json_docs="[]"
|
||||||
|
else
|
||||||
|
json_docs=$(for d in "${docs[@]}"; do printf '"%s",' "$(json_escape "$d")"; done)
|
||||||
|
json_docs="[${json_docs%,}]"
|
||||||
|
fi
|
||||||
|
printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$(json_escape "$FEATURE_DIR")" "$json_docs"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Text output
|
||||||
|
echo "FEATURE_DIR:$FEATURE_DIR"
|
||||||
|
echo "AVAILABLE_DOCS:"
|
||||||
|
|
||||||
|
# Show status of each potential document
|
||||||
|
check_file "$RESEARCH" "research.md"
|
||||||
|
check_file "$DATA_MODEL" "data-model.md"
|
||||||
|
check_dir "$CONTRACTS_DIR" "contracts/"
|
||||||
|
check_file "$QUICKSTART" "quickstart.md"
|
||||||
|
|
||||||
|
if $INCLUDE_TASKS; then
|
||||||
|
check_file "$TASKS" "tasks.md"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
704
.specify/scripts/bash/common.sh
Executable file
704
.specify/scripts/bash/common.sh
Executable file
@ -0,0 +1,704 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Common functions and variables for all scripts
|
||||||
|
|
||||||
|
# Find repository root by searching upward for .specify directory
|
||||||
|
# This is the primary marker for spec-kit projects
|
||||||
|
find_specify_root() {
|
||||||
|
local dir="${1:-$(pwd)}"
|
||||||
|
# Normalize to absolute path to prevent infinite loop with relative paths
|
||||||
|
# Use -- to handle paths starting with - (e.g., -P, -L)
|
||||||
|
dir="$(cd -- "$dir" 2>/dev/null && pwd)" || return 1
|
||||||
|
local prev_dir=""
|
||||||
|
while true; do
|
||||||
|
if [ -d "$dir/.specify" ]; then
|
||||||
|
echo "$dir"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
# Stop if we've reached filesystem root or dirname stops changing
|
||||||
|
if [ "$dir" = "/" ] || [ "$dir" = "$prev_dir" ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
prev_dir="$dir"
|
||||||
|
dir="$(dirname "$dir")"
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resolve an explicit SPECIFY_INIT_DIR project override (the directory that
|
||||||
|
# *contains* .specify/), for non-interactive / CI use — e.g. running a Spec Kit
|
||||||
|
# command against a member project from a monorepo root without cd.
|
||||||
|
#
|
||||||
|
# Precondition: SPECIFY_INIT_DIR is non-empty. Echoes the validated absolute
|
||||||
|
# project root, or prints an error and returns 1. Strict by design: the path
|
||||||
|
# must exist and contain .specify/, with no silent fallback to cwd or the
|
||||||
|
# script-location default (which would silently write to the wrong project).
|
||||||
|
#
|
||||||
|
# This is the single resolver: bundled extensions inherit it by sourcing core
|
||||||
|
# (e.g. the git extension's create-new-feature-branch) rather than duplicating it.
|
||||||
|
resolve_specify_init_dir() {
|
||||||
|
local init_root
|
||||||
|
# Normalize: relative paths resolve against $(pwd); a trailing slash collapses.
|
||||||
|
# CDPATH="" so a relative value cannot be resolved against the caller's CDPATH
|
||||||
|
# (which would also echo to stdout and corrupt the captured path).
|
||||||
|
if ! init_root="$(CDPATH="" cd -- "$SPECIFY_INIT_DIR" 2>/dev/null && pwd)"; then
|
||||||
|
echo "ERROR: SPECIFY_INIT_DIR does not point to an existing directory: $SPECIFY_INIT_DIR" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [[ ! -d "$init_root/.specify" ]]; then
|
||||||
|
echo "ERROR: SPECIFY_INIT_DIR is not a Spec Kit project (no .specify/ directory): $init_root" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$init_root"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get repository root, prioritizing .specify directory
|
||||||
|
# This prevents using a parent repository when spec-kit is initialized in a subdirectory
|
||||||
|
get_repo_root() {
|
||||||
|
# Explicit project override wins (see resolve_specify_init_dir).
|
||||||
|
if [[ -n "${SPECIFY_INIT_DIR:-}" ]]; then
|
||||||
|
resolve_specify_init_dir
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# First, look for .specify directory (spec-kit's own marker)
|
||||||
|
local specify_root
|
||||||
|
if specify_root=$(find_specify_root); then
|
||||||
|
echo "$specify_root"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Final fallback to script location
|
||||||
|
local script_dir="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
(cd "$script_dir/../../.." && pwd)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get current feature name from explicit state only.
|
||||||
|
# Returns the feature identifier or empty string if none is set.
|
||||||
|
# Feature state is set by SPECIFY_FEATURE (from create-new-feature or
|
||||||
|
# the git extension) or implicitly via .specify/feature.json.
|
||||||
|
get_current_branch() {
|
||||||
|
if [[ -n "${SPECIFY_FEATURE:-}" ]]; then
|
||||||
|
echo "$SPECIFY_FEATURE"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# No explicit feature set — caller must handle this via feature.json
|
||||||
|
# in get_feature_paths(). Return empty to signal "unknown".
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Safely read .specify/feature.json's "feature_directory" value.
|
||||||
|
# Prints the raw value (possibly relative) to stdout, or empty string if the file
|
||||||
|
# is missing, unparseable, or does not contain the key. Always returns 0 so callers
|
||||||
|
# under `set -e` cannot be aborted by parser failure.
|
||||||
|
# Parser order mirrors the historical get_feature_paths behavior: jq -> python3 -> grep/sed.
|
||||||
|
read_feature_json_feature_directory() {
|
||||||
|
local repo_root="$1"
|
||||||
|
local fj="$repo_root/.specify/feature.json"
|
||||||
|
[[ -f "$fj" ]] || { printf '%s' ''; return 0; }
|
||||||
|
|
||||||
|
# Try parsers in order (jq -> python3 -> grep/sed), falling through on
|
||||||
|
# failure. Selection is by *parse success*, not mere availability: on
|
||||||
|
# Windows `python3` commonly resolves to the Microsoft Store App Execution
|
||||||
|
# Alias stub, which passes `command -v` but fails at runtime (exit 49), so
|
||||||
|
# an availability-gated `elif` would pick python3, swallow its failure, and
|
||||||
|
# never reach the grep/sed fallback -- leaving feature.json unreadable even
|
||||||
|
# though it is valid (issue #3304).
|
||||||
|
local _fd=''
|
||||||
|
if command -v jq >/dev/null 2>&1; then
|
||||||
|
if ! _fd=$(jq -r '.feature_directory // empty' "$fj" 2>/dev/null); then
|
||||||
|
_fd=''
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ -z "$_fd" ]] && command -v python3 >/dev/null 2>&1; then
|
||||||
|
# Use Python so pretty-printed/multi-line JSON still parses correctly.
|
||||||
|
if ! _fd=$(python3 -c "import json,sys; d=json.load(open(sys.argv[1])); v=d.get('feature_directory'); print(v if v else '')" "$fj" 2>/dev/null); then
|
||||||
|
_fd=''
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ -z "$_fd" ]]; then
|
||||||
|
# Last-resort single-line grep/sed fallback. The `|| true` guards against
|
||||||
|
# grep returning 1 (no match) aborting under `set -e` / `pipefail`.
|
||||||
|
_fd=$( { grep -E '"feature_directory"[[:space:]]*:' "$fj" 2>/dev/null || true; } \
|
||||||
|
| head -n 1 \
|
||||||
|
| sed -E 's/^[^:]*:[[:space:]]*"([^"]*)".*$/\1/' )
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s' "$_fd"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Persist a feature_directory value to .specify/feature.json.
|
||||||
|
# Writes only when the file is missing or the value differs from what's stored.
|
||||||
|
# Accepts the raw (possibly relative) path — callers should pass the original
|
||||||
|
# user-supplied value, not the normalized absolute path.
|
||||||
|
_persist_feature_json() {
|
||||||
|
local repo_root="$1"
|
||||||
|
local feature_dir_value="$2"
|
||||||
|
local fj="$repo_root/.specify/feature.json"
|
||||||
|
|
||||||
|
# Strip repo_root prefix if the value is absolute and under repo_root
|
||||||
|
if [[ "$feature_dir_value" == "$repo_root/"* ]]; then
|
||||||
|
feature_dir_value="${feature_dir_value#"$repo_root/"}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Read current value (if any) and skip write when unchanged
|
||||||
|
local current_val
|
||||||
|
current_val=$(read_feature_json_feature_directory "$repo_root")
|
||||||
|
if [[ "$current_val" == "$feature_dir_value" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure .specify/ directory exists
|
||||||
|
mkdir -p "$repo_root/.specify"
|
||||||
|
|
||||||
|
# Write feature.json — prefer jq for safe JSON, fall back to printf
|
||||||
|
if command -v jq >/dev/null 2>&1; then
|
||||||
|
jq -cn --arg fd "$feature_dir_value" '{feature_directory:$fd}' > "$fj"
|
||||||
|
else
|
||||||
|
printf '{"feature_directory":"%s"}\n' "$(json_escape "$feature_dir_value")" > "$fj"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
get_feature_paths() {
|
||||||
|
# Read-only callers (e.g. check-prerequisites.sh --paths-only) pass
|
||||||
|
# --no-persist so pure path resolution never writes .specify/feature.json,
|
||||||
|
# which would dirty the working tree or overwrite a pinned value (issue #3025).
|
||||||
|
local no_persist=false
|
||||||
|
if [[ "${1:-}" == "--no-persist" ]]; then
|
||||||
|
no_persist=true
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Split decl/assignment so a SPECIFY_INIT_DIR validation failure in
|
||||||
|
# get_repo_root propagates as a hard error instead of being masked by `local`.
|
||||||
|
local repo_root
|
||||||
|
repo_root=$(get_repo_root) || return 1
|
||||||
|
local current_branch
|
||||||
|
current_branch=$(get_current_branch)
|
||||||
|
|
||||||
|
# Resolve feature directory. Priority:
|
||||||
|
# 1. SPECIFY_FEATURE_DIRECTORY env var (explicit override)
|
||||||
|
# 2. .specify/feature.json "feature_directory" key (persisted by specify command)
|
||||||
|
# 3. Error — no feature context available
|
||||||
|
local feature_dir
|
||||||
|
if [[ -n "${SPECIFY_FEATURE_DIRECTORY:-}" ]]; then
|
||||||
|
feature_dir="$SPECIFY_FEATURE_DIRECTORY"
|
||||||
|
# Normalize relative paths to absolute under repo root
|
||||||
|
[[ "$feature_dir" != /* ]] && feature_dir="$repo_root/$feature_dir"
|
||||||
|
# Persist to feature.json so future sessions without the env var still
|
||||||
|
# work — unless the caller opted out for read-only resolution (#3025).
|
||||||
|
if [[ "$no_persist" != true ]]; then
|
||||||
|
_persist_feature_json "$repo_root" "$SPECIFY_FEATURE_DIRECTORY"
|
||||||
|
fi
|
||||||
|
elif [[ -f "$repo_root/.specify/feature.json" ]]; then
|
||||||
|
local _fd
|
||||||
|
_fd=$(read_feature_json_feature_directory "$repo_root")
|
||||||
|
if [[ -n "$_fd" ]]; then
|
||||||
|
feature_dir="$_fd"
|
||||||
|
# Normalize relative paths to absolute under repo root
|
||||||
|
[[ "$feature_dir" != /* ]] && feature_dir="$repo_root/$feature_dir"
|
||||||
|
else
|
||||||
|
echo "ERROR: Feature directory not found. Set SPECIFY_FEATURE_DIRECTORY or ensure .specify/feature.json contains feature_directory." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "ERROR: Feature directory not found. Set SPECIFY_FEATURE_DIRECTORY or run the specify command to create .specify/feature.json." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# When no branch context exists (no SPECIFY_FEATURE, feature resolved via
|
||||||
|
# SPECIFY_FEATURE_DIRECTORY or feature.json), fall back to the feature
|
||||||
|
# directory basename so CURRENT_BRANCH is a usable identifier rather than
|
||||||
|
# an empty, misleading value (issue #3026).
|
||||||
|
if [[ -z "$current_branch" ]]; then
|
||||||
|
local feature_dir_trimmed="${feature_dir%/}"
|
||||||
|
current_branch="${feature_dir_trimmed##*/}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use printf '%q' to safely quote values, preventing shell injection
|
||||||
|
# via crafted branch names or paths containing special characters
|
||||||
|
printf 'REPO_ROOT=%q\n' "$repo_root"
|
||||||
|
printf 'CURRENT_BRANCH=%q\n' "$current_branch"
|
||||||
|
printf 'FEATURE_DIR=%q\n' "$feature_dir"
|
||||||
|
printf 'FEATURE_SPEC=%q\n' "$feature_dir/spec.md"
|
||||||
|
printf 'IMPL_PLAN=%q\n' "$feature_dir/plan.md"
|
||||||
|
printf 'TASKS=%q\n' "$feature_dir/tasks.md"
|
||||||
|
printf 'RESEARCH=%q\n' "$feature_dir/research.md"
|
||||||
|
printf 'DATA_MODEL=%q\n' "$feature_dir/data-model.md"
|
||||||
|
printf 'QUICKSTART=%q\n' "$feature_dir/quickstart.md"
|
||||||
|
printf 'CONTRACTS_DIR=%q\n' "$feature_dir/contracts"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if jq is available for safe JSON construction
|
||||||
|
has_jq() {
|
||||||
|
command -v jq >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
get_invoke_separator() {
|
||||||
|
local repo_root="${1:-$(get_repo_root)}"
|
||||||
|
if [[ "${_SPECIFY_INVOKE_SEPARATOR_CACHE_REPO_ROOT:-}" == "$repo_root" && -n "${_SPECIFY_INVOKE_SEPARATOR_CACHE_VALUE:-}" ]]; then
|
||||||
|
printf '%s\n' "$_SPECIFY_INVOKE_SEPARATOR_CACHE_VALUE"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local integration_json="$repo_root/.specify/integration.json"
|
||||||
|
local separator="."
|
||||||
|
local parsed=0
|
||||||
|
|
||||||
|
if [[ -f "$integration_json" ]]; then
|
||||||
|
# Try parsers in order (jq -> python3 -> awk), falling through on
|
||||||
|
# failure. Selection is by *parse success*, not mere availability: on
|
||||||
|
# Windows `python3` commonly resolves to the Microsoft Store App
|
||||||
|
# Execution Alias stub, which passes `command -v` but fails at runtime
|
||||||
|
# (exit 49). An availability-gated branch would pick python3, swallow
|
||||||
|
# its failure, and — because this function historically had no text
|
||||||
|
# fallback — silently return "." even for `-`-separator integrations
|
||||||
|
# (e.g. forge, cline), yielding wrong command hints (issue #3304).
|
||||||
|
if command -v jq >/dev/null 2>&1; then
|
||||||
|
local jq_separator
|
||||||
|
if jq_separator=$(jq -r '(.default_integration // .integration // "") as $k | if $k == "" then "." else (.integration_settings[$k].invoke_separator // ".") end' "$integration_json" 2>/dev/null); then
|
||||||
|
case "$jq_separator" in
|
||||||
|
"."|"-") separator="$jq_separator"; parsed=1 ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$parsed" -eq 0 ]] && command -v python3 >/dev/null 2>&1; then
|
||||||
|
local py_separator
|
||||||
|
if py_separator=$(python3 - "$integration_json" <<'PY' 2>/dev/null
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(sys.argv[1], encoding="utf-8") as fh:
|
||||||
|
state = json.load(fh)
|
||||||
|
key = state.get("default_integration") or state.get("integration") or ""
|
||||||
|
settings = state.get("integration_settings")
|
||||||
|
separator = "."
|
||||||
|
if isinstance(key, str) and isinstance(settings, dict):
|
||||||
|
entry = settings.get(key)
|
||||||
|
if isinstance(entry, dict) and entry.get("invoke_separator") in {".", "-"}:
|
||||||
|
separator = entry["invoke_separator"]
|
||||||
|
print(separator)
|
||||||
|
except Exception:
|
||||||
|
sys.exit(1)
|
||||||
|
PY
|
||||||
|
); then
|
||||||
|
case "$py_separator" in
|
||||||
|
"."|"-") separator="$py_separator"; parsed=1 ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$parsed" -eq 0 ]]; then
|
||||||
|
# Last-resort text fallback for environments with neither jq nor a
|
||||||
|
# working python3 (e.g. stock Windows + Git Bash). Reads the active
|
||||||
|
# integration key (default_integration, else integration) and its
|
||||||
|
# invoke_separator from within the integration_settings object.
|
||||||
|
# Handles both pretty-printed (the written form) and compact JSON.
|
||||||
|
# Accumulate all lines into one buffer in END rather than using
|
||||||
|
# gawk-only whole-file slurp (RS="^$"), so this stays portable to
|
||||||
|
# the BSD awk on macOS.
|
||||||
|
local awk_separator
|
||||||
|
awk_separator=$(awk '
|
||||||
|
function keyval(d, name, v) {
|
||||||
|
if (match(d, "\"" name "\"[ \t\r\n]*:[ \t\r\n]*\"[^\"]*\"")) {
|
||||||
|
v=substr(d,RSTART,RLENGTH); sub(/^.*:[ \t\r\n]*"/,"",v); sub(/"$/,"",v); return v
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
{ doc = doc $0 "\n" }
|
||||||
|
END {
|
||||||
|
key=keyval(doc,"default_integration"); if (key=="") key=keyval(doc,"integration")
|
||||||
|
sep="."
|
||||||
|
if (key!="") {
|
||||||
|
settings=doc
|
||||||
|
if (match(doc, /"integration_settings"[ \t\r\n]*:[ \t\r\n]*[{]/)) {
|
||||||
|
settings=substr(doc, RSTART+RLENGTH-1)
|
||||||
|
}
|
||||||
|
if (match(settings, "\"" key "\"[ \t\r\n]*:[ \t\r\n]*[{]")) {
|
||||||
|
start=RSTART+RLENGTH-1
|
||||||
|
depth=0
|
||||||
|
obj=""
|
||||||
|
for (i=start; i<=length(settings); i++) {
|
||||||
|
c=substr(settings,i,1)
|
||||||
|
obj=obj c
|
||||||
|
if (c=="{") depth++
|
||||||
|
else if (c=="}") { depth--; if (depth==0) break }
|
||||||
|
}
|
||||||
|
if (match(obj, /"invoke_separator"[ \t\r\n]*:[ \t\r\n]*"[-.]"/)) {
|
||||||
|
tok=substr(obj,RSTART,RLENGTH); s=substr(tok,length(tok)-1,1)
|
||||||
|
if (s=="." || s=="-") sep=s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print sep
|
||||||
|
}
|
||||||
|
' "$integration_json" 2>/dev/null)
|
||||||
|
case "$awk_separator" in
|
||||||
|
"."|"-") separator="$awk_separator" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
_SPECIFY_INVOKE_SEPARATOR_CACHE_REPO_ROOT="$repo_root"
|
||||||
|
_SPECIFY_INVOKE_SEPARATOR_CACHE_VALUE="$separator"
|
||||||
|
printf '%s\n' "$separator"
|
||||||
|
}
|
||||||
|
|
||||||
|
format_speckit_command() {
|
||||||
|
local command_name="$1"
|
||||||
|
local repo_root="${2:-$(get_repo_root)}"
|
||||||
|
local separator
|
||||||
|
if [[ "${_SPECIFY_INVOKE_SEPARATOR_CACHE_REPO_ROOT:-}" == "$repo_root" && -n "${_SPECIFY_INVOKE_SEPARATOR_CACHE_VALUE:-}" ]]; then
|
||||||
|
separator="$_SPECIFY_INVOKE_SEPARATOR_CACHE_VALUE"
|
||||||
|
else
|
||||||
|
separator=$(get_invoke_separator "$repo_root")
|
||||||
|
_SPECIFY_INVOKE_SEPARATOR_CACHE_REPO_ROOT="$repo_root"
|
||||||
|
_SPECIFY_INVOKE_SEPARATOR_CACHE_VALUE="$separator"
|
||||||
|
fi
|
||||||
|
|
||||||
|
command_name="${command_name#/}"
|
||||||
|
command_name="${command_name#speckit.}"
|
||||||
|
command_name="${command_name#speckit-}"
|
||||||
|
command_name="${command_name//./$separator}"
|
||||||
|
|
||||||
|
printf '/speckit%s%s\n' "$separator" "$command_name"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Escape a string for safe embedding in a JSON value (fallback when jq is unavailable).
|
||||||
|
# Handles backslash, double-quote, and JSON-required control character escapes (RFC 8259).
|
||||||
|
json_escape() {
|
||||||
|
local s="$1"
|
||||||
|
s="${s//\\/\\\\}"
|
||||||
|
s="${s//\"/\\\"}"
|
||||||
|
s="${s//$'\n'/\\n}"
|
||||||
|
s="${s//$'\t'/\\t}"
|
||||||
|
s="${s//$'\r'/\\r}"
|
||||||
|
s="${s//$'\b'/\\b}"
|
||||||
|
s="${s//$'\f'/\\f}"
|
||||||
|
# Escape any remaining U+0001-U+001F control characters as \uXXXX.
|
||||||
|
# (U+0000/NUL cannot appear in bash strings and is excluded.)
|
||||||
|
# LC_ALL=C ensures ${#s} counts bytes and ${s:$i:1} yields single bytes,
|
||||||
|
# so multi-byte UTF-8 sequences (first byte >= 0xC0) pass through intact.
|
||||||
|
local LC_ALL=C
|
||||||
|
local i char code
|
||||||
|
for (( i=0; i<${#s}; i++ )); do
|
||||||
|
char="${s:$i:1}"
|
||||||
|
printf -v code '%d' "'$char" 2>/dev/null || code=256
|
||||||
|
if (( code >= 1 && code <= 31 )); then
|
||||||
|
printf '\\u%04x' "$code"
|
||||||
|
else
|
||||||
|
printf '%s' "$char"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
check_file() { [[ -f "$1" ]] && echo " ✓ $2" || echo " ✗ $2"; }
|
||||||
|
check_dir() { [[ -d "$1" && -n $(ls -A "$1" 2>/dev/null) ]] && echo " ✓ $2" || echo " ✗ $2"; }
|
||||||
|
|
||||||
|
# Resolve a template name to a file path using the priority stack:
|
||||||
|
# 1. .specify/templates/overrides/
|
||||||
|
# 2. .specify/presets/<preset-id>/templates/ (sorted by priority from .registry)
|
||||||
|
# 3. .specify/extensions/<ext-id>/templates/
|
||||||
|
# 4. .specify/templates/ (core)
|
||||||
|
resolve_template() {
|
||||||
|
local template_name="$1"
|
||||||
|
local repo_root="$2"
|
||||||
|
local base="$repo_root/.specify/templates"
|
||||||
|
|
||||||
|
# Priority 1: Project overrides
|
||||||
|
local override="$base/overrides/${template_name}.md"
|
||||||
|
[ -f "$override" ] && echo "$override" && return 0
|
||||||
|
|
||||||
|
# Priority 2: Installed presets (sorted by priority from .registry)
|
||||||
|
local presets_dir="$repo_root/.specify/presets"
|
||||||
|
if [ -d "$presets_dir" ]; then
|
||||||
|
local registry_file="$presets_dir/.registry"
|
||||||
|
if [ -f "$registry_file" ] && command -v python3 >/dev/null 2>&1; then
|
||||||
|
# Read preset IDs sorted by priority (lower number = higher precedence).
|
||||||
|
# The python3 call is wrapped in an if-condition so that set -e does not
|
||||||
|
# abort the function when python3 exits non-zero (e.g. invalid JSON).
|
||||||
|
local sorted_presets=""
|
||||||
|
if sorted_presets=$(SPECKIT_REGISTRY="$registry_file" python3 -c "
|
||||||
|
import json, sys, os
|
||||||
|
try:
|
||||||
|
with open(os.environ['SPECKIT_REGISTRY']) as f:
|
||||||
|
data = json.load(f)
|
||||||
|
presets = data.get('presets', {})
|
||||||
|
for pid, meta in sorted(presets.items(), key=lambda x: x[1].get('priority', 10) if isinstance(x[1], dict) else 10):
|
||||||
|
if isinstance(meta, dict) and meta.get('enabled', True) is not False:
|
||||||
|
print(pid)
|
||||||
|
except Exception:
|
||||||
|
sys.exit(1)
|
||||||
|
" 2>/dev/null); then
|
||||||
|
if [ -n "$sorted_presets" ]; then
|
||||||
|
# python3 succeeded and returned preset IDs — search in priority order
|
||||||
|
while IFS= read -r preset_id; do
|
||||||
|
local candidate="$presets_dir/$preset_id/templates/${template_name}.md"
|
||||||
|
[ -f "$candidate" ] && echo "$candidate" && return 0
|
||||||
|
done <<< "$sorted_presets"
|
||||||
|
fi
|
||||||
|
# python3 succeeded but registry has no presets — nothing to search
|
||||||
|
else
|
||||||
|
# python3 failed (missing, or registry parse error) — fall back to unordered directory scan
|
||||||
|
for preset in "$presets_dir"/*/; do
|
||||||
|
[ -d "$preset" ] || continue
|
||||||
|
local candidate="$preset/templates/${template_name}.md"
|
||||||
|
[ -f "$candidate" ] && echo "$candidate" && return 0
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Fallback: alphabetical directory order (no python3 available)
|
||||||
|
for preset in "$presets_dir"/*/; do
|
||||||
|
[ -d "$preset" ] || continue
|
||||||
|
local candidate="$preset/templates/${template_name}.md"
|
||||||
|
[ -f "$candidate" ] && echo "$candidate" && return 0
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Priority 3: Extension-provided templates
|
||||||
|
local ext_dir="$repo_root/.specify/extensions"
|
||||||
|
if [ -d "$ext_dir" ]; then
|
||||||
|
for ext in "$ext_dir"/*/; do
|
||||||
|
[ -d "$ext" ] || continue
|
||||||
|
# Skip hidden directories (e.g. .backup, .cache)
|
||||||
|
case "$(basename "$ext")" in .*) continue;; esac
|
||||||
|
local candidate="$ext/templates/${template_name}.md"
|
||||||
|
[ -f "$candidate" ] && echo "$candidate" && return 0
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Priority 4: Core templates
|
||||||
|
local core="$base/${template_name}.md"
|
||||||
|
[ -f "$core" ] && echo "$core" && return 0
|
||||||
|
|
||||||
|
# Template not found in any location.
|
||||||
|
# Return 1 so callers can distinguish "not found" from "found".
|
||||||
|
# Callers running under set -e should use: TEMPLATE=$(resolve_template ...) || true
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resolve a template name to composed content using composition strategies.
|
||||||
|
# Reads strategy metadata from preset manifests and composes content
|
||||||
|
# from multiple layers using prepend, append, or wrap strategies.
|
||||||
|
#
|
||||||
|
# Usage: CONTENT=$(resolve_template_content "template-name" "$REPO_ROOT")
|
||||||
|
# Returns composed content string on stdout; exit code 1 if not found.
|
||||||
|
resolve_template_content() {
|
||||||
|
local template_name="$1"
|
||||||
|
local repo_root="$2"
|
||||||
|
local base="$repo_root/.specify/templates"
|
||||||
|
|
||||||
|
# Collect all layers (highest priority first)
|
||||||
|
local -a layer_paths=()
|
||||||
|
local -a layer_strategies=()
|
||||||
|
|
||||||
|
# Priority 1: Project overrides (always "replace")
|
||||||
|
local override="$base/overrides/${template_name}.md"
|
||||||
|
if [ -f "$override" ]; then
|
||||||
|
layer_paths+=("$override")
|
||||||
|
layer_strategies+=("replace")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Priority 2: Installed presets (sorted by priority from .registry)
|
||||||
|
local presets_dir="$repo_root/.specify/presets"
|
||||||
|
if [ -d "$presets_dir" ]; then
|
||||||
|
local registry_file="$presets_dir/.registry"
|
||||||
|
local sorted_presets=""
|
||||||
|
if [ -f "$registry_file" ] && command -v python3 >/dev/null 2>&1; then
|
||||||
|
if sorted_presets=$(SPECKIT_REGISTRY="$registry_file" python3 -c "
|
||||||
|
import json, sys, os
|
||||||
|
try:
|
||||||
|
with open(os.environ['SPECKIT_REGISTRY']) as f:
|
||||||
|
data = json.load(f)
|
||||||
|
presets = data.get('presets', {})
|
||||||
|
for pid, meta in sorted(presets.items(), key=lambda x: x[1].get('priority', 10) if isinstance(x[1], dict) else 10):
|
||||||
|
if isinstance(meta, dict) and meta.get('enabled', True) is not False:
|
||||||
|
print(pid)
|
||||||
|
except Exception:
|
||||||
|
sys.exit(1)
|
||||||
|
" 2>/dev/null); then
|
||||||
|
if [ -n "$sorted_presets" ]; then
|
||||||
|
local yaml_warned=false
|
||||||
|
while IFS= read -r preset_id; do
|
||||||
|
# Read strategy and file path from preset manifest
|
||||||
|
local strategy="replace"
|
||||||
|
local manifest_file=""
|
||||||
|
local manifest="$presets_dir/$preset_id/preset.yml"
|
||||||
|
if [ -f "$manifest" ] && command -v python3 >/dev/null 2>&1; then
|
||||||
|
# Requires PyYAML; falls back to replace/convention if unavailable
|
||||||
|
local result
|
||||||
|
local py_stderr
|
||||||
|
py_stderr=$(mktemp)
|
||||||
|
result=$(SPECKIT_MANIFEST="$manifest" SPECKIT_TMPL="$template_name" python3 -c "
|
||||||
|
import sys, os
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
except ImportError:
|
||||||
|
print('yaml_missing', file=sys.stderr)
|
||||||
|
print('replace\t')
|
||||||
|
sys.exit(0)
|
||||||
|
try:
|
||||||
|
with open(os.environ['SPECKIT_MANIFEST']) as f:
|
||||||
|
data = yaml.safe_load(f)
|
||||||
|
for t in data.get('provides', {}).get('templates', []):
|
||||||
|
if t.get('name') == os.environ['SPECKIT_TMPL'] and t.get('type', 'template') == 'template':
|
||||||
|
print(t.get('strategy', 'replace') + '\t' + t.get('file', ''))
|
||||||
|
sys.exit(0)
|
||||||
|
print('replace\t')
|
||||||
|
except Exception:
|
||||||
|
print('replace\t')
|
||||||
|
" 2>"$py_stderr")
|
||||||
|
local parse_status=$?
|
||||||
|
if [ $parse_status -eq 0 ] && [ -n "$result" ]; then
|
||||||
|
IFS=$'\t' read -r strategy manifest_file <<< "$result"
|
||||||
|
strategy=$(printf '%s' "$strategy" | tr '[:upper:]' '[:lower:]')
|
||||||
|
fi
|
||||||
|
if [ "$yaml_warned" = false ] && grep -q 'yaml_missing' "$py_stderr" 2>/dev/null; then
|
||||||
|
echo "Warning: PyYAML not available; composition strategies may be ignored" >&2
|
||||||
|
yaml_warned=true
|
||||||
|
fi
|
||||||
|
rm -f "$py_stderr"
|
||||||
|
fi
|
||||||
|
# Try manifest file path first, then convention path
|
||||||
|
local candidate=""
|
||||||
|
if [ -n "$manifest_file" ]; then
|
||||||
|
# Reject absolute paths and parent traversal
|
||||||
|
case "$manifest_file" in
|
||||||
|
/*|*../*|../*) manifest_file="" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
if [ -n "$manifest_file" ]; then
|
||||||
|
local mf="$presets_dir/$preset_id/$manifest_file"
|
||||||
|
[ -f "$mf" ] && candidate="$mf"
|
||||||
|
fi
|
||||||
|
if [ -z "$candidate" ]; then
|
||||||
|
local cf="$presets_dir/$preset_id/templates/${template_name}.md"
|
||||||
|
[ -f "$cf" ] && candidate="$cf"
|
||||||
|
fi
|
||||||
|
if [ -n "$candidate" ]; then
|
||||||
|
layer_paths+=("$candidate")
|
||||||
|
layer_strategies+=("$strategy")
|
||||||
|
fi
|
||||||
|
done <<< "$sorted_presets"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# python3 failed — fall back to unordered directory scan (replace only)
|
||||||
|
for preset in "$presets_dir"/*/; do
|
||||||
|
[ -d "$preset" ] || continue
|
||||||
|
local candidate="$preset/templates/${template_name}.md"
|
||||||
|
if [ -f "$candidate" ]; then
|
||||||
|
layer_paths+=("$candidate")
|
||||||
|
layer_strategies+=("replace")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# No python3 or registry — fall back to unordered directory scan (replace only)
|
||||||
|
for preset in "$presets_dir"/*/; do
|
||||||
|
[ -d "$preset" ] || continue
|
||||||
|
local candidate="$preset/templates/${template_name}.md"
|
||||||
|
if [ -f "$candidate" ]; then
|
||||||
|
layer_paths+=("$candidate")
|
||||||
|
layer_strategies+=("replace")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Priority 3: Extension-provided templates (always "replace")
|
||||||
|
local ext_dir="$repo_root/.specify/extensions"
|
||||||
|
if [ -d "$ext_dir" ]; then
|
||||||
|
for ext in "$ext_dir"/*/; do
|
||||||
|
[ -d "$ext" ] || continue
|
||||||
|
case "$(basename "$ext")" in .*) continue;; esac
|
||||||
|
local candidate="$ext/templates/${template_name}.md"
|
||||||
|
if [ -f "$candidate" ]; then
|
||||||
|
layer_paths+=("$candidate")
|
||||||
|
layer_strategies+=("replace")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Priority 4: Core templates (always "replace")
|
||||||
|
local core="$base/${template_name}.md"
|
||||||
|
if [ -f "$core" ]; then
|
||||||
|
layer_paths+=("$core")
|
||||||
|
layer_strategies+=("replace")
|
||||||
|
fi
|
||||||
|
|
||||||
|
local count=${#layer_paths[@]}
|
||||||
|
[ "$count" -eq 0 ] && return 1
|
||||||
|
|
||||||
|
# Check if any layer uses a non-replace strategy
|
||||||
|
local has_composition=false
|
||||||
|
for s in "${layer_strategies[@]}"; do
|
||||||
|
[ "$s" != "replace" ] && has_composition=true && break
|
||||||
|
done
|
||||||
|
|
||||||
|
# If the top (highest-priority) layer is replace, it wins entirely —
|
||||||
|
# lower layers are irrelevant regardless of their strategies.
|
||||||
|
if [ "${layer_strategies[0]}" = "replace" ]; then
|
||||||
|
cat "${layer_paths[0]}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$has_composition" = false ]; then
|
||||||
|
cat "${layer_paths[0]}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find the effective base: scan from highest priority (index 0) downward
|
||||||
|
# to find the nearest replace layer. Only compose layers above that base.
|
||||||
|
local base_idx=-1
|
||||||
|
local i
|
||||||
|
for (( i=0; i<count; i++ )); do
|
||||||
|
if [ "${layer_strategies[$i]}" = "replace" ]; then
|
||||||
|
base_idx=$i
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $base_idx -lt 0 ]; then
|
||||||
|
return 1 # no base layer found
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Read the base content; compose layers above the base (higher priority)
|
||||||
|
local content
|
||||||
|
content=$(cat "${layer_paths[$base_idx]}"; printf x)
|
||||||
|
content="${content%x}"
|
||||||
|
|
||||||
|
for (( i=base_idx-1; i>=0; i-- )); do
|
||||||
|
local path="${layer_paths[$i]}"
|
||||||
|
local strat="${layer_strategies[$i]}"
|
||||||
|
local layer_content
|
||||||
|
# Preserve trailing newlines
|
||||||
|
layer_content=$(cat "$path"; printf x)
|
||||||
|
layer_content="${layer_content%x}"
|
||||||
|
|
||||||
|
case "$strat" in
|
||||||
|
replace) content="$layer_content" ;;
|
||||||
|
prepend) content="$(printf '%s\n\n%s' "$layer_content" "$content")" ;;
|
||||||
|
append) content="$(printf '%s\n\n%s' "$content" "$layer_content")" ;;
|
||||||
|
wrap)
|
||||||
|
case "$layer_content" in
|
||||||
|
*'{CORE_TEMPLATE}'*) ;;
|
||||||
|
*) echo "Error: wrap strategy missing {CORE_TEMPLATE} placeholder" >&2; return 1 ;;
|
||||||
|
esac
|
||||||
|
while [[ "$layer_content" == *'{CORE_TEMPLATE}'* ]]; do
|
||||||
|
local before="${layer_content%%\{CORE_TEMPLATE\}*}"
|
||||||
|
local after="${layer_content#*\{CORE_TEMPLATE\}}"
|
||||||
|
layer_content="${before}${content}${after}"
|
||||||
|
done
|
||||||
|
content="$layer_content"
|
||||||
|
;;
|
||||||
|
*) echo "Error: unknown strategy '$strat'" >&2; return 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '%s' "$content"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
301
.specify/scripts/bash/create-new-feature.sh
Executable file
301
.specify/scripts/bash/create-new-feature.sh
Executable file
@ -0,0 +1,301 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
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}"
|
||||||
|
# Check if the next argument is another option (starts with --)
|
||||||
|
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"
|
||||||
|
;;
|
||||||
|
--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 feature name and paths without creating directories or files"
|
||||||
|
echo " --allow-existing-branch Reuse an existing feature directory if it already exists"
|
||||||
|
echo " --short-name <name> Provide a custom short name (2-4 words) for the feature"
|
||||||
|
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 "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'"
|
||||||
|
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 (e.g., user passed only whitespace)
|
||||||
|
FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')
|
||||||
|
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 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/-$//'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resolve repository root using common.sh functions which prioritize .specify
|
||||||
|
SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$SCRIPT_DIR/common.sh"
|
||||||
|
|
||||||
|
REPO_ROOT=$(get_repo_root) || exit 1
|
||||||
|
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
|
||||||
|
SPECS_DIR="$REPO_ROOT/specs"
|
||||||
|
if [ "$DRY_RUN" != true ]; then
|
||||||
|
mkdir -p "$SPECS_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Function to generate branch name with stop word filtering and length filtering
|
||||||
|
generate_branch_name() {
|
||||||
|
local description="$1"
|
||||||
|
|
||||||
|
# Common stop words to filter out
|
||||||
|
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)$"
|
||||||
|
|
||||||
|
# Convert to lowercase and split into words
|
||||||
|
local clean_name=$(printf '%s' "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g')
|
||||||
|
|
||||||
|
# Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original)
|
||||||
|
local meaningful_words=()
|
||||||
|
for word in $clean_name; do
|
||||||
|
# Skip empty words
|
||||||
|
[ -z "$word" ] && continue
|
||||||
|
|
||||||
|
# Keep words that are NOT stop words AND (length >= 3 OR are potential acronyms)
|
||||||
|
if ! echo "$word" | grep -qiE "$stop_words"; then
|
||||||
|
if [ ${#word} -ge 3 ]; then
|
||||||
|
meaningful_words+=("$word")
|
||||||
|
# Keep short words that appear as an uppercase acronym in the original.
|
||||||
|
# Uppercase via tr and match with grep -w (both portable) rather than
|
||||||
|
# bash's 4+ "^^" case expansion (breaks on macOS bash 3.2) and \b (non-POSIX).
|
||||||
|
elif printf '%s' "$description" | grep -qw -- "$(printf '%s' "$word" | tr '[:lower:]' '[:upper:]')"; then
|
||||||
|
meaningful_words+=("$word")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# If we have meaningful words, use first 3-4 of them
|
||||||
|
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
|
||||||
|
# Fallback to original logic if no meaningful words found
|
||||||
|
local cleaned=$(clean_branch_name "$description")
|
||||||
|
echo "$cleaned" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate branch name
|
||||||
|
if [ -n "$SHORT_NAME" ]; then
|
||||||
|
# Use provided short name, just clean it up
|
||||||
|
BRANCH_SUFFIX=$(clean_branch_name "$SHORT_NAME")
|
||||||
|
else
|
||||||
|
# Generate from description with smart filtering
|
||||||
|
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
|
||||||
|
# Determine branch number from existing feature directories
|
||||||
|
if [ -z "$BRANCH_NUMBER" ]; then
|
||||||
|
HIGHEST=$(get_highest_from_specs "$SPECS_DIR")
|
||||||
|
BRANCH_NUMBER=$((HIGHEST + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Force base-10 interpretation to prevent octal conversion (e.g., 010 → 8 in octal, but should be 10 in decimal)
|
||||||
|
FEATURE_NUM=$(printf "%03d" "$((10#$BRANCH_NUMBER))")
|
||||||
|
BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# GitHub enforces a 244-byte limit on branch names
|
||||||
|
# Validate and truncate if necessary
|
||||||
|
MAX_BRANCH_LENGTH=244
|
||||||
|
if [ ${#BRANCH_NAME} -gt $MAX_BRANCH_LENGTH ]; then
|
||||||
|
# Calculate how much we need to trim from suffix
|
||||||
|
# Account for prefix length: timestamp (15) + hyphen (1) = 16, or sequential (3) + hyphen (1) = 4
|
||||||
|
PREFIX_LENGTH=$(( ${#FEATURE_NUM} + 1 ))
|
||||||
|
MAX_SUFFIX_LENGTH=$((MAX_BRANCH_LENGTH - PREFIX_LENGTH))
|
||||||
|
|
||||||
|
# Truncate suffix at word boundary if possible
|
||||||
|
TRUNCATED_SUFFIX=$(echo "$BRANCH_SUFFIX" | cut -c1-$MAX_SUFFIX_LENGTH)
|
||||||
|
# Remove trailing hyphen if truncation created one
|
||||||
|
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
|
||||||
|
|
||||||
|
FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME"
|
||||||
|
SPEC_FILE="$FEATURE_DIR/spec.md"
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" != true ]; then
|
||||||
|
if [ -d "$FEATURE_DIR" ] && [ "$ALLOW_EXISTING" != true ]; then
|
||||||
|
if [ "$USE_TIMESTAMP" = true ]; then
|
||||||
|
>&2 echo "Error: Feature directory '$FEATURE_DIR' already exists. Rerun to get a new timestamp or use a different --short-name."
|
||||||
|
else
|
||||||
|
>&2 echo "Error: Feature directory '$FEATURE_DIR' already exists. Please use a different feature name or specify a different number with --number."
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$FEATURE_DIR"
|
||||||
|
|
||||||
|
if [ ! -f "$SPEC_FILE" ]; then
|
||||||
|
TEMPLATE=$(resolve_template "spec-template" "$REPO_ROOT") || true
|
||||||
|
if [ -n "$TEMPLATE" ] && [ -f "$TEMPLATE" ]; then
|
||||||
|
cp "$TEMPLATE" "$SPEC_FILE"
|
||||||
|
else
|
||||||
|
echo "Warning: Spec template not found; created empty spec file" >&2
|
||||||
|
touch "$SPEC_FILE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Persist to .specify/feature.json so downstream commands can find the feature
|
||||||
|
_persist_feature_json "$REPO_ROOT" "$FEATURE_DIR"
|
||||||
|
|
||||||
|
# Inform the user how to set feature state in their own shell
|
||||||
|
printf '# To persist: export SPECIFY_FEATURE=%q\n' "$BRANCH_NAME" >&2
|
||||||
|
printf '# export SPECIFY_FEATURE_DIRECTORY=%q\n' "$FEATURE_DIR" >&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 spec_file "$SPEC_FILE" \
|
||||||
|
--arg feature_num "$FEATURE_NUM" \
|
||||||
|
'{BRANCH_NAME:$branch_name,SPEC_FILE:$spec_file,FEATURE_NUM:$feature_num,DRY_RUN:true}'
|
||||||
|
else
|
||||||
|
jq -cn \
|
||||||
|
--arg branch_name "$BRANCH_NAME" \
|
||||||
|
--arg spec_file "$SPEC_FILE" \
|
||||||
|
--arg feature_num "$FEATURE_NUM" \
|
||||||
|
'{BRANCH_NAME:$branch_name,SPEC_FILE:$spec_file,FEATURE_NUM:$feature_num}'
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s","DRY_RUN":true}\n' "$(json_escape "$BRANCH_NAME")" "$(json_escape "$SPEC_FILE")" "$(json_escape "$FEATURE_NUM")"
|
||||||
|
else
|
||||||
|
printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$(json_escape "$BRANCH_NAME")" "$(json_escape "$SPEC_FILE")" "$(json_escape "$FEATURE_NUM")"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "BRANCH_NAME: $BRANCH_NAME"
|
||||||
|
echo "SPEC_FILE: $SPEC_FILE"
|
||||||
|
echo "FEATURE_NUM: $FEATURE_NUM"
|
||||||
|
if [ "$DRY_RUN" != true ]; then
|
||||||
|
printf '# To persist in your shell: export SPECIFY_FEATURE=%q\n' "$BRANCH_NAME"
|
||||||
|
printf '# export SPECIFY_FEATURE_DIRECTORY=%q\n' "$FEATURE_DIR"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
83
.specify/scripts/bash/setup-plan.sh
Executable file
83
.specify/scripts/bash/setup-plan.sh
Executable file
@ -0,0 +1,83 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
JSON_MODE=false
|
||||||
|
ARGS=()
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--json)
|
||||||
|
JSON_MODE=true
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
echo "Usage: $0 [--json]"
|
||||||
|
echo " --json Output results in JSON format"
|
||||||
|
echo " --help Show this help message"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
ARGS+=("$arg")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Get script directory and load common functions
|
||||||
|
SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$SCRIPT_DIR/common.sh"
|
||||||
|
|
||||||
|
# Get all paths and variables from common functions
|
||||||
|
_paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; }
|
||||||
|
eval "$_paths_output"
|
||||||
|
unset _paths_output
|
||||||
|
|
||||||
|
# Ensure the feature directory exists
|
||||||
|
mkdir -p "$FEATURE_DIR"
|
||||||
|
|
||||||
|
# Copy plan template if plan doesn't already exist
|
||||||
|
if [[ -f "$IMPL_PLAN" ]]; then
|
||||||
|
if $JSON_MODE; then
|
||||||
|
echo "Plan already exists at $IMPL_PLAN, skipping template copy" >&2
|
||||||
|
else
|
||||||
|
echo "Plan already exists at $IMPL_PLAN, skipping template copy"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
TEMPLATE=$(resolve_template "plan-template" "$REPO_ROOT") || true
|
||||||
|
if [[ -n "$TEMPLATE" ]] && [[ -f "$TEMPLATE" ]]; then
|
||||||
|
cp "$TEMPLATE" "$IMPL_PLAN"
|
||||||
|
if $JSON_MODE; then
|
||||||
|
echo "Copied plan template to $IMPL_PLAN" >&2
|
||||||
|
else
|
||||||
|
echo "Copied plan template to $IMPL_PLAN"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if $JSON_MODE; then
|
||||||
|
echo "Warning: Plan template not found" >&2
|
||||||
|
else
|
||||||
|
echo "Warning: Plan template not found"
|
||||||
|
fi
|
||||||
|
# Create a basic plan file if template doesn't exist
|
||||||
|
touch "$IMPL_PLAN"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Output results
|
||||||
|
if $JSON_MODE; then
|
||||||
|
if has_jq; then
|
||||||
|
jq -cn \
|
||||||
|
--arg feature_spec "$FEATURE_SPEC" \
|
||||||
|
--arg impl_plan "$IMPL_PLAN" \
|
||||||
|
--arg specs_dir "$FEATURE_DIR" \
|
||||||
|
--arg branch "$CURRENT_BRANCH" \
|
||||||
|
'{FEATURE_SPEC:$feature_spec,IMPL_PLAN:$impl_plan,SPECS_DIR:$specs_dir,BRANCH:$branch}'
|
||||||
|
else
|
||||||
|
printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s"}\n' \
|
||||||
|
"$(json_escape "$FEATURE_SPEC")" "$(json_escape "$IMPL_PLAN")" "$(json_escape "$FEATURE_DIR")" "$(json_escape "$CURRENT_BRANCH")"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "FEATURE_SPEC: $FEATURE_SPEC"
|
||||||
|
echo "IMPL_PLAN: $IMPL_PLAN"
|
||||||
|
echo "SPECS_DIR: $FEATURE_DIR"
|
||||||
|
echo "BRANCH: $CURRENT_BRANCH"
|
||||||
|
fi
|
||||||
91
.specify/scripts/bash/setup-tasks.sh
Executable file
91
.specify/scripts/bash/setup-tasks.sh
Executable file
@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
JSON_MODE=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--json) JSON_MODE=true ;;
|
||||||
|
--help|-h)
|
||||||
|
echo "Usage: $0 [--json]"
|
||||||
|
echo " --json Output results in JSON format"
|
||||||
|
echo " --help Show this help message"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*) echo "ERROR: Unknown option '$arg'" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Source common functions
|
||||||
|
SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$SCRIPT_DIR/common.sh"
|
||||||
|
|
||||||
|
# Get feature paths
|
||||||
|
_paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; }
|
||||||
|
eval "$_paths_output"
|
||||||
|
unset _paths_output
|
||||||
|
|
||||||
|
# Validate required files
|
||||||
|
if [[ ! -f "$IMPL_PLAN" ]]; then
|
||||||
|
echo "ERROR: plan.md not found in $FEATURE_DIR" >&2
|
||||||
|
echo "Run /speckit-plan first to create the implementation plan." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$FEATURE_SPEC" ]]; then
|
||||||
|
echo "ERROR: spec.md not found in $FEATURE_DIR" >&2
|
||||||
|
echo "Run /speckit-specify first to create the feature structure." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build available docs list
|
||||||
|
docs=()
|
||||||
|
[[ -f "$RESEARCH" ]] && docs+=("research.md")
|
||||||
|
[[ -f "$DATA_MODEL" ]] && docs+=("data-model.md")
|
||||||
|
if [[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]; then
|
||||||
|
docs+=("contracts/")
|
||||||
|
fi
|
||||||
|
[[ -f "$QUICKSTART" ]] && docs+=("quickstart.md")
|
||||||
|
|
||||||
|
# Resolve tasks template through override stack
|
||||||
|
TASKS_TEMPLATE=$(resolve_template "tasks-template" "$REPO_ROOT") || true
|
||||||
|
if [[ -z "$TASKS_TEMPLATE" ]] || [[ ! -f "$TASKS_TEMPLATE" ]]; then
|
||||||
|
echo "ERROR: Could not resolve required tasks-template from the template override stack for $REPO_ROOT" >&2
|
||||||
|
echo "Template 'tasks-template' was not found in any supported location (overrides, presets, extensions, or shared core). Add an override at .specify/templates/overrides/tasks-template.md, or run 'specify init' / reinstall shared infra to restore the core .specify/templates/tasks-template.md template." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Output results
|
||||||
|
if $JSON_MODE; then
|
||||||
|
if has_jq; then
|
||||||
|
if [[ ${#docs[@]} -eq 0 ]]; then
|
||||||
|
json_docs="[]"
|
||||||
|
else
|
||||||
|
json_docs=$(printf '%s\n' "${docs[@]}" | jq -R . | jq -s .)
|
||||||
|
fi
|
||||||
|
jq -cn \
|
||||||
|
--arg feature_dir "$FEATURE_DIR" \
|
||||||
|
--argjson docs "$json_docs" \
|
||||||
|
--arg tasks_template "${TASKS_TEMPLATE:-}" \
|
||||||
|
'{FEATURE_DIR:$feature_dir,AVAILABLE_DOCS:$docs,TASKS_TEMPLATE:$tasks_template}'
|
||||||
|
else
|
||||||
|
if [[ ${#docs[@]} -eq 0 ]]; then
|
||||||
|
json_docs="[]"
|
||||||
|
else
|
||||||
|
json_docs=$(for d in "${docs[@]}"; do printf '"%s",' "$(json_escape "$d")"; done)
|
||||||
|
json_docs="[${json_docs%,}]"
|
||||||
|
fi
|
||||||
|
printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s,"TASKS_TEMPLATE":"%s"}\n' \
|
||||||
|
"$(json_escape "$FEATURE_DIR")" "$json_docs" "$(json_escape "${TASKS_TEMPLATE:-}")"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "FEATURE_DIR: $FEATURE_DIR"
|
||||||
|
echo "TASKS_TEMPLATE: ${TASKS_TEMPLATE:-not found}"
|
||||||
|
echo "AVAILABLE_DOCS:"
|
||||||
|
check_file "$RESEARCH" "research.md"
|
||||||
|
check_file "$DATA_MODEL" "data-model.md"
|
||||||
|
check_dir "$CONTRACTS_DIR" "contracts/"
|
||||||
|
check_file "$QUICKSTART" "quickstart.md"
|
||||||
|
fi
|
||||||
40
.specify/templates/checklist-template.md
Normal file
40
.specify/templates/checklist-template.md
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# [CHECKLIST TYPE] Checklist: [FEATURE NAME]
|
||||||
|
|
||||||
|
**Purpose**: [Brief description of what this checklist covers]
|
||||||
|
**Created**: [DATE]
|
||||||
|
**Feature**: [Link to spec.md or relevant documentation]
|
||||||
|
|
||||||
|
**Note**: This checklist is generated by the `/speckit-checklist` command based on feature context and requirements.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
============================================================================
|
||||||
|
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.
|
||||||
|
============================================================================
|
||||||
|
-->
|
||||||
|
|
||||||
|
## [Category 1]
|
||||||
|
|
||||||
|
- [ ] CHK001 First checklist item with clear action
|
||||||
|
- [ ] CHK002 Second checklist item
|
||||||
|
- [ ] CHK003 Third checklist item
|
||||||
|
|
||||||
|
## [Category 2]
|
||||||
|
|
||||||
|
- [ ] CHK004 Another category item
|
||||||
|
- [ ] CHK005 Item with specific criteria
|
||||||
|
- [ ] CHK006 Final item in this category
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Check items off as completed: `[x]`
|
||||||
|
- Add comments or findings inline
|
||||||
|
- Link to relevant resources or documentation
|
||||||
|
- Items are numbered sequentially for easy reference
|
||||||
50
.specify/templates/constitution-template.md
Normal file
50
.specify/templates/constitution-template.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# [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 -->
|
||||||
125
.specify/templates/plan-template.md
Normal file
125
.specify/templates/plan-template.md
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
# Implementation Plan: [FEATURE]
|
||||||
|
|
||||||
|
**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link]
|
||||||
|
|
||||||
|
**Input**: Feature specification from `/specs/[###-feature-name]/spec.md`
|
||||||
|
|
||||||
|
**Note**: This template is filled in by the `/speckit-plan` command; its definition describes the execution workflow.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
[Extract from feature spec: primary requirement + technical approach from research]
|
||||||
|
|
||||||
|
## Technical Context
|
||||||
|
|
||||||
|
<!--
|
||||||
|
ACTION REQUIRED: Replace the content in this section with the technical details
|
||||||
|
for the project. The structure here is presented in advisory capacity to guide
|
||||||
|
the iteration process.
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Language/Version**: [e.g., Python 3.11, Swift 5.9, Rust 1.75 or NEEDS CLARIFICATION]
|
||||||
|
|
||||||
|
**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]
|
||||||
|
|
||||||
|
**Target Platform**: [e.g., Linux server, iOS 15+, WASM or NEEDS CLARIFICATION]
|
||||||
|
|
||||||
|
**Project Type**: [e.g., library/cli/web-service/mobile-app/compiler/desktop-app or NEEDS CLARIFICATION]
|
||||||
|
|
||||||
|
**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]
|
||||||
|
|
||||||
|
## Constitution Check
|
||||||
|
|
||||||
|
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||||
|
|
||||||
|
- **Enterprise experience**: Primary journey, failure states, recovery guidance, and terminology
|
||||||
|
consistency are defined for every affected customer-facing surface.
|
||||||
|
- **Lifecycle safety**: Installation, upgrade, configuration, backup/restore, interruption, rollback,
|
||||||
|
and data-migration impacts are identified or explicitly marked not applicable.
|
||||||
|
- **Security and audit**: Authentication, authorization, secrets, personal data, secure defaults, and
|
||||||
|
audit-event impacts are documented.
|
||||||
|
- **Entitlement and packaging**: Workstation/Server and tier behaviour, compatibility identifiers,
|
||||||
|
packaged copies, deployment roles, and ISO impact are mapped where applicable.
|
||||||
|
- **Evidence**: Measurable acceptance criteria and verification environments are defined; release
|
||||||
|
claims distinguish static, local, deployment, and live-appliance validation.
|
||||||
|
|
||||||
|
Any failed gate requires a documented exception in Complexity Tracking with risk, mitigation, owner,
|
||||||
|
and expiry condition.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
### Documentation (this feature)
|
||||||
|
|
||||||
|
```text
|
||||||
|
specs/[###-feature]/
|
||||||
|
├── plan.md # This file (/speckit-plan command output)
|
||||||
|
├── research.md # Phase 0 output (/speckit-plan command)
|
||||||
|
├── data-model.md # Phase 1 output (/speckit-plan command)
|
||||||
|
├── quickstart.md # Phase 1 output (/speckit-plan command)
|
||||||
|
├── contracts/ # Phase 1 output (/speckit-plan command)
|
||||||
|
└── tasks.md # Phase 2 output (/speckit-tasks command - NOT created by /speckit-plan)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Source Code (repository root)
|
||||||
|
<!--
|
||||||
|
ACTION REQUIRED: Replace the placeholder tree below with the concrete layout
|
||||||
|
for this feature. Delete unused options and expand the chosen structure with
|
||||||
|
real paths (e.g., apps/admin, packages/something). The delivered plan must
|
||||||
|
not include Option labels.
|
||||||
|
-->
|
||||||
|
|
||||||
|
```text
|
||||||
|
# [REMOVE IF UNUSED] Option 1: Single project (DEFAULT)
|
||||||
|
src/
|
||||||
|
├── models/
|
||||||
|
├── services/
|
||||||
|
├── cli/
|
||||||
|
└── lib/
|
||||||
|
|
||||||
|
tests/
|
||||||
|
├── contract/
|
||||||
|
├── integration/
|
||||||
|
└── unit/
|
||||||
|
|
||||||
|
# [REMOVE IF UNUSED] Option 2: Web application (when "frontend" + "backend" detected)
|
||||||
|
backend/
|
||||||
|
├── src/
|
||||||
|
│ ├── models/
|
||||||
|
│ ├── services/
|
||||||
|
│ └── api/
|
||||||
|
└── tests/
|
||||||
|
|
||||||
|
frontend/
|
||||||
|
├── src/
|
||||||
|
│ ├── components/
|
||||||
|
│ ├── pages/
|
||||||
|
│ └── services/
|
||||||
|
└── tests/
|
||||||
|
|
||||||
|
# [REMOVE IF UNUSED] Option 3: Mobile + API (when "iOS/Android" detected)
|
||||||
|
api/
|
||||||
|
└── [same as backend above]
|
||||||
|
|
||||||
|
ios/ or android/
|
||||||
|
└── [platform-specific structure: feature modules, UI flows, platform tests]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Structure Decision**: [Document the selected structure and reference the real
|
||||||
|
directories captured above]
|
||||||
|
|
||||||
|
## Complexity Tracking
|
||||||
|
|
||||||
|
> **Fill ONLY if Constitution Check has violations that must be justified**
|
||||||
|
|
||||||
|
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
||||||
|
|-----------|------------|-------------------------------------|
|
||||||
|
| [e.g., 4th project] | [current need] | [why 3 projects insufficient] |
|
||||||
|
| [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] |
|
||||||
154
.specify/templates/spec-template.md
Normal file
154
.specify/templates/spec-template.md
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
# Feature Specification: [FEATURE NAME]
|
||||||
|
|
||||||
|
**Feature Branch**: `[###-feature-name]`
|
||||||
|
|
||||||
|
**Created**: [DATE]
|
||||||
|
|
||||||
|
**Status**: Draft
|
||||||
|
|
||||||
|
**Input**: User description: "$ARGUMENTS"
|
||||||
|
|
||||||
|
## User Scenarios & Testing *(mandatory)*
|
||||||
|
|
||||||
|
<!--
|
||||||
|
IMPORTANT: User stories should be PRIORITIZED as user journeys ordered by importance.
|
||||||
|
Each user story/journey must be INDEPENDENTLY TESTABLE - meaning if you implement just ONE of them,
|
||||||
|
you should still have a viable MVP (Minimum Viable Product) that delivers value.
|
||||||
|
|
||||||
|
Assign priorities (P1, P2, P3, etc.) to each story, where P1 is the most critical.
|
||||||
|
Think of each story as a standalone slice of functionality that can be:
|
||||||
|
- Developed independently
|
||||||
|
- Tested independently
|
||||||
|
- Deployed independently
|
||||||
|
- Demonstrated to users independently
|
||||||
|
-->
|
||||||
|
|
||||||
|
### User Story 1 - [Brief Title] (Priority: P1)
|
||||||
|
|
||||||
|
[Describe this user journey in plain language]
|
||||||
|
|
||||||
|
**Why this priority**: [Explain the value and why it has this priority level]
|
||||||
|
|
||||||
|
**Independent Test**: [Describe how this can be tested independently - e.g., "Can be fully tested by [specific action] and delivers [specific value]"]
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** [initial state], **When** [action], **Then** [expected outcome]
|
||||||
|
2. **Given** [initial state], **When** [action], **Then** [expected outcome]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 2 - [Brief Title] (Priority: P2)
|
||||||
|
|
||||||
|
[Describe this user journey in plain language]
|
||||||
|
|
||||||
|
**Why this priority**: [Explain the value and why it has this priority level]
|
||||||
|
|
||||||
|
**Independent Test**: [Describe how this can be tested independently]
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** [initial state], **When** [action], **Then** [expected outcome]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 3 - [Brief Title] (Priority: P3)
|
||||||
|
|
||||||
|
[Describe this user journey in plain language]
|
||||||
|
|
||||||
|
**Why this priority**: [Explain the value and why it has this priority level]
|
||||||
|
|
||||||
|
**Independent Test**: [Describe how this can be tested independently]
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** [initial state], **When** [action], **Then** [expected outcome]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[Add more user stories as needed, each with an assigned priority]
|
||||||
|
|
||||||
|
### Edge Cases
|
||||||
|
|
||||||
|
<!--
|
||||||
|
ACTION REQUIRED: The content in this section represents placeholders.
|
||||||
|
Fill them out with the right edge cases.
|
||||||
|
-->
|
||||||
|
|
||||||
|
- What happens when [boundary condition]?
|
||||||
|
- How does system handle [error scenario]?
|
||||||
|
- What happens when installation, upgrade, or a dependent service is interrupted?
|
||||||
|
- What can the customer or support engineer safely do to recover?
|
||||||
|
- How do Workstation/Server tiers and entitlements change the journey?
|
||||||
|
|
||||||
|
## Requirements *(mandatory)*
|
||||||
|
|
||||||
|
<!--
|
||||||
|
ACTION REQUIRED: The content in this section represents placeholders.
|
||||||
|
Fill them out with the right functional requirements.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Functional Requirements
|
||||||
|
|
||||||
|
- **FR-001**: System MUST [specific capability, e.g., "allow users to create accounts"]
|
||||||
|
- **FR-002**: System MUST [specific capability, e.g., "validate email addresses"]
|
||||||
|
- **FR-003**: Users MUST be able to [key interaction, e.g., "reset their password"]
|
||||||
|
- **FR-004**: System MUST [data requirement, e.g., "persist user preferences"]
|
||||||
|
- **FR-005**: System MUST [behavior, e.g., "log all security events"]
|
||||||
|
|
||||||
|
### Enterprise Appliance Requirements *(mandatory)*
|
||||||
|
|
||||||
|
- **EA-001 Customer Experience**: [Define complete primary journey, consistent terminology, and
|
||||||
|
actionable empty/error/degraded states]
|
||||||
|
- **EA-002 Security & Audit**: [Define authorization, secure defaults, sensitive-data boundaries,
|
||||||
|
and required audit evidence]
|
||||||
|
- **EA-003 Lifecycle & Recovery**: [Define install/upgrade/configuration impact, interruption
|
||||||
|
behaviour, rollback, backup/restore, and support recovery path, or state N/A with rationale]
|
||||||
|
- **EA-004 Entitlement & Packaging**: [Define Workstation/Server and tier behaviour plus every
|
||||||
|
packaged/deployment surface that must remain aligned, or state N/A with rationale]
|
||||||
|
- **EA-005 Operability**: [Define diagnostics, logs, health signals, offline/restricted-network
|
||||||
|
assumptions, and support documentation]
|
||||||
|
|
||||||
|
*Example of marking unclear requirements:*
|
||||||
|
|
||||||
|
- **FR-006**: System MUST authenticate users via [NEEDS CLARIFICATION: auth method not specified - email/password, SSO, OAuth?]
|
||||||
|
- **FR-007**: System MUST retain user data for [NEEDS CLARIFICATION: retention period not specified]
|
||||||
|
|
||||||
|
### Key Entities *(include if feature involves data)*
|
||||||
|
|
||||||
|
- **[Entity 1]**: [What it represents, key attributes without implementation]
|
||||||
|
- **[Entity 2]**: [What it represents, relationships to other entities]
|
||||||
|
|
||||||
|
## Success Criteria *(mandatory)*
|
||||||
|
|
||||||
|
<!--
|
||||||
|
ACTION REQUIRED: Define measurable success criteria.
|
||||||
|
These must be technology-agnostic and measurable.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Measurable Outcomes
|
||||||
|
|
||||||
|
- **SC-001**: [Measurable metric, e.g., "Users can complete account creation in under 2 minutes"]
|
||||||
|
- **SC-002**: [Measurable metric, e.g., "System handles 1000 concurrent users without degradation"]
|
||||||
|
- **SC-003**: [User satisfaction metric, e.g., "90% of users successfully complete primary task on first attempt"]
|
||||||
|
- **SC-004**: [Business metric, e.g., "Reduce support tickets related to [X] by 50%"]
|
||||||
|
|
||||||
|
### Verification Evidence *(mandatory)*
|
||||||
|
|
||||||
|
- **Static/local evidence**: [Checks and automated tests required]
|
||||||
|
- **Deployment evidence**: [Installed-environment checks required, or N/A with rationale]
|
||||||
|
- **Live-appliance evidence**: [Customer-like journey and failure/recovery checks, or N/A with rationale]
|
||||||
|
- **Release artifact evidence**: [Package/ISO provenance and content checks, or N/A with rationale]
|
||||||
|
|
||||||
|
## Assumptions
|
||||||
|
|
||||||
|
<!--
|
||||||
|
ACTION REQUIRED: The content in this section represents placeholders.
|
||||||
|
Fill them out with the right assumptions based on reasonable defaults
|
||||||
|
chosen when the feature description did not specify certain details.
|
||||||
|
-->
|
||||||
|
|
||||||
|
- [Assumption about target users, e.g., "Users have stable internet connectivity"]
|
||||||
|
- [Assumption about scope boundaries, e.g., "Mobile support is out of scope for v1"]
|
||||||
|
- [Assumption about data/environment, e.g., "Existing authentication system will be reused"]
|
||||||
|
- [Dependency on existing system/service, e.g., "Requires access to the existing user profile API"]
|
||||||
259
.specify/templates/tasks-template.md
Normal file
259
.specify/templates/tasks-template.md
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
description: "Task list template for feature implementation"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Tasks: [FEATURE NAME]
|
||||||
|
|
||||||
|
**Input**: Design documents from `/specs/[###-feature-name]/`
|
||||||
|
|
||||||
|
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/
|
||||||
|
|
||||||
|
**Tests**: Tests and verification tasks required by the specification and constitution are mandatory.
|
||||||
|
Add the lowest sufficient test levels plus deployment or live-appliance evidence where applicable.
|
||||||
|
|
||||||
|
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
|
||||||
|
|
||||||
|
## Format: `[ID] [P?] [Story] Description`
|
||||||
|
|
||||||
|
- **[P]**: Can run in parallel (different files, no dependencies)
|
||||||
|
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
|
||||||
|
- Include exact file paths in descriptions
|
||||||
|
|
||||||
|
## Path Conventions
|
||||||
|
|
||||||
|
- **Single project**: `src/`, `tests/` at repository root
|
||||||
|
- **Web app**: `backend/src/`, `frontend/src/`
|
||||||
|
- **Mobile**: `api/src/`, `ios/src/` or `android/src/`
|
||||||
|
- Paths shown below assume single project - adjust based on plan.md structure
|
||||||
|
|
||||||
|
<!--
|
||||||
|
============================================================================
|
||||||
|
IMPORTANT: The tasks below are SAMPLE TASKS for illustration purposes only.
|
||||||
|
|
||||||
|
The /speckit-tasks command MUST replace these with actual tasks based on:
|
||||||
|
- User stories from spec.md (with their priorities P1, P2, P3...)
|
||||||
|
- Feature requirements from plan.md
|
||||||
|
- Entities from data-model.md
|
||||||
|
- Endpoints from contracts/
|
||||||
|
|
||||||
|
Tasks MUST be organized by user story so each story can be:
|
||||||
|
- Implemented independently
|
||||||
|
- Tested independently
|
||||||
|
- Delivered as an MVP increment
|
||||||
|
|
||||||
|
DO NOT keep these sample tasks in the generated tasks.md file.
|
||||||
|
============================================================================
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Phase 1: Setup (Shared Infrastructure)
|
||||||
|
|
||||||
|
**Purpose**: Project initialization and basic structure
|
||||||
|
|
||||||
|
- [ ] T001 Create project structure per implementation plan
|
||||||
|
- [ ] T002 Initialize [language] project with [framework] dependencies
|
||||||
|
- [ ] T003 [P] Configure linting and formatting tools
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Foundational (Blocking Prerequisites)
|
||||||
|
|
||||||
|
**Purpose**: Core infrastructure that MUST be complete before ANY user story can be implemented
|
||||||
|
|
||||||
|
**⚠️ CRITICAL**: No user story work can begin until this phase is complete
|
||||||
|
|
||||||
|
Examples of foundational tasks (adjust based on your project):
|
||||||
|
|
||||||
|
- [ ] T004 Setup database schema and migrations framework
|
||||||
|
- [ ] T005 [P] Implement authentication/authorization framework
|
||||||
|
- [ ] T006 [P] Setup API routing and middleware structure
|
||||||
|
- [ ] T007 Create base models/entities that all stories depend on
|
||||||
|
- [ ] T008 Configure error handling and logging infrastructure
|
||||||
|
- [ ] T009 Setup environment configuration management
|
||||||
|
|
||||||
|
**Checkpoint**: Foundation ready - user story implementation can now begin in parallel
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: User Story 1 - [Title] (Priority: P1) 🎯 MVP
|
||||||
|
|
||||||
|
**Goal**: [Brief description of what this story delivers]
|
||||||
|
|
||||||
|
**Independent Test**: [How to verify this story works on its own]
|
||||||
|
|
||||||
|
### Tests for User Story 1 (OPTIONAL - only if tests requested) ⚠️
|
||||||
|
|
||||||
|
> **NOTE: Write these tests FIRST, ensure they FAIL before implementation**
|
||||||
|
|
||||||
|
- [ ] T010 [P] [US1] Contract test for [endpoint] in tests/contract/test_[name].py
|
||||||
|
- [ ] T011 [P] [US1] Integration test for [user journey] in tests/integration/test_[name].py
|
||||||
|
|
||||||
|
### Implementation for User Story 1
|
||||||
|
|
||||||
|
- [ ] T012 [P] [US1] Create [Entity1] model in src/models/[entity1].py
|
||||||
|
- [ ] T013 [P] [US1] Create [Entity2] model in src/models/[entity2].py
|
||||||
|
- [ ] T014 [US1] Implement [Service] in src/services/[service].py (depends on T012, T013)
|
||||||
|
- [ ] T015 [US1] Implement [endpoint/feature] in src/[location]/[file].py
|
||||||
|
- [ ] T016 [US1] Add validation and error handling
|
||||||
|
- [ ] T017 [US1] Add logging for user story 1 operations
|
||||||
|
|
||||||
|
**Checkpoint**: At this point, User Story 1 should be fully functional and testable independently
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: User Story 2 - [Title] (Priority: P2)
|
||||||
|
|
||||||
|
**Goal**: [Brief description of what this story delivers]
|
||||||
|
|
||||||
|
**Independent Test**: [How to verify this story works on its own]
|
||||||
|
|
||||||
|
### Tests for User Story 2 (OPTIONAL - only if tests requested) ⚠️
|
||||||
|
|
||||||
|
- [ ] T018 [P] [US2] Contract test for [endpoint] in tests/contract/test_[name].py
|
||||||
|
- [ ] T019 [P] [US2] Integration test for [user journey] in tests/integration/test_[name].py
|
||||||
|
|
||||||
|
### Implementation for User Story 2
|
||||||
|
|
||||||
|
- [ ] T020 [P] [US2] Create [Entity] model in src/models/[entity].py
|
||||||
|
- [ ] T021 [US2] Implement [Service] in src/services/[service].py
|
||||||
|
- [ ] T022 [US2] Implement [endpoint/feature] in src/[location]/[file].py
|
||||||
|
- [ ] T023 [US2] Integrate with User Story 1 components (if needed)
|
||||||
|
|
||||||
|
**Checkpoint**: At this point, User Stories 1 AND 2 should both work independently
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: User Story 3 - [Title] (Priority: P3)
|
||||||
|
|
||||||
|
**Goal**: [Brief description of what this story delivers]
|
||||||
|
|
||||||
|
**Independent Test**: [How to verify this story works on its own]
|
||||||
|
|
||||||
|
### Tests for User Story 3 (OPTIONAL - only if tests requested) ⚠️
|
||||||
|
|
||||||
|
- [ ] T024 [P] [US3] Contract test for [endpoint] in tests/contract/test_[name].py
|
||||||
|
- [ ] T025 [P] [US3] Integration test for [user journey] in tests/integration/test_[name].py
|
||||||
|
|
||||||
|
### Implementation for User Story 3
|
||||||
|
|
||||||
|
- [ ] T026 [P] [US3] Create [Entity] model in src/models/[entity].py
|
||||||
|
- [ ] T027 [US3] Implement [Service] in src/services/[service].py
|
||||||
|
- [ ] T028 [US3] Implement [endpoint/feature] in src/[location]/[file].py
|
||||||
|
|
||||||
|
**Checkpoint**: All user stories should now be independently functional
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[Add more user story phases as needed, following the same pattern]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase N: Polish & Cross-Cutting Concerns
|
||||||
|
|
||||||
|
**Purpose**: Improvements that affect multiple user stories
|
||||||
|
|
||||||
|
- [ ] TXXX [P] Documentation updates in docs/
|
||||||
|
- [ ] TXXX Code cleanup and refactoring
|
||||||
|
- [ ] TXXX Performance optimization across all stories
|
||||||
|
- [ ] TXXX [P] Additional unit tests (if requested) in tests/unit/
|
||||||
|
- [ ] TXXX Security hardening
|
||||||
|
- [ ] TXXX Verify customer-visible terminology, empty/error/degraded states, and recovery guidance
|
||||||
|
- [ ] TXXX Verify Workstation/Server tier and entitlement boundaries
|
||||||
|
- [ ] TXXX Verify audit events, diagnostics, and sensitive-data redaction
|
||||||
|
- [ ] TXXX Validate upgrade/rollback and backup/restore impact where applicable
|
||||||
|
- [ ] TXXX Verify packaged copies and ISO provenance where applicable
|
||||||
|
- [ ] TXXX Record static, local, deployment, and live-appliance evidence without overstating coverage
|
||||||
|
- [ ] TXXX Run quickstart.md validation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependencies & Execution Order
|
||||||
|
|
||||||
|
### Phase Dependencies
|
||||||
|
|
||||||
|
- **Setup (Phase 1)**: No dependencies - can start immediately
|
||||||
|
- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories
|
||||||
|
- **User Stories (Phase 3+)**: All depend on Foundational phase completion
|
||||||
|
- User stories can then proceed in parallel (if staffed)
|
||||||
|
- Or sequentially in priority order (P1 → P2 → P3)
|
||||||
|
- **Polish (Final Phase)**: Depends on all desired user stories being complete
|
||||||
|
|
||||||
|
### User Story Dependencies
|
||||||
|
|
||||||
|
- **User Story 1 (P1)**: Can start after Foundational (Phase 2) - No dependencies on other stories
|
||||||
|
- **User Story 2 (P2)**: Can start after Foundational (Phase 2) - May integrate with US1 but should be independently testable
|
||||||
|
- **User Story 3 (P3)**: Can start after Foundational (Phase 2) - May integrate with US1/US2 but should be independently testable
|
||||||
|
|
||||||
|
### Within Each User Story
|
||||||
|
|
||||||
|
- Tests (if included) MUST be written and FAIL before implementation
|
||||||
|
- Models before services
|
||||||
|
- Services before endpoints
|
||||||
|
- Core implementation before integration
|
||||||
|
- Story complete before moving to next priority
|
||||||
|
|
||||||
|
### Parallel Opportunities
|
||||||
|
|
||||||
|
- All Setup tasks marked [P] can run in parallel
|
||||||
|
- All Foundational tasks marked [P] can run in parallel (within Phase 2)
|
||||||
|
- Once Foundational phase completes, all user stories can start in parallel (if team capacity allows)
|
||||||
|
- All tests for a user story marked [P] can run in parallel
|
||||||
|
- Models within a story marked [P] can run in parallel
|
||||||
|
- Different user stories can be worked on in parallel by different team members
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parallel Example: User Story 1
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Launch all tests for User Story 1 together (if tests requested):
|
||||||
|
Task: "Contract test for [endpoint] in tests/contract/test_[name].py"
|
||||||
|
Task: "Integration test for [user journey] in tests/integration/test_[name].py"
|
||||||
|
|
||||||
|
# Launch all models for User Story 1 together:
|
||||||
|
Task: "Create [Entity1] model in src/models/[entity1].py"
|
||||||
|
Task: "Create [Entity2] model in src/models/[entity2].py"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Strategy
|
||||||
|
|
||||||
|
### MVP First (User Story 1 Only)
|
||||||
|
|
||||||
|
1. Complete Phase 1: Setup
|
||||||
|
2. Complete Phase 2: Foundational (CRITICAL - blocks all stories)
|
||||||
|
3. Complete Phase 3: User Story 1
|
||||||
|
4. **STOP and VALIDATE**: Test User Story 1 independently
|
||||||
|
5. Deploy/demo if ready
|
||||||
|
|
||||||
|
### Incremental Delivery
|
||||||
|
|
||||||
|
1. Complete Setup + Foundational → Foundation ready
|
||||||
|
2. Add User Story 1 → Test independently → Deploy/Demo (MVP!)
|
||||||
|
3. Add User Story 2 → Test independently → Deploy/Demo
|
||||||
|
4. Add User Story 3 → Test independently → Deploy/Demo
|
||||||
|
5. Each story adds value without breaking previous stories
|
||||||
|
|
||||||
|
### Parallel Team Strategy
|
||||||
|
|
||||||
|
With multiple developers:
|
||||||
|
|
||||||
|
1. Team completes Setup + Foundational together
|
||||||
|
2. Once Foundational is done:
|
||||||
|
- Developer A: User Story 1
|
||||||
|
- Developer B: User Story 2
|
||||||
|
- Developer C: User Story 3
|
||||||
|
3. Stories complete and integrate independently
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- [P] tasks = different files, no dependencies
|
||||||
|
- [Story] label maps task to specific user story for traceability
|
||||||
|
- Each user story should be independently completable and testable
|
||||||
|
- Verify tests fail before implementing
|
||||||
|
- Commit, push, deploy, or rebuild release artifacts only when explicitly authorized
|
||||||
|
- Stop at any checkpoint to validate story independently
|
||||||
|
- Avoid: vague tasks, same file conflicts, cross-story dependencies that break independence
|
||||||
77
.specify/workflows/speckit/workflow.yml
Normal file
77
.specify/workflows/speckit/workflow.yml
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
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:
|
||||||
|
# 0.8.5 is the first release with engine-side resolution of the
|
||||||
|
# ``integration: "auto"`` default. Older versions would treat "auto"
|
||||||
|
# as a literal integration key and fail at dispatch.
|
||||||
|
speckit_version: ">=0.8.5"
|
||||||
|
integrations:
|
||||||
|
# The four commands below (specify, plan, tasks, implement) are core
|
||||||
|
# spec-kit commands provided by every integration. The list here is an
|
||||||
|
# advisory, non-exhaustive compatibility hint following the documented
|
||||||
|
# ``any: [...]`` schema -- it is NOT a closed set. The workflow runs
|
||||||
|
# against any integration the project was initialized with, including
|
||||||
|
# ones not listed below, as long as that integration provides the four
|
||||||
|
# core commands referenced in ``steps``.
|
||||||
|
any:
|
||||||
|
- "claude"
|
||||||
|
- "copilot"
|
||||||
|
- "gemini"
|
||||||
|
- "opencode"
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
spec:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
prompt: "Describe what you want to build"
|
||||||
|
integration:
|
||||||
|
type: string
|
||||||
|
default: "auto"
|
||||||
|
prompt: "Integration to use (e.g. claude, copilot, gemini; 'auto' uses the project's initialized integration)"
|
||||||
|
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 }}"
|
||||||
13
.specify/workflows/workflow-registry.json
Normal file
13
.specify/workflows/workflow-registry.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"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-07-12T05:32:39.660499+00:00",
|
||||||
|
"updated_at": "2026-07-12T05:32:39.660506+00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -67,6 +67,12 @@ JWT_ALGORITHM = "HS256"
|
|||||||
JWT_EXPIRE_HRS = 8
|
JWT_EXPIRE_HRS = 8
|
||||||
COOKIE_NAME = "cezen_token"
|
COOKIE_NAME = "cezen_token"
|
||||||
OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://localhost:11434")
|
OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://localhost:11434")
|
||||||
|
SCHEMA_VERSION = 1
|
||||||
|
|
||||||
|
CUSTOMER_STATES = {"loading", "empty", "ready", "degraded", "failed", "restricted", "unavailable", "stale"}
|
||||||
|
OPERATION_STATES = {"requested", "validating", "queued", "running", "awaiting_reboot", "succeeded", "failed", "cancelled", "recovery_required"}
|
||||||
|
TERMINAL_OPERATION_STATES = {"succeeded", "failed", "cancelled", "recovery_required"}
|
||||||
|
_AUDIT_SENSITIVE_KEYS = {"password", "token", "secret", "signature", "license_key", "license_json", "override_json", "content"}
|
||||||
|
|
||||||
# Tier is set by Cezen at deployment time via environment variable.
|
# Tier is set by Cezen at deployment time via environment variable.
|
||||||
# When set, it cannot be changed from the portal — customers get read-only visibility.
|
# When set, it cannot be changed from the portal — customers get read-only visibility.
|
||||||
@ -223,6 +229,47 @@ def get_db():
|
|||||||
conn.execute("PRAGMA journal_mode=WAL")
|
conn.execute("PRAGMA journal_mode=WAL")
|
||||||
return conn
|
return conn
|
||||||
|
|
||||||
|
def _column_names(db, table: str) -> set[str]:
|
||||||
|
return {row[1] for row in db.execute(f"PRAGMA table_info({table})").fetchall()}
|
||||||
|
|
||||||
|
def run_migrations(db) -> int:
|
||||||
|
"""Apply additive, idempotent appliance migrations in one transaction."""
|
||||||
|
db.execute("CREATE TABLE IF NOT EXISTS schema_metadata (key TEXT PRIMARY KEY, value TEXT NOT NULL)")
|
||||||
|
row = db.execute("SELECT value FROM schema_metadata WHERE key='schema_version'").fetchone()
|
||||||
|
current = int(row[0]) if row else 0
|
||||||
|
if current > SCHEMA_VERSION:
|
||||||
|
raise RuntimeError(f"Database schema {current} is newer than supported {SCHEMA_VERSION}")
|
||||||
|
if current < 1:
|
||||||
|
db.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS operations (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
correlation_id TEXT NOT NULL UNIQUE,
|
||||||
|
kind TEXT NOT NULL,
|
||||||
|
actor_id INTEGER,
|
||||||
|
actor_name TEXT NOT NULL DEFAULT '',
|
||||||
|
scope TEXT NOT NULL DEFAULT '',
|
||||||
|
state TEXT NOT NULL,
|
||||||
|
progress_percent INTEGER,
|
||||||
|
progress_label TEXT NOT NULL DEFAULT '',
|
||||||
|
requested_at TEXT NOT NULL,
|
||||||
|
started_at TEXT,
|
||||||
|
updated_at TEXT NOT NULL,
|
||||||
|
finished_at TEXT,
|
||||||
|
result_code TEXT NOT NULL DEFAULT '',
|
||||||
|
customer_message TEXT NOT NULL DEFAULT '',
|
||||||
|
recovery_actions TEXT NOT NULL DEFAULT '[]',
|
||||||
|
metadata_json TEXT NOT NULL DEFAULT '{}',
|
||||||
|
idempotency_key TEXT,
|
||||||
|
parent_id TEXT,
|
||||||
|
UNIQUE(kind, idempotency_key)
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
if "correlation_id" not in _column_names(db, "audit_log"):
|
||||||
|
db.execute("ALTER TABLE audit_log ADD COLUMN correlation_id TEXT NOT NULL DEFAULT ''")
|
||||||
|
db.execute("INSERT OR REPLACE INTO schema_metadata (key,value) VALUES ('schema_version','1')")
|
||||||
|
current = 1
|
||||||
|
return current
|
||||||
|
|
||||||
def init_db():
|
def init_db():
|
||||||
db = get_db()
|
db = get_db()
|
||||||
db.executescript("""
|
db.executescript("""
|
||||||
@ -691,6 +738,12 @@ def init_db():
|
|||||||
created_at TEXT NOT NULL
|
created_at TEXT NOT NULL
|
||||||
);
|
);
|
||||||
""")
|
""")
|
||||||
|
try:
|
||||||
|
with db:
|
||||||
|
run_migrations(db)
|
||||||
|
except Exception:
|
||||||
|
db.close()
|
||||||
|
raise
|
||||||
|
|
||||||
# Seed default admin if no users exist
|
# Seed default admin if no users exist
|
||||||
row = db.execute("SELECT COUNT(*) as n FROM users").fetchone()
|
row = db.execute("SELECT COUNT(*) as n FROM users").fetchone()
|
||||||
@ -868,10 +921,36 @@ async def unhandled_exception_handler(request: Request, exc: Exception):
|
|||||||
# the client. This logs the real error server-side and raises a structured,
|
# the client. This logs the real error server-side and raises a structured,
|
||||||
# actionable HTTPException instead — {code, message, remediation} rather than
|
# actionable HTTPException instead — {code, message, remediation} rather than
|
||||||
# a bare string, so the portal can show "what happened / why / how to fix it".
|
# a bare string, so the portal can show "what happened / why / how to fix it".
|
||||||
def raise_api_error(status_code: int, code: str, message: str, remediation: str = "", *, log_context: str = "", log_exc=None):
|
def customer_state(state: str, summary: str, *, severity: str = "info", observed_at: str = "", next_actions=None, code: str = "") -> dict:
|
||||||
|
normalized = str(state or "unavailable").lower().replace("_", "-")
|
||||||
|
if normalized not in CUSTOMER_STATES:
|
||||||
|
normalized = "unavailable"
|
||||||
|
if severity not in {"info", "warning", "critical"}:
|
||||||
|
severity = "info"
|
||||||
|
return {
|
||||||
|
"schema": "cezen.customer_state.v1",
|
||||||
|
"state": normalized,
|
||||||
|
"severity": severity,
|
||||||
|
"summary": str(summary or "Status unavailable"),
|
||||||
|
"observed_at": observed_at or utcnow(),
|
||||||
|
"next_actions": list(next_actions or []),
|
||||||
|
"code": str(code or normalized),
|
||||||
|
}
|
||||||
|
|
||||||
|
def error_envelope(code: str, message: str, remediation: str = "", *, affected_scope: str = "", correlation_id: str = "") -> dict:
|
||||||
|
return {
|
||||||
|
"schema": "cezen.error.v1",
|
||||||
|
"code": str(code),
|
||||||
|
"message": str(message),
|
||||||
|
"remediation": str(remediation),
|
||||||
|
"affected_scope": str(affected_scope),
|
||||||
|
"correlation_id": str(correlation_id),
|
||||||
|
}
|
||||||
|
|
||||||
|
def raise_api_error(status_code: int, code: str, message: str, remediation: str = "", *, log_context: str = "", log_exc=None, affected_scope: str = "", correlation_id: str = ""):
|
||||||
if log_exc is not None:
|
if log_exc is not None:
|
||||||
logger.error("%s: %s", log_context or code, log_exc, exc_info=True)
|
logger.error("%s: %s", log_context or code, log_exc, exc_info=True)
|
||||||
raise HTTPException(status_code=status_code, detail={"code": code, "message": message, "remediation": remediation})
|
raise HTTPException(status_code=status_code, detail=error_envelope(code, message, remediation, affected_scope=affected_scope, correlation_id=correlation_id))
|
||||||
|
|
||||||
# ── Auth helpers ──────────────────────────────────────────────────────────────
|
# ── Auth helpers ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@ -924,25 +1003,85 @@ def admin_only(user: dict = Depends(current_user)) -> dict:
|
|||||||
raise HTTPException(status_code=403, detail="Admin access required")
|
raise HTTPException(status_code=403, detail="Admin access required")
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def audit(db, user_id, username, action, detail="", ip="", result="success"):
|
def _redact_audit_value(value):
|
||||||
|
if isinstance(value, dict):
|
||||||
|
return {str(k): ("[REDACTED]" if str(k).lower() in _AUDIT_SENSITIVE_KEYS else _redact_audit_value(v)) for k, v in value.items()}
|
||||||
|
if isinstance(value, list):
|
||||||
|
return [_redact_audit_value(v) for v in value]
|
||||||
|
return value
|
||||||
|
|
||||||
|
def safe_audit_detail(detail) -> str:
|
||||||
|
if isinstance(detail, (dict, list)):
|
||||||
|
return json.dumps(_redact_audit_value(detail), separators=(",", ":"), sort_keys=True)
|
||||||
|
text = str(detail or "")
|
||||||
|
for key in _AUDIT_SENSITIVE_KEYS:
|
||||||
|
text = __import__("re").sub(rf"(?i)({key}\s*[=:]\s*)([^\s,;]+)", rf"\1[REDACTED]", text)
|
||||||
|
return text[:2000]
|
||||||
|
|
||||||
|
def audit(db, user_id, username, action, detail="", ip="", result="success", correlation_id=""):
|
||||||
db.execute(
|
db.execute(
|
||||||
"INSERT INTO audit_log (timestamp,user_id,username,action,detail,ip_address,result) VALUES (?,?,?,?,?,?,?)",
|
"INSERT INTO audit_log (timestamp,user_id,username,action,detail,ip_address,result,correlation_id) VALUES (?,?,?,?,?,?,?,?)",
|
||||||
(utcnow(), user_id, username, action, detail, ip, result)
|
(utcnow(), user_id, username, action, safe_audit_detail(detail), ip, result, correlation_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _audit(db, username, action, detail="", ip="", result="success", user_id=None):
|
def _audit(db, username, action, detail="", ip="", result="success", user_id=None, correlation_id=""):
|
||||||
"""Audit helper for API sections that do not already hold a DB connection."""
|
"""Audit helper for API sections that do not already hold a DB connection."""
|
||||||
own_db = db is None
|
own_db = db is None
|
||||||
if own_db:
|
if own_db:
|
||||||
db = get_db()
|
db = get_db()
|
||||||
try:
|
try:
|
||||||
audit(db, user_id, username, action, detail, ip, result)
|
audit(db, user_id, username, action, detail, ip, result, correlation_id)
|
||||||
if own_db:
|
if own_db:
|
||||||
db.commit()
|
db.commit()
|
||||||
finally:
|
finally:
|
||||||
if own_db:
|
if own_db:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
def create_operation(kind: str, actor: dict, scope: str = "", *, idempotency_key: str = "", metadata=None) -> dict:
|
||||||
|
if not actor or actor.get("role") != "admin":
|
||||||
|
raise HTTPException(status_code=403, detail=error_envelope("admin_required", "Admin access required", "Sign in with an authorized administrator account."))
|
||||||
|
now = utcnow()
|
||||||
|
db = get_db()
|
||||||
|
try:
|
||||||
|
if idempotency_key:
|
||||||
|
existing = db.execute("SELECT * FROM operations WHERE kind=? AND idempotency_key=?", (kind, idempotency_key)).fetchone()
|
||||||
|
if existing:
|
||||||
|
return dict(existing)
|
||||||
|
operation_id = str(uuid.uuid4())
|
||||||
|
correlation_id = str(uuid.uuid4())
|
||||||
|
db.execute("""INSERT INTO operations
|
||||||
|
(id,correlation_id,kind,actor_id,actor_name,scope,state,requested_at,updated_at,metadata_json,idempotency_key)
|
||||||
|
VALUES (?,?,?,?,?,?,?,?,?,?,?)""",
|
||||||
|
(operation_id, correlation_id, kind, actor.get("id") or actor.get("sub"), actor.get("username", ""), scope,
|
||||||
|
"requested", now, now, json.dumps(_redact_audit_value(metadata or {})), idempotency_key or None))
|
||||||
|
audit(db, actor.get("id") or actor.get("sub"), actor.get("username", ""), "operation_create",
|
||||||
|
{"kind": kind, "scope": scope}, result="success", correlation_id=correlation_id)
|
||||||
|
db.commit()
|
||||||
|
return dict(db.execute("SELECT * FROM operations WHERE id=?", (operation_id,)).fetchone())
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
def transition_operation(operation_id: str, new_state: str, *, progress_percent=None, progress_label: str = "", result_code: str = "", customer_message: str = "", recovery_actions=None) -> dict:
|
||||||
|
if new_state not in OPERATION_STATES:
|
||||||
|
raise ValueError(f"Invalid operation state: {new_state}")
|
||||||
|
db = get_db()
|
||||||
|
try:
|
||||||
|
row = db.execute("SELECT * FROM operations WHERE id=?", (operation_id,)).fetchone()
|
||||||
|
if not row:
|
||||||
|
raise KeyError(operation_id)
|
||||||
|
if row["state"] in TERMINAL_OPERATION_STATES:
|
||||||
|
raise ValueError("Terminal operation cannot transition; create a linked retry")
|
||||||
|
now = utcnow()
|
||||||
|
started_at = row["started_at"] or (now if new_state in {"running", "awaiting_reboot"} else None)
|
||||||
|
finished_at = now if new_state in TERMINAL_OPERATION_STATES else None
|
||||||
|
db.execute("""UPDATE operations SET state=?,progress_percent=?,progress_label=?,started_at=?,updated_at=?,finished_at=?,result_code=?,customer_message=?,recovery_actions=? WHERE id=?""",
|
||||||
|
(new_state, progress_percent, progress_label, started_at, now, finished_at, result_code, customer_message,
|
||||||
|
json.dumps(recovery_actions or []), operation_id))
|
||||||
|
db.commit()
|
||||||
|
return dict(db.execute("SELECT * FROM operations WHERE id=?", (operation_id,)).fetchone())
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
def _setting_value(key: str, default: str = "") -> str:
|
def _setting_value(key: str, default: str = "") -> str:
|
||||||
db = get_db()
|
db = get_db()
|
||||||
try:
|
try:
|
||||||
@ -2036,6 +2175,7 @@ async def reconcile_license(request: Request, admin: dict = Depends(admin_only))
|
|||||||
|
|
||||||
@app.get("/api/system/readiness-report")
|
@app.get("/api/system/readiness-report")
|
||||||
async def readiness_report(admin: dict = Depends(admin_only)):
|
async def readiness_report(admin: dict = Depends(admin_only)):
|
||||||
|
observed_at = utcnow()
|
||||||
feasibility = await system_feasibility(admin)
|
feasibility = await system_feasibility(admin)
|
||||||
license_payload = _tier_payload()
|
license_payload = _tier_payload()
|
||||||
entitlement = _entitlement_summary(feasibility)
|
entitlement = _entitlement_summary(feasibility)
|
||||||
@ -2050,9 +2190,27 @@ async def readiness_report(admin: dict = Depends(admin_only)):
|
|||||||
recommended_rank = list(TIER_MATRIX.keys()).index(_normalize_tier(recommended_tier))
|
recommended_rank = list(TIER_MATRIX.keys()).index(_normalize_tier(recommended_tier))
|
||||||
commercial_fit = "matched" if current_rank >= recommended_rank else "license_upgrade_recommended"
|
commercial_fit = "matched" if current_rank >= recommended_rank else "license_upgrade_recommended"
|
||||||
commercial_fit_note = "Current license covers the recommended deployment." if commercial_fit == "matched" else "Quote a higher tier or reduce enabled features for this hardware."
|
commercial_fit_note = "Current license covers the recommended deployment." if commercial_fit == "matched" else "Quote a higher tier or reduce enabled features for this hardware."
|
||||||
|
raw_status = readiness.get("status")
|
||||||
|
state = "ready" if raw_status == "ready" else "degraded" if raw_status == "limited" else "failed"
|
||||||
|
affected = [str(name) for name in readiness.get("blockers", [])]
|
||||||
|
capability_reasons = {
|
||||||
|
"not_licensed": "Capability is not included in the current license.",
|
||||||
|
"not_provisioned": "Capability is licensed but not installed on this appliance.",
|
||||||
|
"unsupported_hardware": "Capability is not supported by the detected hardware.",
|
||||||
|
"degraded": "Capability is installed but currently degraded.",
|
||||||
|
"unavailable": "Capability is currently unavailable.",
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
"schema": "cezen.readiness_report.v1",
|
"schema": "cezen.readiness_report.v2",
|
||||||
"generated_at": utcnow(),
|
"generated_at": observed_at,
|
||||||
|
"observed_at": observed_at,
|
||||||
|
"freshness_seconds": 0,
|
||||||
|
"state": state,
|
||||||
|
"severity": "info" if state == "ready" else "warning" if state == "degraded" else "critical",
|
||||||
|
"customer_impact": "Appliance is ready for supported use." if state == "ready" else "Some appliance capabilities need attention.",
|
||||||
|
"affected_capabilities": affected,
|
||||||
|
"next_actions": ([{"kind": "review_health", "label": "Review Health Center", "href": "health.html"}] if affected else []),
|
||||||
|
"capability_reasons": capability_reasons,
|
||||||
"customer_mode": "software_only" if (feasibility.get("features") or {}).get("software_only", True) else "appliance",
|
"customer_mode": "software_only" if (feasibility.get("features") or {}).get("software_only", True) else "appliance",
|
||||||
"license": license_payload,
|
"license": license_payload,
|
||||||
"entitlement": entitlement,
|
"entitlement": entitlement,
|
||||||
|
|||||||
@ -43,6 +43,21 @@
|
|||||||
mode: "0640"
|
mode: "0640"
|
||||||
when: cezen_license_file.stat.exists
|
when: cezen_license_file.stat.exists
|
||||||
|
|
||||||
|
- name: Check for existing appliance database before schema migration
|
||||||
|
stat:
|
||||||
|
path: /opt/cezen/data/cezen.db
|
||||||
|
register: cezen_existing_db
|
||||||
|
|
||||||
|
- name: Create pre-deploy database safety copy
|
||||||
|
copy:
|
||||||
|
src: /opt/cezen/data/cezen.db
|
||||||
|
dest: /opt/cezen/data/cezen.db.pre-deploy
|
||||||
|
remote_src: yes
|
||||||
|
owner: "{{ cezen_user }}"
|
||||||
|
group: "{{ cezen_user }}"
|
||||||
|
mode: "0600"
|
||||||
|
when: cezen_existing_db.stat.exists
|
||||||
|
|
||||||
- name: Copy FastAPI application
|
- name: Copy FastAPI application
|
||||||
copy:
|
copy:
|
||||||
src: main.py
|
src: main.py
|
||||||
|
|||||||
@ -12,10 +12,50 @@ LICENSE_CHECK_SCRIPT="$AIPACKAGE_DIR/scripts/cezen-license-check.py"
|
|||||||
PUBLIC_KEY_PATH="${CEZEN_LICENSE_PUBLIC_KEY:-$AIPACKAGE_DIR/autoinstall/keys/cezen-license-public.pem}"
|
PUBLIC_KEY_PATH="${CEZEN_LICENSE_PUBLIC_KEY:-$AIPACKAGE_DIR/autoinstall/keys/cezen-license-public.pem}"
|
||||||
LOG_FILE="/var/log/cezen-setup.log"
|
LOG_FILE="/var/log/cezen-setup.log"
|
||||||
INSTALL_LOG_FILE="/var/log/cezen-install.log"
|
INSTALL_LOG_FILE="/var/log/cezen-install.log"
|
||||||
|
SETUP_OPERATION_FILE="/opt/cezen/setup-operation.json"
|
||||||
export TERM="${TERM:-linux}"
|
export TERM="${TERM:-linux}"
|
||||||
touch "$LOG_FILE" "$INSTALL_LOG_FILE"
|
touch "$LOG_FILE" "$INSTALL_LOG_FILE"
|
||||||
chmod 0644 "$LOG_FILE" "$INSTALL_LOG_FILE"
|
chmod 0644 "$LOG_FILE" "$INSTALL_LOG_FILE"
|
||||||
|
|
||||||
|
write_setup_operation() {
|
||||||
|
local state="$1" progress="$2" label="$3" message="$4"
|
||||||
|
mkdir -p /opt/cezen
|
||||||
|
STATE="$state" PROGRESS="$progress" LABEL="$label" MESSAGE="$message" SETUP_OPERATION_FILE="$SETUP_OPERATION_FILE" python3 - <<'PY'
|
||||||
|
import json, os, time, uuid
|
||||||
|
path = os.environ["SETUP_OPERATION_FILE"]
|
||||||
|
try:
|
||||||
|
with open(path) as fh:
|
||||||
|
payload = json.load(fh)
|
||||||
|
except Exception:
|
||||||
|
payload = {
|
||||||
|
"schema": "cezen.setup_operation.v1",
|
||||||
|
"operation_id": str(uuid.uuid4()),
|
||||||
|
"correlation_id": str(uuid.uuid4()),
|
||||||
|
"idempotency_key": str(uuid.uuid4()),
|
||||||
|
"kind": "install",
|
||||||
|
"acknowledged_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||||
|
}
|
||||||
|
payload.update({
|
||||||
|
"state": os.environ["STATE"],
|
||||||
|
"progress_percent": int(os.environ["PROGRESS"]),
|
||||||
|
"progress_label": os.environ["LABEL"],
|
||||||
|
"customer_message": os.environ["MESSAGE"],
|
||||||
|
"updated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||||
|
"recovery_actions": ["retry", "view_log", "escalate"] if os.environ["STATE"] == "recovery_required" else [],
|
||||||
|
})
|
||||||
|
tmp = path + ".tmp"
|
||||||
|
with open(tmp, "w") as fh:
|
||||||
|
json.dump(payload, fh, indent=2)
|
||||||
|
os.replace(tmp, path)
|
||||||
|
os.chmod(path, 0o640)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -f "$SETUP_OPERATION_FILE" ] && grep -q '"state": "recovery_required"' "$SETUP_OPERATION_FILE"; then
|
||||||
|
whiptail --title "Nexus One AI Setup Recovery" --yesno "A previous setup attempt needs recovery.\n\nChoose Yes to resume setup safely. Choose No to leave the existing state unchanged and inspect $INSTALL_LOG_FILE." 14 72 || exit 1
|
||||||
|
fi
|
||||||
|
write_setup_operation "validating" 0 "Collecting setup choices" "Setup is ready to resume safely if interrupted."
|
||||||
|
|
||||||
detect_iface() {
|
detect_iface() {
|
||||||
ip route show default 2>/dev/null | awk '/default/ {print $5; exit}'
|
ip route show default 2>/dev/null | awk '/default/ {print $5; exit}'
|
||||||
}
|
}
|
||||||
@ -387,6 +427,7 @@ whiptail --title "$TITLE" \
|
|||||||
|
|
||||||
clear
|
clear
|
||||||
echo ""
|
echo ""
|
||||||
|
write_setup_operation "running" 5 "Starting appliance installation" "Installation has started."
|
||||||
echo "╔══════════════════════════════════════════╗"
|
echo "╔══════════════════════════════════════════╗"
|
||||||
echo "║ Nexus One AI — Installing... ║"
|
echo "║ Nexus One AI — Installing... ║"
|
||||||
echo "║ Check progress: journalctl -f ║"
|
echo "║ Check progress: journalctl -f ║"
|
||||||
@ -449,10 +490,12 @@ whiptail --title "$TITLE" \
|
|||||||
if bash "$AIPACKAGE_DIR/install.sh" --tier="$TIER" >> "$INSTALL_LOG_FILE" 2>&1; then
|
if bash "$AIPACKAGE_DIR/install.sh" --tier="$TIER" >> "$INSTALL_LOG_FILE" 2>&1; then
|
||||||
# Mark as configured only after the installer finishes successfully.
|
# Mark as configured only after the installer finishes successfully.
|
||||||
touch /opt/cezen/.setup-done
|
touch /opt/cezen/.setup-done
|
||||||
|
write_setup_operation "succeeded" 100 "Installation completed" "Installation completed successfully."
|
||||||
whiptail --title "$TITLE" \
|
whiptail --title "$TITLE" \
|
||||||
--msgbox "\nInstaller command finished successfully.\n\nPortal -> http://localhost\nOllama API -> http://localhost:11434\nGrafana -> http://localhost:3000\n\nAdmin login -> admin / Cezen@2024!\n(you will be required to change this on first login)\n\nFor detailed logs, run:\n sudo tail -f $INSTALL_LOG_FILE" \
|
--msgbox "\nInstaller command finished successfully.\n\nPortal -> http://localhost\nOllama API -> http://localhost:11434\nGrafana -> http://localhost:3000\n\nAdmin login -> admin / Cezen@2024!\n(you will be required to change this on first login)\n\nFor detailed logs, run:\n sudo tail -f $INSTALL_LOG_FILE" \
|
||||||
$H $W
|
$H $W
|
||||||
else
|
else
|
||||||
|
write_setup_operation "recovery_required" 0 "Installation needs attention" "Installation did not complete. Review the local log, then retry or contact support."
|
||||||
whiptail --title "$TITLE" \
|
whiptail --title "$TITLE" \
|
||||||
--msgbox "\nInstaller command failed.\n\nThe setup wizard will run again on next boot.\n\nCheck the log with:\n sudo tail -n 120 $INSTALL_LOG_FILE" \
|
--msgbox "\nInstaller command failed.\n\nThe setup wizard will run again on next boot.\n\nCheck the log with:\n sudo tail -n 120 $INSTALL_LOG_FILE" \
|
||||||
$H $W
|
$H $W
|
||||||
|
|||||||
@ -24,8 +24,50 @@ FEASIBILITY_JSON = "/opt/cezen/feasibility.json"
|
|||||||
LICENSE_JSON = "/opt/cezen/license.json"
|
LICENSE_JSON = "/opt/cezen/license.json"
|
||||||
OVERRIDE_JSON = "/opt/cezen/license.override.json"
|
OVERRIDE_JSON = "/opt/cezen/license.override.json"
|
||||||
INSTALL_RECORD_JSON = "/opt/cezen/install-record.json"
|
INSTALL_RECORD_JSON = "/opt/cezen/install-record.json"
|
||||||
|
INSTALL_OPERATION_JSON = os.environ.get("CEZEN_SETUP_OPERATION_JSON", "/opt/cezen/setup-operation.json")
|
||||||
PUBLIC_KEY_PATH = os.environ.get("CEZEN_LICENSE_PUBLIC_KEY", f"{AIPACKAGE_DIR}/autoinstall/keys/cezen-license-public.pem")
|
PUBLIC_KEY_PATH = os.environ.get("CEZEN_LICENSE_PUBLIC_KEY", f"{AIPACKAGE_DIR}/autoinstall/keys/cezen-license-public.pem")
|
||||||
|
|
||||||
|
def read_install_operation():
|
||||||
|
return read_json_file(INSTALL_OPERATION_JSON)
|
||||||
|
|
||||||
|
def write_install_operation(operation):
|
||||||
|
operation = dict(operation or {})
|
||||||
|
operation["updated_at"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
||||||
|
write_json_file(INSTALL_OPERATION_JSON, operation)
|
||||||
|
try:
|
||||||
|
os.chmod(INSTALL_OPERATION_JSON, 0o640)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return operation
|
||||||
|
|
||||||
|
def start_install_operation(tier, skip_tools, idempotency_key):
|
||||||
|
existing = read_install_operation()
|
||||||
|
if idempotency_key and existing.get("idempotency_key") == idempotency_key:
|
||||||
|
existing["duplicate_request"] = True
|
||||||
|
return existing, False
|
||||||
|
if existing.get("state") in {"validating", "running", "awaiting_reboot"}:
|
||||||
|
existing["duplicate_request"] = True
|
||||||
|
return existing, False
|
||||||
|
now = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
||||||
|
operation = {
|
||||||
|
"schema": "cezen.setup_operation.v1",
|
||||||
|
"operation_id": str(__import__("uuid").uuid4()),
|
||||||
|
"correlation_id": str(__import__("uuid").uuid4()),
|
||||||
|
"idempotency_key": idempotency_key or str(__import__("uuid").uuid4()),
|
||||||
|
"kind": "install",
|
||||||
|
"state": "validating",
|
||||||
|
"tier": tier,
|
||||||
|
"skip_tools": list(skip_tools or []),
|
||||||
|
"progress_percent": 0,
|
||||||
|
"progress_label": "Validating installation",
|
||||||
|
"acknowledged_at": now,
|
||||||
|
"updated_at": now,
|
||||||
|
"duplicate_request": False,
|
||||||
|
"customer_message": "Installation request accepted.",
|
||||||
|
"recovery_actions": [],
|
||||||
|
}
|
||||||
|
return write_install_operation(operation), True
|
||||||
|
|
||||||
# ─── Helpers ──────────────────────────────────────────────
|
# ─── Helpers ──────────────────────────────────────────────
|
||||||
def get_ip():
|
def get_ip():
|
||||||
try:
|
try:
|
||||||
@ -173,10 +215,14 @@ def write_install_record(license_data, tier, skip_tools, preview):
|
|||||||
subprocess.run(["chown", "root:cezen", INSTALL_RECORD_JSON], check=False)
|
subprocess.run(["chown", "root:cezen", INSTALL_RECORD_JSON], check=False)
|
||||||
os.chmod(INSTALL_RECORD_JSON, 0o640)
|
os.chmod(INSTALL_RECORD_JSON, 0o640)
|
||||||
|
|
||||||
def run_install(tier, skip_tools, license_data=None, preview=None):
|
def run_install(tier, skip_tools, license_data=None, preview=None, operation_id=""):
|
||||||
global install_status
|
global install_status
|
||||||
install_status = {"running": True, "done": False, "error": None}
|
install_status = {"running": True, "done": False, "error": None}
|
||||||
try:
|
try:
|
||||||
|
operation = read_install_operation()
|
||||||
|
if operation.get("operation_id") == operation_id:
|
||||||
|
operation.update({"state": "running", "progress_percent": 5, "progress_label": "Preparing appliance"})
|
||||||
|
write_install_operation(operation)
|
||||||
# Write config so phase 2 (post-reboot) knows what to skip
|
# Write config so phase 2 (post-reboot) knows what to skip
|
||||||
os.makedirs("/opt/cezen", exist_ok=True)
|
os.makedirs("/opt/cezen", exist_ok=True)
|
||||||
skip_str = ",".join(skip_tools) if skip_tools else ""
|
skip_str = ",".join(skip_tools) if skip_tools else ""
|
||||||
@ -192,14 +238,25 @@ def run_install(tier, skip_tools, license_data=None, preview=None):
|
|||||||
# Fresh NVIDIA servers do not have nvidia-smi yet, so detect the PCI
|
# Fresh NVIDIA servers do not have nvidia-smi yet, so detect the PCI
|
||||||
# device and run phase 1 to install drivers before the AI stack.
|
# device and run phase 1 to install drivers before the AI stack.
|
||||||
phase = "1" if has_nvidia_gpu() else "2"
|
phase = "1" if has_nvidia_gpu() else "2"
|
||||||
|
if operation.get("operation_id") == operation_id and phase == "1":
|
||||||
|
operation.update({"state": "awaiting_reboot", "progress_percent": 40, "progress_label": "Installing drivers before reboot"})
|
||||||
|
write_install_operation(operation)
|
||||||
cmd = ["bash", f"{AIPACKAGE_DIR}/install.sh", f"--phase={phase}", f"--tier={tier}"]
|
cmd = ["bash", f"{AIPACKAGE_DIR}/install.sh", f"--phase={phase}", f"--tier={tier}"]
|
||||||
with open(INSTALL_LOG, "w") as log:
|
with open(INSTALL_LOG, "w") as log:
|
||||||
proc = subprocess.Popen(cmd, stdout=log, stderr=log, env=env)
|
proc = subprocess.Popen(cmd, stdout=log, stderr=log, env=env)
|
||||||
proc.wait()
|
proc.wait()
|
||||||
# Reaches here only if no reboot happened (e.g. no GPU / drivers already installed)
|
# Reaches here only if no reboot happened (e.g. no GPU / drivers already installed)
|
||||||
install_status = {"running": False, "done": True, "error": None}
|
install_status = {"running": False, "done": True, "error": None}
|
||||||
|
operation = read_install_operation()
|
||||||
|
if operation.get("operation_id") == operation_id:
|
||||||
|
operation.update({"state": "succeeded", "progress_percent": 100, "progress_label": "Installation command completed", "customer_message": "Installation completed successfully."})
|
||||||
|
write_install_operation(operation)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
install_status = {"running": False, "done": False, "error": str(e)}
|
install_status = {"running": False, "done": False, "error": str(e)}
|
||||||
|
operation = read_install_operation()
|
||||||
|
if operation.get("operation_id") == operation_id:
|
||||||
|
operation.update({"state": "recovery_required", "progress_label": "Installation needs attention", "result_code": "install_failed", "customer_message": "Installation did not complete. Review the local log and retry or contact support.", "recovery_actions": ["retry", "view_log", "escalate"]})
|
||||||
|
write_install_operation(operation)
|
||||||
|
|
||||||
# ─── HTML UI ──────────────────────────────────────────────
|
# ─── HTML UI ──────────────────────────────────────────────
|
||||||
HTML = r"""<!DOCTYPE html>
|
HTML = r"""<!DOCTYPE html>
|
||||||
@ -592,6 +649,7 @@ let tools = {
|
|||||||
window.onload = () => {
|
window.onload = () => {
|
||||||
fetch('/api/status').then(r=>r.json()).then(d=>{
|
fetch('/api/status').then(r=>r.json()).then(d=>{
|
||||||
document.getElementById('current-ip').textContent = d.ip || 'unknown';
|
document.getElementById('current-ip').textContent = d.ip || 'unknown';
|
||||||
|
if (d.operation && ['validating','running','awaiting_reboot','recovery_required'].includes(d.operation.state)) resumeOperation(d.operation);
|
||||||
});
|
});
|
||||||
renderTools();
|
renderTools();
|
||||||
renderTierAvailability(null);
|
renderTierAvailability(null);
|
||||||
@ -749,10 +807,11 @@ function startInstall() {
|
|||||||
fetch('/api/install', {
|
fetch('/api/install', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type':'application/json'},
|
headers: {'Content-Type':'application/json'},
|
||||||
body: JSON.stringify({ tier: selectedTier, skip_tools: skip, license: collectLicense() })
|
body: JSON.stringify({ tier: selectedTier, skip_tools: skip, license: collectLicense(), idempotency_key: sessionStorage.getItem('cezen-install-key') || (() => { const k = (crypto.randomUUID ? crypto.randomUUID() : String(Date.now())); sessionStorage.setItem('cezen-install-key', k); return k; })() })
|
||||||
}).then(async r => {
|
}).then(async r => {
|
||||||
const data = await r.json();
|
const data = await r.json();
|
||||||
if (!r.ok || !data.ok) throw new Error(data.error || 'Install start failed');
|
if (!r.ok || !data.ok) throw new Error(data.error || 'Install start failed');
|
||||||
|
window.currentOperationId = data.operation_id;
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
showBanner(err.message, 'error');
|
showBanner(err.message, 'error');
|
||||||
document.getElementById('install-btn-row').classList.remove('hidden');
|
document.getElementById('install-btn-row').classList.remove('hidden');
|
||||||
@ -763,6 +822,17 @@ function startInstall() {
|
|||||||
streamLog();
|
streamLog();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resumeOperation(operation) {
|
||||||
|
window.currentOperationId = operation.operation_id;
|
||||||
|
document.getElementById('install-btn-row').classList.add('hidden');
|
||||||
|
document.getElementById('summary-card').classList.add('hidden');
|
||||||
|
document.getElementById('progress-wrap').classList.add('show');
|
||||||
|
document.getElementById('progress-label').textContent = operation.progress_label || 'Resuming installation status…';
|
||||||
|
document.getElementById('progress-bar').style.width = (operation.progress_percent || 0) + '%';
|
||||||
|
if (operation.state === 'recovery_required') showBanner(operation.customer_message || 'Installation needs attention. Retry or contact support.', 'error');
|
||||||
|
else streamLog();
|
||||||
|
}
|
||||||
|
|
||||||
function streamLog() {
|
function streamLog() {
|
||||||
const log = document.getElementById('log-box');
|
const log = document.getElementById('log-box');
|
||||||
const bar = document.getElementById('progress-bar');
|
const bar = document.getElementById('progress-bar');
|
||||||
@ -890,6 +960,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
"interfaces": get_interfaces(),
|
"interfaces": get_interfaces(),
|
||||||
"setup_done": os.path.exists(SETUP_DONE_FILE),
|
"setup_done": os.path.exists(SETUP_DONE_FILE),
|
||||||
"install_status": install_status,
|
"install_status": install_status,
|
||||||
|
"operation": read_install_operation(),
|
||||||
})
|
})
|
||||||
|
|
||||||
elif path == "/api/progress":
|
elif path == "/api/progress":
|
||||||
@ -961,6 +1032,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
tier = body.get("tier", "basic")
|
tier = body.get("tier", "basic")
|
||||||
skip = body.get("skip_tools", [])
|
skip = body.get("skip_tools", [])
|
||||||
license_data = body.get("license", {})
|
license_data = body.get("license", {})
|
||||||
|
idempotency_key = str(body.get("idempotency_key") or "").strip()
|
||||||
try:
|
try:
|
||||||
preview = evaluate_install_constraints(license_data or {})
|
preview = evaluate_install_constraints(license_data or {})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -970,12 +1042,13 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
if not selected or not selected.get("selectable"):
|
if not selected or not selected.get("selectable"):
|
||||||
self.send_json({"ok": False, "error": f"Tier '{tier}' is not allowed for this license/hardware."}, 400)
|
self.send_json({"ok": False, "error": f"Tier '{tier}' is not allowed for this license/hardware."}, 400)
|
||||||
return
|
return
|
||||||
if not install_status["running"]:
|
operation, created = start_install_operation(tier, skip, idempotency_key)
|
||||||
t = threading.Thread(target=run_install, args=(tier, skip, license_data, preview), daemon=True)
|
if created and not install_status["running"]:
|
||||||
|
t = threading.Thread(target=run_install, args=(tier, skip, license_data, preview, operation["operation_id"]), daemon=True)
|
||||||
t.start()
|
t.start()
|
||||||
self.send_json({"ok": True})
|
self.send_json({"ok": True, "operation_id": operation["operation_id"], "acknowledged_at": operation["acknowledged_at"], "duplicate_request": False})
|
||||||
else:
|
else:
|
||||||
self.send_json({"ok": False, "error": "Install already running"})
|
self.send_json({"ok": True, "operation_id": operation.get("operation_id"), "acknowledged_at": operation.get("acknowledged_at"), "duplicate_request": True})
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.send_response(404)
|
self.send_response(404)
|
||||||
|
|||||||
@ -292,12 +292,13 @@ async function api(path, opts={}) {
|
|||||||
function renderReadiness(d) {
|
function renderReadiness(d) {
|
||||||
readinessData = d;
|
readinessData = d;
|
||||||
const score = d.readiness?.score ?? 0;
|
const score = d.readiness?.score ?? 0;
|
||||||
const status = d.readiness?.status || 'unknown';
|
const status = d.state || d.readiness?.status || 'unavailable';
|
||||||
const entitlement = d.entitlement || d.license?.entitlement || {};
|
const entitlement = d.entitlement || d.license?.entitlement || {};
|
||||||
document.getElementById('readiness-score').textContent = score + '%';
|
document.getElementById('readiness-score').textContent = score + '%';
|
||||||
const statusEl = document.getElementById('readiness-status');
|
const statusEl = document.getElementById('readiness-status');
|
||||||
statusEl.textContent = status.replace(/_/g, ' ');
|
if (window.CezenExperience) window.CezenExperience.renderState(statusEl, {state: status, severity: d.severity, summary: status.replace(/_/g, ' ')});
|
||||||
statusEl.className = 'pill ' + (status === 'ready' ? 'ok' : status === 'limited' ? 'warn' : 'bad');
|
else statusEl.textContent = status.replace(/_/g, ' ');
|
||||||
|
statusEl.title = window.CezenExperience ? window.CezenExperience.formatFreshness(d.observed_at) : '';
|
||||||
document.getElementById('license-tier').textContent = d.license?.label || '—';
|
document.getElementById('license-tier').textContent = d.license?.label || '—';
|
||||||
document.getElementById('license-position').textContent = `${d.license?.positioning || '—'} · status: ${licenseStatusLabel(entitlement.license_status || d.license?.license_status || 'missing')}`;
|
document.getElementById('license-position').textContent = `${d.license?.positioning || '—'} · status: ${licenseStatusLabel(entitlement.license_status || d.license?.license_status || 'missing')}`;
|
||||||
|
|
||||||
@ -457,6 +458,7 @@ function exportReadiness() {
|
|||||||
loadAll();
|
loadAll();
|
||||||
</script>
|
</script>
|
||||||
<script src="auth.js"></script>
|
<script src="auth.js"></script>
|
||||||
|
<script src="experience.js?v=1"></script>
|
||||||
<script src="branding.js?v=3"></script>
|
<script src="branding.js?v=3"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -50,6 +50,22 @@
|
|||||||
max: 'Server Max',
|
max: 'Server Max',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var PRODUCT_VOCABULARY = Object.freeze({
|
||||||
|
companyAttribution: 'Powered by Cezen',
|
||||||
|
workstation: 'Nexus One AI Workstation',
|
||||||
|
server: 'Nexus One AI Server',
|
||||||
|
tiers: Object.freeze({ starter: 'Server S', basic: 'Server M', pro: 'Server L', max: 'Server Max' }),
|
||||||
|
capabilityReasons: Object.freeze({
|
||||||
|
licensed: 'Included in the current license',
|
||||||
|
not_licensed: 'Not included in the current license',
|
||||||
|
not_provisioned: 'Licensed but not installed on this appliance',
|
||||||
|
unsupported_hardware: 'Not supported by the detected hardware',
|
||||||
|
degraded: 'Temporarily degraded',
|
||||||
|
unavailable: 'Currently unavailable'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
window.NexusOneVocabulary = PRODUCT_VOCABULARY;
|
||||||
|
|
||||||
var SERVICE_PORTS = {
|
var SERVICE_PORTS = {
|
||||||
'3001': { name: 'open-webui', label: 'Open WebUI' },
|
'3001': { name: 'open-webui', label: 'Open WebUI' },
|
||||||
'11434': { name: 'ollama', label: 'Ollama' },
|
'11434': { name: 'ollama', label: 'Ollama' },
|
||||||
|
|||||||
70
cezen-portal/experience.js
Normal file
70
cezen-portal/experience.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/** Shared, dependency-free customer experience primitives for Nexus One AI. */
|
||||||
|
(function (global) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var STATES = ['loading', 'empty', 'ready', 'degraded', 'failed', 'restricted', 'unavailable', 'stale'];
|
||||||
|
var SEVERITIES = ['info', 'warning', 'critical'];
|
||||||
|
|
||||||
|
function canonicalState(value) {
|
||||||
|
value = String(value || '').toLowerCase().replace(/[_ ]+/g, '-');
|
||||||
|
var aliases = { ok: 'ready', healthy: 'ready', limited: 'degraded', warning: 'degraded', error: 'failed', critical: 'failed', not_ready: 'failed' };
|
||||||
|
value = aliases[value] || value;
|
||||||
|
return STATES.indexOf(value) >= 0 ? value : 'unavailable';
|
||||||
|
}
|
||||||
|
|
||||||
|
function announce(message, priority) {
|
||||||
|
var id = priority === 'assertive' ? 'cezen-live-assertive' : 'cezen-live-polite';
|
||||||
|
var region = document.getElementById(id);
|
||||||
|
if (!region) {
|
||||||
|
region = document.createElement('div');
|
||||||
|
region.id = id;
|
||||||
|
region.className = 'sr-only';
|
||||||
|
region.setAttribute('aria-live', priority === 'assertive' ? 'assertive' : 'polite');
|
||||||
|
region.setAttribute('aria-atomic', 'true');
|
||||||
|
document.body.appendChild(region);
|
||||||
|
}
|
||||||
|
region.textContent = '';
|
||||||
|
global.setTimeout(function () { region.textContent = String(message || ''); }, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderState(element, payload) {
|
||||||
|
if (!element) return;
|
||||||
|
payload = payload || {};
|
||||||
|
var state = canonicalState(payload.state || payload.status);
|
||||||
|
var severity = SEVERITIES.indexOf(payload.severity) >= 0 ? payload.severity : (state === 'failed' ? 'critical' : state === 'degraded' || state === 'stale' ? 'warning' : 'info');
|
||||||
|
element.classList.remove.apply(element.classList, STATES.map(function (item) { return 'cx-state-' + item; }));
|
||||||
|
element.classList.add('cx-state', 'cx-state-' + state);
|
||||||
|
element.setAttribute('data-state', state);
|
||||||
|
element.setAttribute('data-severity', severity);
|
||||||
|
element.setAttribute('role', severity === 'critical' ? 'alert' : 'status');
|
||||||
|
element.textContent = payload.summary || state.replace(/-/g, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatFreshness(observedAt, now) {
|
||||||
|
if (!observedAt) return 'Freshness unknown';
|
||||||
|
var observed = new Date(observedAt).getTime();
|
||||||
|
var current = now ? new Date(now).getTime() : Date.now();
|
||||||
|
if (!isFinite(observed)) return 'Freshness unknown';
|
||||||
|
var seconds = Math.max(0, Math.round((current - observed) / 1000));
|
||||||
|
if (seconds < 60) return 'Observed ' + seconds + ' seconds ago';
|
||||||
|
if (seconds < 3600) return 'Observed ' + Math.floor(seconds / 60) + ' minutes ago';
|
||||||
|
return 'Observed ' + Math.floor(seconds / 3600) + ' hours ago';
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusFirstError(container) {
|
||||||
|
var target = (container || document).querySelector('[aria-invalid="true"], .field-error, .cx-error');
|
||||||
|
if (!target) return false;
|
||||||
|
if (!target.hasAttribute('tabindex')) target.setAttribute('tabindex', '-1');
|
||||||
|
target.focus();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
global.CezenExperience = Object.freeze({
|
||||||
|
states: STATES.slice(),
|
||||||
|
canonicalState: canonicalState,
|
||||||
|
announce: announce,
|
||||||
|
renderState: renderState,
|
||||||
|
formatFreshness: formatFreshness,
|
||||||
|
focusFirstError: focusFirstError
|
||||||
|
});
|
||||||
|
})(window);
|
||||||
@ -245,6 +245,11 @@ function renderSummary(status) {
|
|||||||
const dot = document.getElementById('hc-summary-dot');
|
const dot = document.getElementById('hc-summary-dot');
|
||||||
const title = document.getElementById('hc-summary-title');
|
const title = document.getElementById('hc-summary-title');
|
||||||
const sub = document.getElementById('hc-summary-sub');
|
const sub = document.getElementById('hc-summary-sub');
|
||||||
|
if (window.CezenExperience) {
|
||||||
|
const mapped = status === 'ok' ? 'ready' : status === 'warning' ? 'degraded' : status === 'critical' ? 'failed' : 'unavailable';
|
||||||
|
title.className = 'hc-summary-title cx-state cx-state-' + mapped;
|
||||||
|
title.setAttribute('role', mapped === 'failed' ? 'alert' : 'status');
|
||||||
|
}
|
||||||
if (status === 'ok') {
|
if (status === 'ok') {
|
||||||
dot.className = 'hc-summary-dot healthy';
|
dot.className = 'hc-summary-dot healthy';
|
||||||
title.textContent = 'Healthy — all systems normal';
|
title.textContent = 'Healthy — all systems normal';
|
||||||
@ -284,6 +289,7 @@ loadHealth();
|
|||||||
setInterval(loadHealth, 30000);
|
setInterval(loadHealth, 30000);
|
||||||
</script>
|
</script>
|
||||||
<script src="auth.js"></script>
|
<script src="auth.js"></script>
|
||||||
|
<script src="experience.js?v=1"></script>
|
||||||
<script src="branding.js?v=3"></script>
|
<script src="branding.js?v=3"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -817,11 +817,32 @@ async function fetchSystemInfo() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchApplianceReadiness() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/system/readiness-report', { credentials: 'include' });
|
||||||
|
if (!response.ok) return;
|
||||||
|
const data = await response.json();
|
||||||
|
const label = document.getElementById('status-overall-label');
|
||||||
|
const checked = document.getElementById('status-last-checked');
|
||||||
|
if (label) {
|
||||||
|
label.textContent = data.customer_impact || (data.state === 'ready' ? 'Appliance ready' : 'Appliance needs attention');
|
||||||
|
label.setAttribute('role', data.severity === 'critical' ? 'alert' : 'status');
|
||||||
|
}
|
||||||
|
if (checked && window.CezenExperience) checked.textContent = window.CezenExperience.formatFreshness(data.observed_at);
|
||||||
|
if (data.next_actions && data.next_actions.length && window.CezenExperience) window.CezenExperience.announce(label.textContent, data.severity === 'critical' ? 'assertive' : 'polite');
|
||||||
|
} catch (e) {
|
||||||
|
/* Existing service checks remain visible when readiness aggregation is unavailable. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fetchSystemInfo();
|
fetchSystemInfo();
|
||||||
fetchMetrics();
|
fetchMetrics();
|
||||||
|
fetchApplianceReadiness();
|
||||||
setInterval(fetchMetrics, 30000);
|
setInterval(fetchMetrics, 30000);
|
||||||
|
setInterval(fetchApplianceReadiness, 30000);
|
||||||
</script>
|
</script>
|
||||||
<script src="auth.js"></script>
|
<script src="auth.js"></script>
|
||||||
|
<script src="experience.js?v=1"></script>
|
||||||
<script src="branding.js?v=3"></script>
|
<script src="branding.js?v=3"></script>
|
||||||
<script>
|
<script>
|
||||||
function renderLocalIcons() {
|
function renderLocalIcons() {
|
||||||
|
|||||||
@ -640,6 +640,7 @@ checkStepCompletion();
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="auth.js"></script>
|
<script src="auth.js"></script>
|
||||||
|
<script src="experience.js?v=1"></script>
|
||||||
<script src="branding.js?v=3"></script>
|
<script src="branding.js?v=3"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -159,6 +159,26 @@ a { color: inherit; text-decoration: none; }
|
|||||||
font-size: .6875rem; font-weight: 600; letter-spacing: .02em;
|
font-size: .6875rem; font-weight: 600; letter-spacing: .02em;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Shared enterprise state and accessibility primitives. */
|
||||||
|
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
|
||||||
|
outline: 3px solid var(--brand);
|
||||||
|
outline-offset: 2px;
|
||||||
|
box-shadow: var(--shadow-focus);
|
||||||
|
}
|
||||||
|
.cx-state { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--border); border-radius: var(--radius-pill); padding: 3px 9px; font-weight: 600; }
|
||||||
|
.cx-state::before { content: ''; width: 8px; height: 8px; border-radius: 50%; background: currentColor; }
|
||||||
|
.cx-state-ready { color: var(--green); background: var(--green-lt); border-color: var(--green-border); }
|
||||||
|
.cx-state-degraded, .cx-state-stale { color: var(--amber); background: var(--amber-lt); border-color: var(--amber-border); }
|
||||||
|
.cx-state-failed { color: var(--red); background: var(--red-lt); border-color: var(--red-border); }
|
||||||
|
.cx-state-loading, .cx-state-empty, .cx-state-restricted, .cx-state-unavailable { color: var(--text-secondary); background: var(--surface2); }
|
||||||
|
.cx-alert { border: 1px solid var(--border); border-radius: var(--radius-md); padding: 12px 14px; }
|
||||||
|
.cx-alert[data-severity="critical"] { border-color: var(--red-border); background: var(--red-lt); }
|
||||||
|
.cx-alert[data-severity="warning"] { border-color: var(--amber-border); background: var(--amber-lt); }
|
||||||
|
.cx-error { color: var(--red); font-weight: 600; }
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*, *::before, *::after { scroll-behavior: auto !important; transition-duration: .01ms !important; animation-duration: .01ms !important; animation-iteration-count: 1 !important; }
|
||||||
|
}
|
||||||
.badge-pill.violet { background: var(--brand-lt); color: var(--brand-text); border: 1px solid var(--brand-border); }
|
.badge-pill.violet { background: var(--brand-lt); color: var(--brand-text); border: 1px solid var(--brand-border); }
|
||||||
.badge-pill.green { background: var(--green-lt); color: var(--green); border: 1px solid var(--green-border); }
|
.badge-pill.green { background: var(--green-lt); color: var(--green); border: 1px solid var(--green-border); }
|
||||||
.badge-pill.amber { background: var(--amber-lt); color: var(--amber); border: 1px solid var(--amber-border); }
|
.badge-pill.amber { background: var(--amber-lt); color: var(--amber); border: 1px solid var(--amber-border); }
|
||||||
|
|||||||
@ -369,6 +369,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="ts-issue" data-tags="setup interrupted resume recovery install">
|
||||||
|
<div class="ts-issue-header" onclick="toggleIssue(this)"><div class="ts-issue-title">Setup or installation was interrupted</div><span class="ts-chevron">›</span></div>
|
||||||
|
<div class="ts-issue-body"><p>Return to the setup page. Nexus One AI preserves a local operation record and shows whether setup can resume, should be retried, or needs support.</p><ol><li>Do not delete <code>/opt/cezen/setup-operation.json</code>.</li><li>Review <code>/var/log/cezen-install.log</code> for the last completed phase.</li><li>Use the displayed retry or recovery action. If it remains in <strong>Needs attention</strong>, contact support with the operation/correlation ID.</li></ol></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ts-issue" data-tags="readiness stale degraded unavailable offline">
|
||||||
|
<div class="ts-issue-header" onclick="toggleIssue(this)"><div class="ts-issue-title">Readiness is degraded, unavailable, or stale</div><span class="ts-chevron">›</span></div>
|
||||||
|
<div class="ts-issue-body"><p>Open <a href="health.html">Health Center</a> and review affected capabilities and observation time. Stale means the last observation is too old to claim current health; it does not automatically mean the appliance has failed.</p><p>This guidance is stored locally and does not require public internet access.</p></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
@ -418,6 +427,7 @@ document.querySelector('.ts-issue-header').click();
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="auth.js"></script>
|
<script src="auth.js"></script>
|
||||||
|
<script src="experience.js?v=1"></script>
|
||||||
<script src="branding.js?v=3"></script>
|
<script src="branding.js?v=3"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
36
specs/001-enterprise-experience/checklists/requirements.md
Normal file
36
specs/001-enterprise-experience/checklists/requirements.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# Specification Quality Checklist: Enterprise Appliance Experience Hardening
|
||||||
|
|
||||||
|
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||||
|
**Created**: 2026-07-12
|
||||||
|
**Feature**: [spec.md](../spec.md)
|
||||||
|
|
||||||
|
## Content Quality
|
||||||
|
|
||||||
|
- [x] No implementation details (languages, frameworks, APIs)
|
||||||
|
- [x] Focused on user value and business needs
|
||||||
|
- [x] Written for non-technical stakeholders
|
||||||
|
- [x] All mandatory sections completed
|
||||||
|
|
||||||
|
## Requirement Completeness
|
||||||
|
|
||||||
|
- [x] No `[NEEDS CLARIFICATION]` markers remain
|
||||||
|
- [x] Requirements are testable and unambiguous
|
||||||
|
- [x] Success criteria are measurable
|
||||||
|
- [x] Success criteria are technology-agnostic (no implementation details)
|
||||||
|
- [x] All acceptance scenarios are defined
|
||||||
|
- [x] Edge cases are identified
|
||||||
|
- [x] Scope is clearly bounded
|
||||||
|
- [x] Dependencies and assumptions identified
|
||||||
|
|
||||||
|
## Feature Readiness
|
||||||
|
|
||||||
|
- [x] All functional requirements have clear acceptance criteria
|
||||||
|
- [x] User scenarios cover primary flows
|
||||||
|
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||||
|
- [x] No implementation details leak into specification
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Validation completed on 2026-07-12.
|
||||||
|
- Revalidated after five clarification decisions on 2026-07-12; all 16 items remain passing.
|
||||||
|
- The clarification pass is complete and the specification is ready for `$speckit-plan`.
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
# Customer Experience Contract
|
||||||
|
|
||||||
|
This contract defines observable behaviour. Exact endpoint paths may remain compatible with existing
|
||||||
|
routes or be introduced additively during implementation.
|
||||||
|
|
||||||
|
## Common Response State
|
||||||
|
|
||||||
|
Every customer-facing state-bearing response provides:
|
||||||
|
|
||||||
|
- canonical `state` and `severity`;
|
||||||
|
- `observed_at` and explicit freshness when not live;
|
||||||
|
- plain-language `summary` and customer impact;
|
||||||
|
- stable non-secret `code` for support correlation;
|
||||||
|
- ordered `next_actions`, filtered by authorization;
|
||||||
|
- `correlation_id` when an operation or failure is involved.
|
||||||
|
|
||||||
|
Raw exceptions, stack traces, signed license material, credentials, tokens, document content, and
|
||||||
|
secret-bearing command output MUST NOT appear.
|
||||||
|
|
||||||
|
## Readiness Interface
|
||||||
|
|
||||||
|
The readiness interface returns the ApplianceReadiness model. It MUST distinguish:
|
||||||
|
|
||||||
|
- product/API unreachable from a degraded dependent capability;
|
||||||
|
- licensed from provisioned, hardware-supported, and currently available;
|
||||||
|
- ready from stale last-known-ready;
|
||||||
|
- customer impact from technical diagnostic detail.
|
||||||
|
|
||||||
|
Phase 1 may extend the existing readiness-report route additively. Existing fields remain until all
|
||||||
|
packaged consumers move to the versioned contract.
|
||||||
|
|
||||||
|
## Operation Interface
|
||||||
|
|
||||||
|
Supported behaviours:
|
||||||
|
|
||||||
|
1. **Create/acknowledge**: validates authorization, prerequisites, conflicts, scope, and confirmation.
|
||||||
|
2. **Read**: returns durable current state by operation ID after refresh, restart, or session change.
|
||||||
|
3. **List**: returns role-filtered recent and active operations.
|
||||||
|
4. **Act**: exposes only valid retry/resume/cancel/recovery actions for the current state.
|
||||||
|
|
||||||
|
Creation MUST be idempotent for duplicate customer submissions within the defined request window.
|
||||||
|
Conflicting operations return a customer-facing explanation and reference the blocking operation.
|
||||||
|
Every operation interface enforces server-side authorization and is tested for both permitted and
|
||||||
|
denied roles. Removal additionally exposes retained/deleted data categories and preserves backups by
|
||||||
|
default unless a separately confirmed policy explicitly requires deletion.
|
||||||
|
|
||||||
|
## Error Contract
|
||||||
|
|
||||||
|
Errors provide status, stable `code`, plain-language `message`, `affected_scope`, safe
|
||||||
|
`remediation`, and optional `correlation_id`. Validation errors identify fields without discarding
|
||||||
|
other valid setup progress. Authorization errors do not reveal protected resource existence.
|
||||||
|
|
||||||
|
## Entitlement Contract
|
||||||
|
|
||||||
|
Customer-facing output uses Workstation and Server S/M/L/Max labels. Compatibility identifiers may
|
||||||
|
appear only in authorized diagnostics. Every capability state identifies one primary reason:
|
||||||
|
licensed, not licensed, not provisioned, unsupported by hardware, degraded, or unavailable.
|
||||||
|
|
||||||
|
## Audit Contract
|
||||||
|
|
||||||
|
Every privileged, security-sensitive, licensing, configuration, and lifecycle action records actor,
|
||||||
|
time, category, redacted scope, result, and correlation ID. Secret values and signed payloads are
|
||||||
|
never recorded. Denied and failed actions are auditable where safe.
|
||||||
|
|
||||||
|
## Accessibility Contract
|
||||||
|
|
||||||
|
- All functionality is operable by keyboard with visible focus and logical order.
|
||||||
|
- Dynamic status and validation feedback is programmatically announced without stealing focus.
|
||||||
|
- Labels, instructions, errors, and confirmations are programmatically associated.
|
||||||
|
- Status and severity never rely on colour alone.
|
||||||
|
- Content supports required contrast, zoom, reflow, and reduced-motion behaviour.
|
||||||
|
- Acceptance requires automated and representative manual WCAG 2.2 AA evidence.
|
||||||
|
|
||||||
|
## Release Evidence Contract
|
||||||
|
|
||||||
|
Each phase records source revision, package/ISO identity and checksum, test environment, product
|
||||||
|
configuration, validation results, known limitations, pilot findings, resolutions, and joint approval
|
||||||
|
from product ownership and delivery/support.
|
||||||
114
specs/001-enterprise-experience/data-model.md
Normal file
114
specs/001-enterprise-experience/data-model.md
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
# Data Model: Enterprise Appliance Experience Hardening
|
||||||
|
|
||||||
|
## ApplianceReadiness
|
||||||
|
|
||||||
|
Customer-facing aggregate of appliance state.
|
||||||
|
|
||||||
|
| Field | Rules |
|
||||||
|
|---|---|
|
||||||
|
| `schema_version` | Required contract version |
|
||||||
|
| `state` | One of loading, empty, ready, degraded, failed, restricted, unavailable, stale |
|
||||||
|
| `severity` | info, warning, critical |
|
||||||
|
| `observed_at` | UTC timestamp for underlying observation |
|
||||||
|
| `freshness_seconds` | Non-negative age at response time |
|
||||||
|
| `summary` | Plain-language customer impact |
|
||||||
|
| `affected_capabilities` | Customer-facing capability identifiers, not raw service names |
|
||||||
|
| `next_actions` | Ordered safe actions with required role and action kind |
|
||||||
|
| `checks` | Component/capability observations using the same state contract |
|
||||||
|
| `entitlement` | Reference to current EntitlementState summary |
|
||||||
|
|
||||||
|
State precedence: failed/critical impact outranks degraded; stale MUST be reported when freshness is
|
||||||
|
outside the defined check window even if the previous observation was ready.
|
||||||
|
|
||||||
|
## Operation
|
||||||
|
|
||||||
|
Durable record for installation and consequential or long-running actions.
|
||||||
|
|
||||||
|
| Field | Rules |
|
||||||
|
|---|---|
|
||||||
|
| `id` | Stable opaque identifier |
|
||||||
|
| `correlation_id` | Stable across API, audit, logs, and support evidence |
|
||||||
|
| `kind` | setup, install, backup, restore, upgrade, restart, remove, reconcile, diagnostic_export, other |
|
||||||
|
| `actor_id` / `actor_name` | Authenticated actor or explicit system/installer identity |
|
||||||
|
| `scope` | Redacted description of affected appliance resources |
|
||||||
|
| `state` | requested, validating, queued, running, awaiting_reboot, succeeded, failed, cancelled, recovery_required |
|
||||||
|
| `progress_percent` | 0–100 only when meaningful; otherwise omitted |
|
||||||
|
| `progress_label` | Plain-language current phase |
|
||||||
|
| `requested_at`, `started_at`, `updated_at`, `finished_at` | UTC timestamps |
|
||||||
|
| `result_code` | Stable non-secret machine-readable result |
|
||||||
|
| `customer_message` | Plain-language outcome and impact |
|
||||||
|
| `recovery_actions` | Safe retry/resume/rollback/restore/restart/escalate actions |
|
||||||
|
| `metadata` | Allowlisted, versioned, non-secret details only |
|
||||||
|
|
||||||
|
Transitions are monotonic except explicit retry, which creates a new Operation linked to its parent.
|
||||||
|
Terminal states are succeeded, failed, cancelled, and recovery_required. Concurrent conflicting
|
||||||
|
operations MUST be rejected or queued with an explanation.
|
||||||
|
|
||||||
|
Removal operation metadata includes an allowlisted retained-data inventory, deleted-data categories,
|
||||||
|
backup preservation status, and rollback limitations; it never includes protected content values.
|
||||||
|
|
||||||
|
## EntitlementState
|
||||||
|
|
||||||
|
| Field | Rules |
|
||||||
|
|---|---|
|
||||||
|
| `category` | workstation or server |
|
||||||
|
| `commercial_label` | Workstation or Server S/M/L/Max |
|
||||||
|
| `compatibility_tier` | workstation or starter/basic/pro/max; not primary customer text |
|
||||||
|
| `license_state` | valid, missing, expired, invalid, mismatch, other stable states |
|
||||||
|
| `licensed_capabilities` | Capability keys from signed entitlement evaluation |
|
||||||
|
| `provisioned_capabilities` | Derived from install record and deployed roles |
|
||||||
|
| `hardware_capabilities` | Derived from feasibility evidence |
|
||||||
|
| `available_capabilities` | Current intersection adjusted for runtime health |
|
||||||
|
| `observed_at` | UTC evaluation time |
|
||||||
|
|
||||||
|
The model MUST distinguish why a capability is unavailable: commercial entitlement, provisioning,
|
||||||
|
hardware, or current runtime condition.
|
||||||
|
|
||||||
|
## AuditEvent
|
||||||
|
|
||||||
|
| Field | Rules |
|
||||||
|
|---|---|
|
||||||
|
| `id` | Monotonic appliance-local identifier |
|
||||||
|
| `occurred_at` | UTC timestamp |
|
||||||
|
| `actor_id` / `actor_name` | Actor identity when known |
|
||||||
|
| `action_category` | Stable category rather than secret-bearing free text |
|
||||||
|
| `scope` | Redacted affected resource |
|
||||||
|
| `result` | success, failure, denied, partial |
|
||||||
|
| `correlation_id` | Links to Operation and support evidence |
|
||||||
|
| `detail` | Allowlisted, redacted context |
|
||||||
|
|
||||||
|
## SupportEvidencePackage
|
||||||
|
|
||||||
|
Logical export; raw secret-bearing files are excluded by default.
|
||||||
|
|
||||||
|
| Field | Rules |
|
||||||
|
|---|---|
|
||||||
|
| `package_id` | Opaque identifier |
|
||||||
|
| `created_at` / `created_by` | UTC time and authorized actor |
|
||||||
|
| `product_identity` | Product category, commercial label, version/build identity |
|
||||||
|
| `entitlement_summary` | Status and capability summary without signed payload or key |
|
||||||
|
| `readiness_snapshot` | ApplianceReadiness contract |
|
||||||
|
| `operations` | Selected redacted Operation records |
|
||||||
|
| `audit_events` | Selected redacted AuditEvent records |
|
||||||
|
| `component_evidence` | Allowlisted health/version/status data |
|
||||||
|
| `redaction_report` | Categories excluded or transformed |
|
||||||
|
| `checksum` | Integrity checksum of the completed export |
|
||||||
|
|
||||||
|
## GuidanceItem
|
||||||
|
|
||||||
|
| Field | Rules |
|
||||||
|
|---|---|
|
||||||
|
| `id` | Stable local identifier |
|
||||||
|
| `journey` | setup, readiness, administration, recovery, support |
|
||||||
|
| `trigger_state` | Optional state or result code |
|
||||||
|
| `required_role` | Minimum role permitted to execute actions |
|
||||||
|
| `title`, `summary`, `steps` | Plain-language local guidance |
|
||||||
|
| `action_kind` | navigate, retry, resume, rollback, restore, restart, escalate |
|
||||||
|
| `version` | Guidance revision aligned to packaged product |
|
||||||
|
|
||||||
|
## Relationships
|
||||||
|
|
||||||
|
- ApplianceReadiness references one current EntitlementState and zero or more active Operations.
|
||||||
|
- Operation produces AuditEvents and may be included in a SupportEvidencePackage.
|
||||||
|
- SupportEvidencePackage contains snapshots, never live mutable references.
|
||||||
|
- GuidanceItems are selected by journey, state, role, and result code.
|
||||||
46
specs/001-enterprise-experience/evidence/phase1-manifest.md
Normal file
46
specs/001-enterprise-experience/evidence/phase1-manifest.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# Phase 1 Evidence Manifest
|
||||||
|
|
||||||
|
**Status**: Test foundation established; runtime implementation not yet accepted.
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
- Git revision: record at validation time
|
||||||
|
- Working-tree status: record at validation time
|
||||||
|
- Feature: `001-enterprise-experience`
|
||||||
|
- Product coverage: one representative Workstation and one representative Server
|
||||||
|
|
||||||
|
## Required Evidence
|
||||||
|
|
||||||
|
| Gate | Evidence | Status |
|
||||||
|
|---|---|---|
|
||||||
|
| Specification | Requirements checklist 16/16 | Complete |
|
||||||
|
| Test foundation | Isolated Python, browser, deployment and fixture guidance | Complete |
|
||||||
|
| Migration | Versioned migration and rollback design | Complete |
|
||||||
|
| Backend contracts | State, error, operation, audit, authorization and entitlement tests | Complete |
|
||||||
|
| Browser journeys | Setup, interruption, readiness and WCAG evidence | Pending |
|
||||||
|
| Workstation | Clean install and recovery evidence | Pending |
|
||||||
|
| Server | Clean install and recovery evidence | Pending |
|
||||||
|
| Package provenance | Source-to-package and ISO checksum evidence | Pending |
|
||||||
|
| Pilot | Controlled pilot evidence | Not authorized |
|
||||||
|
| Approval | Product owner plus delivery/support | Pending |
|
||||||
|
|
||||||
|
## Review Boundary
|
||||||
|
|
||||||
|
The test harness adds only `tests/` and specification evidence files. It does not require runtime
|
||||||
|
product changes. Runtime work begins only after the harness and contract tests are reviewable.
|
||||||
|
|
||||||
|
## Foundational Contract Evidence
|
||||||
|
|
||||||
|
- Versioned, idempotent schema migration creates durable operations and audit correlation fields.
|
||||||
|
- Newer unsupported schemas are rejected before feature mutation.
|
||||||
|
- Customer-state and error envelopes expose stable, non-secret fields.
|
||||||
|
- Operation kinds and idempotency constraints are durable in SQLite.
|
||||||
|
- New operation creation requires an administrator identity server-side.
|
||||||
|
- Structured and text audit detail tests redact secret categories.
|
||||||
|
- Canonical and deployed license helpers are byte-identical.
|
||||||
|
- Isolated backend contract suite passed using synthetic in-memory SQLite on 2026-07-12.
|
||||||
|
|
||||||
|
## Artifact Record
|
||||||
|
|
||||||
|
Record source revision, ISO/package path, build timestamp, size, SHA-256, included portal/backend/
|
||||||
|
installer revisions, environment, results, known limitations, and reviewer for each candidate.
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
# Phase 1 Provenance Check — Partial
|
||||||
|
|
||||||
|
**Checked**: 2026-07-12
|
||||||
|
|
||||||
|
## Source State
|
||||||
|
|
||||||
|
- Local repository HEAD before uncommitted Phase 1 work: `b3eec341586551023b05db7a82bf19446186c768`
|
||||||
|
- Phase 1 implementation is currently uncommitted and therefore has no immutable release revision.
|
||||||
|
- Working tree contains the intended Spec Kit, test, backend, installer and portal changes plus the
|
||||||
|
pre-existing unrelated untracked `how-it-flows.gif`.
|
||||||
|
|
||||||
|
## Local ISO Evidence
|
||||||
|
|
||||||
|
| Path | Size | SHA-256 | Result |
|
||||||
|
|---|---:|---|---|
|
||||||
|
| `cezen-ai-ubuntu2204.iso` | 0 bytes | `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855` | Placeholder; invalid evidence |
|
||||||
|
| `autoinstall/cezen-ai-ubuntu2204.iso` | 0 bytes | `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855` | Placeholder; invalid evidence |
|
||||||
|
|
||||||
|
No local Workstation ISO artifact exists. Build scripts remain separate for Server and Workstation.
|
||||||
|
|
||||||
|
## Build-Host Check
|
||||||
|
|
||||||
|
Authenticated read-only verification succeeded against `cezen@172.16.10.145` at
|
||||||
|
`/home/cezen/aipackage/cgit`. No remote state changed.
|
||||||
|
|
||||||
|
| Artifact | Size | Timestamp (UTC) | SHA-256 |
|
||||||
|
|---|---:|---|---|
|
||||||
|
| `autoinstall/cezen-ai-ubuntu2204.iso` | 2,136,397,824 bytes | 2026-07-09 05:52:52 | `bb2d0ab26c34f179dba9e34e8610d8a17763f8d2e38f948900509bfb8fd83b5d` |
|
||||||
|
| `cezen-ai-ubuntu2204.iso` | 2,136,397,824 bytes | 2026-07-09 05:53:40 | `bb2d0ab26c34f179dba9e34e8610d8a17763f8d2e38f948900509bfb8fd83b5d` |
|
||||||
|
|
||||||
|
No `cezen-ai-workstation-ubuntu2204.iso` artifact exists in either checked build-host location.
|
||||||
|
The host is Ubuntu with approximately 8.0 GB free on `/`; capacity must be monitored during rebuild.
|
||||||
|
The existing Server ISO predates the uncommitted Phase 1 implementation and is not valid provenance
|
||||||
|
evidence for this feature.
|
||||||
|
|
||||||
|
## Remaining Evidence
|
||||||
|
|
||||||
|
- After explicit sync/rebuild authorization, sync the accepted source and build Server and Workstation
|
||||||
|
artifacts from that exact revision.
|
||||||
|
- Prove the artifact contains the accepted portal, backend, installer, deployment and license copies.
|
||||||
|
- Complete representative Workstation and Server install/recovery journeys.
|
||||||
|
|
||||||
|
T036, T037 and T038 remain incomplete.
|
||||||
|
|
||||||
|
## Authorized Rebuild Results — 2026-07-12
|
||||||
|
|
||||||
|
Current uncommitted source was synchronized to `/home/cezen/aipackage/cgit` with Git metadata,
|
||||||
|
credentials, caches, unrelated `how-it-flows.gif`, and existing ISO binaries excluded. Both builds
|
||||||
|
completed and their internal `md5sum.txt` manifests verified successfully.
|
||||||
|
|
||||||
|
| Artifact | Size | Timestamp (UTC) | SHA-256 |
|
||||||
|
|---|---:|---|---|
|
||||||
|
| `autoinstall/cezen-ai-ubuntu2204.iso` | 2,136,397,824 bytes | 2026-07-12 06:37:08 | `b7ba156e9eae6de5595fa85354468836d582db093d1c3cb85eceb9caaca5dfcf` |
|
||||||
|
| `autoinstall/cezen-ai-workstation-ubuntu2204.iso` | 2,136,399,872 bytes | 2026-07-12 06:39:55 | `46c0ec277a711e7075082e362bcf5f5882812e8cbf9baf6ca35c4b5afc68e171` |
|
||||||
|
|
||||||
|
The older root-level `cezen-ai-ubuntu2204.iso` remains unchanged with SHA-256
|
||||||
|
`bb2d0ab26c34f179dba9e34e8610d8a17763f8d2e38f948900509bfb8fd83b5d`; it MUST NOT be confused with
|
||||||
|
the rebuilt Server artifact.
|
||||||
|
|
||||||
|
### Provenance limitation
|
||||||
|
|
||||||
|
Both online installer images clone `https://cgit.cezentech.com/jinojose/aipackage.git` during first
|
||||||
|
boot. The Phase 1 implementation is still uncommitted and unpushed, so the rebuilt ISO boot layer is
|
||||||
|
current but a real install would still pull the older remote package. T038 cannot pass until the
|
||||||
|
accepted source is committed/pushed and artifact/source evidence is rechecked. T036–T037 also remain
|
||||||
|
pending real representative installs.
|
||||||
26
specs/001-enterprise-experience/migrations.md
Normal file
26
specs/001-enterprise-experience/migrations.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# SQLite Migration and Rollback Procedure
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
1. Read the current schema version before application startup writes new data.
|
||||||
|
2. Create a verified pre-migration backup of the database and required companion state.
|
||||||
|
3. Apply sequential idempotent migrations in one exclusive transaction where SQLite permits.
|
||||||
|
4. Record the new version only after all statements and validation checks succeed.
|
||||||
|
5. On failure, roll back the transaction, preserve the original database and record a redacted error.
|
||||||
|
6. Never downgrade destructively. Restore the pre-migration backup when rollback requires an older
|
||||||
|
application version.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
- Fresh database reaches the latest version.
|
||||||
|
- Each supported previous version upgrades exactly once.
|
||||||
|
- Re-running the migration is a no-op.
|
||||||
|
- Interrupted/failed migration leaves a usable prior database or verified recovery copy.
|
||||||
|
- Existing users, sessions, audit history, licensing state, settings and supported customer data
|
||||||
|
remain readable.
|
||||||
|
- Backup compatibility is checked before any restore mutation.
|
||||||
|
|
||||||
|
## Rollback Evidence
|
||||||
|
|
||||||
|
Record source and target schema versions, backup path/checksum, start/end time, result, correlation ID,
|
||||||
|
validation outcome, and recovery action without storing secrets or customer content.
|
||||||
171
specs/001-enterprise-experience/plan.md
Normal file
171
specs/001-enterprise-experience/plan.md
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
# Implementation Plan: Enterprise Appliance Experience Hardening
|
||||||
|
|
||||||
|
**Branch**: `001-enterprise-experience` | **Date**: 2026-07-12 | **Spec**: [spec.md](spec.md)
|
||||||
|
|
||||||
|
**Input**: Feature specification from `specs/001-enterprise-experience/spec.md`
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Harden Nexus One AI through three independently accepted phases: setup/readiness, daily
|
||||||
|
administration, and recovery/support. Preserve the current static portal, FastAPI management API,
|
||||||
|
SQLite appliance data, shell/Ansible installer, and license compatibility model. Introduce shared
|
||||||
|
experience contracts for truthful states, durable operations, redacted support evidence, consistent
|
||||||
|
entitlements, accessibility, and release provenance rather than replacing the product architecture.
|
||||||
|
|
||||||
|
## Technical Context
|
||||||
|
|
||||||
|
**Language/Version**: Python 3.11; browser-native HTML/CSS/JavaScript; Bash; Ansible YAML
|
||||||
|
|
||||||
|
**Primary Dependencies**: FastAPI 0.111+, Uvicorn 0.29+, python-jose, passlib/bcrypt, psutil,
|
||||||
|
APScheduler, Nginx, systemd, Ansible, Ubuntu autoinstall
|
||||||
|
|
||||||
|
**Storage**: SQLite at `/opt/cezen/data/cezen.db`; JSON appliance records under `/opt/cezen`;
|
||||||
|
filesystem backups under `/opt/cezen/backups`; systemd journal and install logs
|
||||||
|
|
||||||
|
**Testing**: Python unit/contract tests to be added around backend state and redaction; browser
|
||||||
|
journey and accessibility checks; shell/static validation; Ansible syntax checks; clean-install,
|
||||||
|
upgrade, interruption, backup/restore, pilot, and packaged-artifact verification
|
||||||
|
|
||||||
|
**Target Platform**: Nexus One AI Workstation and Server appliances on Ubuntu 22.04, accessed through
|
||||||
|
supported desktop browsers on customer-controlled networks
|
||||||
|
|
||||||
|
**Project Type**: On-premises appliance package containing a static web portal, management API,
|
||||||
|
installer, deployment automation, system services, and ISO build paths
|
||||||
|
|
||||||
|
**Performance Goals**: Readiness visible within 10 seconds of portal entry under normal appliance
|
||||||
|
conditions; local navigation feedback within 1 second; operation acknowledgement within 2 seconds;
|
||||||
|
progress freshness appropriate to the underlying operation and never presented as live when stale
|
||||||
|
|
||||||
|
**Constraints**: Restricted/offline operation after installation; existing `starter/basic/pro/max`
|
||||||
|
compatibility identifiers; Workstation remains outside the Server tier ladder; no fabricated runtime
|
||||||
|
state; secrets excluded from diagnostics; WCAG 2.2 AA; safe migration of existing SQLite appliances;
|
||||||
|
no unsupported HA or recovery claims
|
||||||
|
|
||||||
|
**Scale/Scope**: Approximately 60 static portal pages, one large FastAPI service, two first-boot
|
||||||
|
interfaces, four Server tiers plus Workstation packaging, multiple Ansible roles, and three ISO build
|
||||||
|
paths. Phase 1 is limited to setup/readiness on one representative Workstation and one representative
|
||||||
|
Server configuration.
|
||||||
|
|
||||||
|
## Constitution Check
|
||||||
|
|
||||||
|
*GATE: Passed before Phase 0 and re-checked after Phase 1 design.*
|
||||||
|
|
||||||
|
- **Enterprise experience — PASS**: The spec and UI contract define primary, loading, empty, ready,
|
||||||
|
degraded, failed, restricted, stale, and recovery states with consistent terminology.
|
||||||
|
- **Lifecycle safety — PASS**: Operation state is durable; setup, interruption, backup/restore,
|
||||||
|
upgrade, retry, resume, rollback, and escalation responsibilities are explicitly mapped.
|
||||||
|
- **Security and audit — PASS**: Existing server-side authorization is preserved; new audit and
|
||||||
|
support-evidence contracts require redaction, actor, scope, result, and correlation identifiers.
|
||||||
|
- **Entitlement and packaging — PASS**: The plan preserves internal identifiers, keeps Workstation
|
||||||
|
separate, and requires portal/API/installer/Ansible/documentation/ISO consistency evidence.
|
||||||
|
- **Evidence — PASS**: Quickstart validation distinguishes static, local, deployed, live-appliance,
|
||||||
|
pilot, and release-artifact evidence.
|
||||||
|
|
||||||
|
Post-design re-check: PASS. No constitutional exception is required.
|
||||||
|
|
||||||
|
## Phased Design
|
||||||
|
|
||||||
|
### Phase 1 — Setup and Readiness
|
||||||
|
|
||||||
|
1. Establish canonical customer-facing state, severity, entitlement, operation, and error vocabulary.
|
||||||
|
2. Make browser and console setup persist non-secret progress and expose safe resume/retry behaviour.
|
||||||
|
3. Replace process-local install status as the sole truth with a durable operation record.
|
||||||
|
4. Normalize readiness into a customer-impact view with freshness and actionable next steps.
|
||||||
|
5. Align Workstation and representative Server setup/readiness behaviour.
|
||||||
|
6. Validate WCAG 2.2 AA, clean install, interruption, restricted-network guidance, and artifact
|
||||||
|
provenance before pilot.
|
||||||
|
|
||||||
|
### Phase 2 — Daily Administration
|
||||||
|
|
||||||
|
1. Introduce shared portal presentation helpers for status, errors, operations, entitlement, and
|
||||||
|
accessible announcements while retaining static delivery.
|
||||||
|
2. Apply the common shell and state contract first to dashboard, appliance, health, licensing,
|
||||||
|
users/security, models, and knowledge journeys.
|
||||||
|
3. Remove fabricated production examples or label them explicitly as samples.
|
||||||
|
4. Provide a unified operations history for consequential and long-running actions.
|
||||||
|
5. Add role-aware orientation and locally available contextual guidance.
|
||||||
|
6. Complete the daily-administration portions of entitlement consistency and guided handover.
|
||||||
|
|
||||||
|
### Phase 3 — Recovery and Support
|
||||||
|
|
||||||
|
1. Model backup, restore, restart, upgrade, removal, diagnostic export, and escalation as authorized,
|
||||||
|
durable operations.
|
||||||
|
2. Add redacted support-evidence generation and correlation across API, audit, install, and service
|
||||||
|
state.
|
||||||
|
3. Define recovery preconditions, customer impact, confirmation, progress, outcome, and next action.
|
||||||
|
4. Validate upgrade/interruption/rollback paths and controlled pilot results before general release.
|
||||||
|
|
||||||
|
Entitlement consistency is a cross-cutting workstream spanning all three delivery phases. Guided
|
||||||
|
handover is accepted within Delivery Phase 2. These workstreams do not create additional delivery
|
||||||
|
phases or alter the approved order.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
### Documentation (this feature)
|
||||||
|
|
||||||
|
```text
|
||||||
|
specs/001-enterprise-experience/
|
||||||
|
├── spec.md
|
||||||
|
├── plan.md
|
||||||
|
├── research.md
|
||||||
|
├── data-model.md
|
||||||
|
├── quickstart.md
|
||||||
|
├── contracts/
|
||||||
|
│ └── customer-experience-contract.md
|
||||||
|
└── checklists/
|
||||||
|
└── requirements.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Source Code (repository root)
|
||||||
|
|
||||||
|
```text
|
||||||
|
autoinstall/
|
||||||
|
├── websetup/server.py # browser setup and current process-local install state
|
||||||
|
├── firstboot-setup.sh # console setup flow
|
||||||
|
├── user-data* # ISO-specific first-boot entrypoints
|
||||||
|
└── build-iso*.sh # Workstation/Server ISO build paths
|
||||||
|
|
||||||
|
cezen-portal/
|
||||||
|
├── branding.js # shared branding/entitlement behaviour
|
||||||
|
├── auth.js # session and role enforcement
|
||||||
|
├── style.css # shared design/accessibility primitives
|
||||||
|
├── index.html # primary landing experience
|
||||||
|
├── appliance.html # readiness, license, audit, backup/restore
|
||||||
|
├── health.html # subsystem health experience
|
||||||
|
└── *.html # remaining administration and user journeys
|
||||||
|
|
||||||
|
ansible/roles/cezen-backend/
|
||||||
|
├── files/main.py # API, SQLite model, audit, readiness, backups
|
||||||
|
├── files/cezen_license.py # deployed entitlement helper
|
||||||
|
├── files/cezen-api.service # runtime contract
|
||||||
|
└── tasks/main.yml # backend deployment
|
||||||
|
|
||||||
|
ansible/roles/cezen-nginx/ # static portal deployment and routing
|
||||||
|
scripts/cezen_license.py # canonical source entitlement helper
|
||||||
|
scripts/cezen-backup.sh # appliance backup/restore utility
|
||||||
|
install.sh # phase orchestration and install record
|
||||||
|
ansible/{starter,entry,pro,max}.yml # Server deployment profiles
|
||||||
|
```
|
||||||
|
|
||||||
|
**Structure Decision**: Extend the existing appliance package in place. Keep the static portal and
|
||||||
|
single management service for this initiative; extract shared modules only where needed to enforce
|
||||||
|
one state/operation contract. Treat `scripts/cezen_license.py` as the canonical entitlement source
|
||||||
|
and keep deployed copies synchronized through explicit verification.
|
||||||
|
|
||||||
|
## Migration and Compatibility Strategy
|
||||||
|
|
||||||
|
- Additive SQLite changes MUST use idempotent migrations executed before new reads/writes; startup
|
||||||
|
`CREATE TABLE IF NOT EXISTS` alone is insufficient for column evolution.
|
||||||
|
- Existing endpoints remain compatible during each phase. New envelope fields are additive until all
|
||||||
|
packaged portal consumers migrate.
|
||||||
|
- Existing install and license JSON fields remain readable. New operation/progress data is separate
|
||||||
|
and excludes raw signed license payloads.
|
||||||
|
- Existing backups remain restorable; schema version and compatibility are checked before mutation.
|
||||||
|
- Removal preserves backups by default, explicitly inventories retained/deleted data, and records the
|
||||||
|
limits of rollback before confirmation.
|
||||||
|
- Portal, API, installer, canonical/deployed license helpers, Ansible roles, docs, and ISO contents are
|
||||||
|
compared during release validation.
|
||||||
|
|
||||||
|
## Complexity Tracking
|
||||||
|
|
||||||
|
No constitutional violations require justification.
|
||||||
115
specs/001-enterprise-experience/quickstart.md
Normal file
115
specs/001-enterprise-experience/quickstart.md
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
# Validation Quickstart: Enterprise Appliance Experience Hardening
|
||||||
|
|
||||||
|
This guide defines the evidence path for implementation. Commands are representative and MUST be
|
||||||
|
updated to the final test tooling before acceptance.
|
||||||
|
|
||||||
|
## 1. Prerequisites
|
||||||
|
|
||||||
|
- Clean checkout of the intended source revision.
|
||||||
|
- One representative Nexus One AI Workstation target.
|
||||||
|
- One representative Nexus One AI Server target with a valid supported tier.
|
||||||
|
- Test licenses for valid, missing/staging, expired, invalid-signature, and mismatch states.
|
||||||
|
- At least 10 representative administrator participants across internal validation and pilot.
|
||||||
|
- A minimum 20-case customer-correctable failure corpus covering all specified failure categories.
|
||||||
|
- Ability to simulate network, service, storage, and reboot interruption safely.
|
||||||
|
- Supported desktop browser plus keyboard-only and representative assistive-technology setup.
|
||||||
|
- Controlled pilot approval and rollback plan.
|
||||||
|
|
||||||
|
Never use production customer secrets or content in validation fixtures.
|
||||||
|
|
||||||
|
## 2. Static and Local Validation
|
||||||
|
|
||||||
|
1. Run Python syntax and focused unit/contract tests for readiness, entitlement, operation transitions,
|
||||||
|
audit redaction, backup compatibility, and error envelopes.
|
||||||
|
2. Run shell syntax checks for changed installer and ISO scripts.
|
||||||
|
3. Run Ansible syntax checks for every affected Workstation/Server deployment path.
|
||||||
|
4. Run portal checks for broken navigation, legacy naming, placeholder/sample data, inaccessible names,
|
||||||
|
keyboard traps, contrast, and invalid markup.
|
||||||
|
5. Confirm canonical and deployed license helpers produce identical entitlement results for the test
|
||||||
|
matrix.
|
||||||
|
6. Verify allowed and denied roles for every new operation, recovery, removal, diagnostic, and
|
||||||
|
support-evidence interface.
|
||||||
|
7. Run readiness, navigation-feedback, and operation-acknowledgement timing trials against SC-014,
|
||||||
|
SC-015, and SC-016 and record conditions, sample count, and percentile results.
|
||||||
|
|
||||||
|
Expected outcome: all checks pass; any intentionally retained sample content is explicitly labelled;
|
||||||
|
no signed license payload, token, credential, or customer content appears in outputs.
|
||||||
|
|
||||||
|
## 3. Phase 1 Clean-Install Journeys
|
||||||
|
|
||||||
|
Run the following on both the representative Workstation and Server:
|
||||||
|
|
||||||
|
1. Start from the supported clean image or installation baseline.
|
||||||
|
2. Complete browser setup with valid network, customer, license, product/tier, and component choices.
|
||||||
|
3. Repeat the supported console setup path.
|
||||||
|
4. Verify progress survives browser refresh, session change, and setup-service restart.
|
||||||
|
5. Verify the resulting readiness view identifies product, entitlement, freshness, degraded states,
|
||||||
|
and correct next actions.
|
||||||
|
6. Confirm locally available setup and recovery guidance works without public internet access.
|
||||||
|
|
||||||
|
Expected outcome: the administrator reaches a truthful ready or explicitly degraded state without
|
||||||
|
undocumented intervention; Workstation never appears as a Server tier.
|
||||||
|
|
||||||
|
## 4. Phase 1 Failure and Recovery Matrix
|
||||||
|
|
||||||
|
For each applicable setup stage, simulate invalid input, network loss, service failure, storage
|
||||||
|
shortage, license failure, interruption before reboot, interruption after reboot, and stale status.
|
||||||
|
|
||||||
|
Verify:
|
||||||
|
|
||||||
|
- completed safe steps and non-secret inputs are retained;
|
||||||
|
- the operation has one durable identity and truthful lifecycle state;
|
||||||
|
- duplicate/conflicting submissions are prevented;
|
||||||
|
- error text explains impact and safe retry/resume/recovery/escalation;
|
||||||
|
- audit and diagnostic evidence share the correlation ID;
|
||||||
|
- no raw exception or secret appears in the customer experience.
|
||||||
|
|
||||||
|
## 5. Accessibility Validation
|
||||||
|
|
||||||
|
On every changed setup and portal page:
|
||||||
|
|
||||||
|
1. Complete the primary and failure-recovery journey using keyboard only.
|
||||||
|
2. Verify focus order, visible focus, modal/confirmation behaviour, and return focus.
|
||||||
|
3. Verify status, progress, errors, and completion are announced appropriately.
|
||||||
|
4. Check contrast, non-colour cues, zoom/reflow, accessible names, headings, and landmarks.
|
||||||
|
5. Run the selected automated WCAG checks and manually review failures or unsupported criteria.
|
||||||
|
|
||||||
|
Expected outcome: no unresolved WCAG 2.2 Level A or AA failure at phase acceptance.
|
||||||
|
|
||||||
|
## 6. Upgrade and Backup Compatibility
|
||||||
|
|
||||||
|
1. Create a verified backup from the supported pre-feature appliance version.
|
||||||
|
2. Upgrade or reconfigure using the planned migration path.
|
||||||
|
3. Confirm existing authentication, users, audit history, licensing, settings, and supported customer
|
||||||
|
data remain usable.
|
||||||
|
4. Exercise backup and restore with schema compatibility checks and a pre-restore safety snapshot.
|
||||||
|
5. Interrupt the operation at safe test points and verify documented recovery.
|
||||||
|
6. Exercise removal in a disposable environment; verify explicit retained/deleted data inventory,
|
||||||
|
backup preservation, authorization, audit evidence, interruption outcome, and rollback limits.
|
||||||
|
|
||||||
|
Expected outcome: no silent data loss; incompatible restore is blocked before mutation; recovery
|
||||||
|
evidence is sufficient for support.
|
||||||
|
|
||||||
|
## 7. Package and ISO Provenance
|
||||||
|
|
||||||
|
For each delivery candidate, record:
|
||||||
|
|
||||||
|
- source revision and working-tree status;
|
||||||
|
- portal, backend, installer, Ansible, entitlement helper, and local guidance versions;
|
||||||
|
- ISO/package filename, build time, size, and SHA-256 checksum;
|
||||||
|
- proof that the artifact contains the accepted source copies;
|
||||||
|
- successful representative boot/install journey.
|
||||||
|
|
||||||
|
Expected outcome: a release is never called current based only on source state or filename.
|
||||||
|
|
||||||
|
## 8. Pilot and Acceptance
|
||||||
|
|
||||||
|
1. Complete internal evidence review on Workstation and Server.
|
||||||
|
2. Deploy only to approved controlled-pilot environments using the rollback plan.
|
||||||
|
3. Capture task completion, recovery results, accessibility findings, support effort, and customer
|
||||||
|
feedback without collecting protected content.
|
||||||
|
4. Resolve and revalidate all critical customer-safety, task-completion, entitlement, and recovery
|
||||||
|
findings.
|
||||||
|
5. Obtain recorded joint approval from product ownership and delivery/support.
|
||||||
|
|
||||||
|
Only then is the phase eligible for general release.
|
||||||
94
specs/001-enterprise-experience/research.md
Normal file
94
specs/001-enterprise-experience/research.md
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# Research: Enterprise Appliance Experience Hardening
|
||||||
|
|
||||||
|
## Decision 1: Evolve the Existing Architecture
|
||||||
|
|
||||||
|
**Decision**: Retain the static portal, FastAPI management API, SQLite appliance store, shell/Ansible
|
||||||
|
installer, Nginx, and systemd deployment model.
|
||||||
|
|
||||||
|
**Rationale**: The current architecture already exposes authentication, audit, readiness, entitlement,
|
||||||
|
backup/restore, health, and installation flows. Enterprise quality depends first on consistent state,
|
||||||
|
durability, recovery, and validation—not a framework rewrite that would expand risk.
|
||||||
|
|
||||||
|
**Alternatives considered**: Rebuild the portal as a single-page application; split the backend into
|
||||||
|
services; adopt an external workflow engine. Deferred because none is required to satisfy Phase 1.
|
||||||
|
|
||||||
|
## Decision 2: Use Durable Operations as the Cross-Surface Backbone
|
||||||
|
|
||||||
|
**Decision**: Represent installation and later consequential actions as durable operation records
|
||||||
|
with lifecycle, progress, actor, scope, timestamps, result, recovery action, and correlation ID.
|
||||||
|
|
||||||
|
**Rationale**: Browser setup currently keeps install status in process memory, while the API runs
|
||||||
|
multiple workers and appliance actions can outlive sessions or processes. A durable record provides
|
||||||
|
one truth for refresh, restart, audit, diagnostics, and support.
|
||||||
|
|
||||||
|
**Alternatives considered**: Continue process-local status; infer progress exclusively from logs; add
|
||||||
|
a separate message broker. Process-local status is not recoverable, logs are not a stable customer
|
||||||
|
contract, and a broker is unnecessary for the current appliance scale.
|
||||||
|
|
||||||
|
## Decision 3: Define One Customer-Facing State Vocabulary
|
||||||
|
|
||||||
|
**Decision**: Normalize state into `loading`, `empty`, `ready`, `degraded`, `failed`, `restricted`,
|
||||||
|
`unavailable`, and `stale`, with severity, freshness, impact, and next action.
|
||||||
|
|
||||||
|
**Rationale**: Existing pages use several incompatible labels and colour treatments. A shared contract
|
||||||
|
prevents each page from inventing meaning and supports accessibility and acceptance tests.
|
||||||
|
|
||||||
|
**Alternatives considered**: Preserve page-specific vocabularies; expose raw service states. Both
|
||||||
|
produce inconsistent customer meaning and leak implementation concepts.
|
||||||
|
|
||||||
|
## Decision 4: Keep Entitlement Logic Canonical and Additive
|
||||||
|
|
||||||
|
**Decision**: Preserve Workstation separation and Server compatibility identifiers. Treat
|
||||||
|
`scripts/cezen_license.py` as canonical and verify parity with installer/backend deployed copies.
|
||||||
|
|
||||||
|
**Rationale**: The repository already documents the separation and copies license helpers into more
|
||||||
|
than one runtime location. Replacing identifiers would create avoidable upgrade and license risk.
|
||||||
|
|
||||||
|
**Alternatives considered**: Rename internal tiers; merge Workstation into the Server ladder; allow
|
||||||
|
each surface to derive its own mapping. Rejected for compatibility and drift risk.
|
||||||
|
|
||||||
|
## Decision 5: Introduce Explicit SQLite Schema Evolution
|
||||||
|
|
||||||
|
**Decision**: Use versioned, idempotent database migrations for new operation/support-evidence fields
|
||||||
|
and tables, with pre-change backup and compatibility checks.
|
||||||
|
|
||||||
|
**Rationale**: The backend creates many tables directly during startup. New enterprise lifecycle data
|
||||||
|
must be safely introduced on existing customer appliances and included in backup/restore validation.
|
||||||
|
|
||||||
|
**Alternatives considered**: Add only new tables through startup creation; replace SQLite; reset data
|
||||||
|
on upgrade. Startup creation lacks controlled evolution, replacement is disproportionate, and reset is
|
||||||
|
unacceptable for customer appliances.
|
||||||
|
|
||||||
|
## Decision 6: Shared Portal Primitives Without Immediate Framework Migration
|
||||||
|
|
||||||
|
**Decision**: Add shared browser-native helpers and CSS primitives for states, operations, errors,
|
||||||
|
focus, announcements, confirmation, and entitlement rather than rewriting all pages at once.
|
||||||
|
|
||||||
|
**Rationale**: The portal is a large static HTML/JavaScript surface already deployed by file sync.
|
||||||
|
Shared primitives enable phased adoption and reduce visual/behavioural drift with minimal packaging
|
||||||
|
change.
|
||||||
|
|
||||||
|
**Alternatives considered**: Full portal rewrite; page-by-page independent cleanup. A rewrite conflicts
|
||||||
|
with phased risk reduction; independent cleanup preserves inconsistency.
|
||||||
|
|
||||||
|
## Decision 7: WCAG 2.2 AA Requires Automated and Manual Evidence
|
||||||
|
|
||||||
|
**Decision**: Combine automated checks with manual keyboard, focus, status-announcement, zoom/reflow,
|
||||||
|
and representative assistive-technology journeys.
|
||||||
|
|
||||||
|
**Rationale**: Automated checks cannot prove complete accessibility, especially for dynamic status,
|
||||||
|
error recovery, and consequential operations.
|
||||||
|
|
||||||
|
**Alternatives considered**: Automated checks only; informal review; AAA. The first two are
|
||||||
|
insufficient for formal AA acceptance; AAA exceeds the approved scope.
|
||||||
|
|
||||||
|
## Decision 8: Release Through Evidence Gates
|
||||||
|
|
||||||
|
**Decision**: Require internal evidence, controlled pilot evidence, joint product and delivery/support
|
||||||
|
approval, then general release; validate both source revision and packaged artifact identity.
|
||||||
|
|
||||||
|
**Rationale**: Installation and recovery changes need customer-like appliance evidence, while the
|
||||||
|
repository contains multiple source and ISO delivery surfaces that can diverge.
|
||||||
|
|
||||||
|
**Alternatives considered**: Direct general release after local tests; UI-only acceptance; checksum
|
||||||
|
without journey validation. Each leaves a material enterprise-appliance risk untested.
|
||||||
356
specs/001-enterprise-experience/spec.md
Normal file
356
specs/001-enterprise-experience/spec.md
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
# Feature Specification: Enterprise Appliance Experience Hardening
|
||||||
|
|
||||||
|
**Feature Branch**: `main`
|
||||||
|
|
||||||
|
**Created**: 2026-07-12
|
||||||
|
|
||||||
|
**Status**: Draft
|
||||||
|
|
||||||
|
**Input**: User description: "Make Nexus One AI a top-notch customer experience that feels like an enterprise appliance."
|
||||||
|
|
||||||
|
## Clarifications
|
||||||
|
|
||||||
|
### Session 2026-07-12
|
||||||
|
|
||||||
|
- Q: Should enterprise-experience hardening ship as phases or as one complete redesign? → A: Phased
|
||||||
|
rollout: setup/readiness first, daily administration second, recovery/support third.
|
||||||
|
- Q: Which product configurations must Phase 1 cover? → A: One representative Workstation and one
|
||||||
|
representative Server configuration.
|
||||||
|
- Q: How should each phase progress to general release? → A: Internal validation, then a controlled
|
||||||
|
customer pilot, then general release.
|
||||||
|
- Q: Who approves completion of each phase? → A: Product owner and delivery/support jointly approve.
|
||||||
|
- Q: What accessibility standard applies to customer-facing setup and portal experiences? → A:
|
||||||
|
WCAG 2.2 Level AA.
|
||||||
|
|
||||||
|
## User Scenarios & Testing *(mandatory)*
|
||||||
|
|
||||||
|
### User Story 1 - Confident First-Time Setup (Priority: P1)
|
||||||
|
|
||||||
|
As a customer administrator, I can move from first boot through a validated, ready-to-use Nexus One
|
||||||
|
AI appliance without needing undocumented knowledge or encountering contradictory product, tier, or
|
||||||
|
license information.
|
||||||
|
|
||||||
|
**Why this priority**: Installation is the customer's first direct experience of the product and a
|
||||||
|
failed or ambiguous setup prevents every later outcome.
|
||||||
|
|
||||||
|
**Independent Test**: A representative administrator can complete setup on a supported appliance,
|
||||||
|
understand every decision, recover from correctable input or service failures, and reach a clearly
|
||||||
|
identified ready state using only product-provided guidance.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** a supported new appliance, **When** the administrator completes setup with valid inputs,
|
||||||
|
**Then** each step explains its purpose, validates before commitment, preserves previous choices,
|
||||||
|
and ends with an accurate readiness summary and next actions.
|
||||||
|
2. **Given** invalid network, customer, license, tier, or component input, **When** validation fails,
|
||||||
|
**Then** the administrator sees a plain-language explanation beside the affected decision and can
|
||||||
|
correct it without restarting the entire journey.
|
||||||
|
3. **Given** installation is interrupted or a required component fails, **When** the administrator
|
||||||
|
returns to the appliance, **Then** the product shows the last reliable state, customer impact,
|
||||||
|
diagnostic reference, and safe resume or recovery action.
|
||||||
|
4. **Given** hardware or licensing prevents a tier or component selection, **When** options are shown,
|
||||||
|
**Then** unavailable choices are explained using customer-facing product names and no unsupported
|
||||||
|
configuration can be committed accidentally.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 2 - Coherent Daily Administration (Priority: P1)
|
||||||
|
|
||||||
|
As an authorized administrator, I can understand appliance health, capacity, licensing, security,
|
||||||
|
users, models, knowledge, and operational work from one coherent product experience.
|
||||||
|
|
||||||
|
**Why this priority**: Daily confidence depends on the portal behaving as a unified control plane,
|
||||||
|
not a collection of disconnected pages and tools.
|
||||||
|
|
||||||
|
**Independent Test**: An administrator can identify overall appliance state, locate the five most
|
||||||
|
common administrative tasks, complete each task, and interpret success or failure without external
|
||||||
|
guidance.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** a healthy appliance, **When** the administrator signs in, **Then** the landing experience
|
||||||
|
presents current readiness, important alerts, capacity context, license/support state, and the
|
||||||
|
highest-value next actions without fabricated or stale status.
|
||||||
|
2. **Given** the administrator moves between portal areas, **When** equivalent concepts or actions
|
||||||
|
appear, **Then** naming, navigation, status severity, interaction, and confirmation patterns remain
|
||||||
|
consistent.
|
||||||
|
3. **Given** an operation is long-running, **When** it starts, progresses, completes, or fails,
|
||||||
|
**Then** its state remains discoverable and the administrator is not left uncertain whether the
|
||||||
|
action occurred.
|
||||||
|
4. **Given** an empty, loading, restricted, partially available, or failed state, **When** a page is
|
||||||
|
viewed, **Then** the state is distinguishable, truthful, and accompanied by an appropriate action.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 3 - Safe Operations and Recovery (Priority: P1)
|
||||||
|
|
||||||
|
As an appliance operator or support engineer, I can diagnose and recover common operational problems
|
||||||
|
without exposing secrets, damaging customer data, or relying on source-code knowledge.
|
||||||
|
|
||||||
|
**Why this priority**: Enterprise trust is established during failure and recovery, not only during
|
||||||
|
normal operation.
|
||||||
|
|
||||||
|
**Independent Test**: A trained operator can use product-provided health, audit, diagnostic, backup,
|
||||||
|
and recovery guidance to resolve or escalate representative failures with a complete evidence trail.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** one or more services are degraded, **When** the operator reviews system status,
|
||||||
|
**Then** the product distinguishes customer impact from technical detail and identifies safe checks,
|
||||||
|
recovery actions, and escalation evidence.
|
||||||
|
2. **Given** a backup, restore, upgrade, restart, or other consequential action, **When** it is
|
||||||
|
requested, **Then** prerequisites, scope, expected disruption, confirmation, progress, result, and
|
||||||
|
rollback or recovery guidance are available.
|
||||||
|
3. **Given** diagnostics are generated, **When** they are viewed or exported, **Then** secrets and
|
||||||
|
protected customer content are excluded while timestamps, correlation details, and relevant state
|
||||||
|
remain sufficient for support.
|
||||||
|
4. **Given** a privileged or security-sensitive action occurs, **When** audit history is reviewed,
|
||||||
|
**Then** it identifies who acted, what category of change occurred, when it occurred, the result,
|
||||||
|
and the affected scope without recording secret values.
|
||||||
|
5. **Given** an authorized administrator needs to remove appliance software, **When** removal is
|
||||||
|
requested, **Then** retained and deleted data, service impact, prerequisites, confirmation,
|
||||||
|
progress, result, rollback limits, and recovery or escalation guidance are explicit.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 4 - Predictable Product Entitlements (Priority: P2)
|
||||||
|
|
||||||
|
As a customer or Cezen delivery team member, I see capabilities that accurately match the purchased
|
||||||
|
Nexus One AI product, licensed tier, provisioned components, and supported hardware.
|
||||||
|
|
||||||
|
**Why this priority**: Contradictions between sales labels, licenses, installation options, portal
|
||||||
|
capabilities, and documentation undermine confidence and create field-support failures.
|
||||||
|
|
||||||
|
**Independent Test**: Representative Workstation and Server S/M/L/Max configurations show the correct
|
||||||
|
labels, permitted actions, explanatory restrictions, and matching documentation throughout setup and
|
||||||
|
daily administration.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** any supported product and tier, **When** product identity or entitlement is displayed,
|
||||||
|
**Then** customer-facing labels are consistent while internal compatibility identifiers remain
|
||||||
|
hidden unless diagnostically necessary.
|
||||||
|
2. **Given** a capability is not licensed, provisioned, supported by hardware, or currently available,
|
||||||
|
**When** the customer encounters it, **Then** the product distinguishes those causes and provides
|
||||||
|
the appropriate commercial, provisioning, hardware, or recovery next action.
|
||||||
|
3. **Given** a package or release is prepared, **When** its customer experience is accepted,
|
||||||
|
**Then** installer, portal, backend behaviour, deployment roles, documentation, and delivery
|
||||||
|
artifact agree on product names and capability boundaries.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 5 - Guided Evaluation and Handover (Priority: P2)
|
||||||
|
|
||||||
|
As a customer evaluator or newly assigned administrator, I can understand what the appliance is,
|
||||||
|
verify that it is ready, discover its key capabilities, and find authoritative help without a live
|
||||||
|
Cezen walkthrough.
|
||||||
|
|
||||||
|
**Why this priority**: A polished enterprise product must remain understandable after the sales or
|
||||||
|
delivery team leaves.
|
||||||
|
|
||||||
|
**Independent Test**: A representative evaluator can identify product purpose, current readiness,
|
||||||
|
licensed capabilities, key first tasks, help resources, and support evidence within ten minutes.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** a newly provisioned appliance, **When** an evaluator first enters the portal, **Then** the
|
||||||
|
experience provides role-appropriate orientation without blocking experienced users.
|
||||||
|
2. **Given** a user needs help, **When** contextual and central help are accessed, **Then** guidance
|
||||||
|
matches the current product terminology and points to safe, relevant actions.
|
||||||
|
3. **Given** the appliance is operating in a restricted network, **When** help or diagnostics are
|
||||||
|
needed, **Then** essential guidance remains locally available and external dependencies are clearly
|
||||||
|
identified.
|
||||||
|
|
||||||
|
### Edge Cases
|
||||||
|
|
||||||
|
- Browser refresh, session expiry, duplicate submission, or navigation during a long-running action.
|
||||||
|
- Power, network, storage, GPU, or dependent-service loss during setup or maintenance.
|
||||||
|
- License missing, malformed, expired, hardware-bound incorrectly, or valid for a different tier.
|
||||||
|
- Appliance provisioned above or below current license or hardware capability.
|
||||||
|
- Partial installation where some tools are intentionally omitted and others failed unexpectedly.
|
||||||
|
- Empty system, first user, no models, no documents, no audit events, and no historical health data.
|
||||||
|
- Multiple simultaneous administrators attempting conflicting consequential operations.
|
||||||
|
- Clock drift or unavailable time source affecting licenses, certificates, logs, and audit ordering.
|
||||||
|
- Insufficient storage or capacity discovered before and during a consequential operation.
|
||||||
|
- Diagnostic export requested when protected customer documents or secrets are present.
|
||||||
|
- Upgrade or restart interrupted after state changes but before completion is recorded.
|
||||||
|
- Removal interrupted after services or data have been changed but before completion is recorded.
|
||||||
|
- Portal information is temporarily older than the underlying appliance state.
|
||||||
|
|
||||||
|
## Requirements *(mandatory)*
|
||||||
|
|
||||||
|
### Functional Requirements
|
||||||
|
|
||||||
|
- **FR-001**: The product MUST provide a continuous, understandable journey from first boot to a
|
||||||
|
verified ready state.
|
||||||
|
- **FR-002**: The product MUST present a unified view of overall readiness, degraded conditions,
|
||||||
|
important alerts, capacity context, license/support state, and actionable next steps.
|
||||||
|
- **FR-003**: Every customer-facing status MUST distinguish at least loading, empty, ready, degraded,
|
||||||
|
failed, restricted, and unavailable states when those states are possible.
|
||||||
|
- **FR-004**: Every customer-correctable error MUST explain the problem, affected scope, and safe next
|
||||||
|
action without exposing implementation-only codes as the primary message.
|
||||||
|
- **FR-005**: Long-running and consequential operations MUST expose initiation, progress, completion,
|
||||||
|
failure, and recovery state that remains discoverable after navigation or session interruption.
|
||||||
|
- **FR-006**: Consequential operations MUST identify prerequisites, scope, expected disruption, and
|
||||||
|
recovery implications before confirmation.
|
||||||
|
- **FR-007**: The product MUST prevent accidental duplicate or conflicting consequential operations.
|
||||||
|
- **FR-008**: Navigation, product terminology, severity levels, interaction patterns, confirmations,
|
||||||
|
and feedback MUST be consistent across customer-facing areas.
|
||||||
|
- **FR-009**: Customer-visible operational values MUST identify their freshness or last-observed time
|
||||||
|
when they are not guaranteed to be live.
|
||||||
|
- **FR-010**: The product MUST provide contextual help for setup, licensing, health, models, knowledge,
|
||||||
|
users, security, backup, restore, upgrade, and recovery journeys.
|
||||||
|
- **FR-011**: Essential setup, operational, recovery, and escalation guidance MUST remain available on
|
||||||
|
the appliance without requiring public internet access.
|
||||||
|
- **FR-012**: Role restrictions MUST be explained without revealing protected data or presenting
|
||||||
|
controls that appear usable but always fail.
|
||||||
|
- **FR-013**: Privileged, security-sensitive, licensing, configuration, and lifecycle actions MUST
|
||||||
|
create an audit record containing actor, time, action category, affected scope, and outcome.
|
||||||
|
- **FR-014**: Diagnostic views and exports MUST omit secrets, credentials, license payloads, and
|
||||||
|
protected customer content by default.
|
||||||
|
- **FR-015**: Support evidence MUST include enough timestamps, correlation details, product identity,
|
||||||
|
entitlement state, and component health to reproduce or escalate a reported issue.
|
||||||
|
- **FR-016**: The product MUST accurately distinguish licensed, provisioned, hardware-supported, and
|
||||||
|
currently available capability states.
|
||||||
|
- **FR-017**: Workstation and Server S/M/L/Max naming and capabilities MUST remain consistent across
|
||||||
|
setup, portal, documentation, support evidence, and release artifacts.
|
||||||
|
- **FR-018**: The product MUST provide a concise first-use orientation and a persistent route to
|
||||||
|
authoritative help without obstructing returning users.
|
||||||
|
- **FR-019**: Every release affecting customer journeys MUST identify all changed surfaces and verify
|
||||||
|
that the packaged delivery contains the accepted versions.
|
||||||
|
- **FR-020**: Known degraded capabilities MUST be visible to affected users and MUST NOT be represented
|
||||||
|
as healthy or complete.
|
||||||
|
- **FR-021**: The product MUST preserve customer-entered setup data and completed safe steps when a
|
||||||
|
recoverable interruption occurs.
|
||||||
|
- **FR-022**: Recovery actions MUST clearly distinguish safe retry, resume, rollback, restore, restart,
|
||||||
|
and escalation choices when applicable.
|
||||||
|
- **FR-023**: Delivery MUST use three independently accepted phases in this order: setup and readiness,
|
||||||
|
daily administration, then recovery and support. Each phase MUST satisfy its applicable quality gates
|
||||||
|
before the next phase is accepted.
|
||||||
|
- **FR-024**: Phase 1 acceptance MUST include one representative Workstation configuration and one
|
||||||
|
representative Server configuration; shared behaviour MUST pass on both, while tier-specific
|
||||||
|
behaviour MUST be explicitly identified for later matrix coverage.
|
||||||
|
- **FR-025**: Each phase MUST pass internal validation and a controlled customer pilot before general
|
||||||
|
release. Pilot findings that affect customer safety, task completion, entitlement accuracy, or
|
||||||
|
recovery MUST be resolved and revalidated before promotion.
|
||||||
|
- **FR-026**: Phase acceptance MUST require recorded joint approval from the product owner and an
|
||||||
|
accountable delivery/support representative after reviewing customer-experience and operational
|
||||||
|
evidence.
|
||||||
|
- **FR-027**: Customer-facing setup and portal journeys MUST conform to WCAG 2.2 Level AA, including
|
||||||
|
keyboard operation, focus visibility, semantic structure, accessible names, contrast, status
|
||||||
|
announcements, error identification, and non-colour status cues.
|
||||||
|
- **FR-028**: Appliance software removal MUST be an authorized, auditable, durable operation that
|
||||||
|
identifies retained and deleted data, requires explicit confirmation, protects backups by default,
|
||||||
|
and provides truthful completion, rollback-limit, recovery, and escalation outcomes.
|
||||||
|
- **FR-029**: Every new operation, recovery, removal, diagnostic, and support-evidence interface MUST
|
||||||
|
enforce server-side authorization and MUST be covered by allowed-role and denied-role tests.
|
||||||
|
|
||||||
|
### Enterprise Appliance Requirements *(mandatory)*
|
||||||
|
|
||||||
|
- **EA-001 Customer Experience**: All primary journeys and their empty, loading, restricted, degraded,
|
||||||
|
failure, and recovery states MUST use consistent Nexus One AI language and actionable guidance.
|
||||||
|
- **EA-002 Security & Audit**: Authorization MUST be enforced for every privileged action; audit and
|
||||||
|
diagnostics MUST retain useful evidence while excluding secret values and protected content.
|
||||||
|
- **EA-003 Lifecycle & Recovery**: Setup, configuration, restart, upgrade, backup, restore, and
|
||||||
|
interruption behaviour MUST define safe preconditions, progress, outcomes, and recovery paths.
|
||||||
|
- **EA-004 Entitlement & Packaging**: Workstation and Server tier behaviour MUST remain consistent with
|
||||||
|
licensed, provisioned, and hardware-supported capabilities across every delivery surface.
|
||||||
|
- **EA-005 Operability**: Health, diagnostics, logs, local help, and escalation evidence MUST support
|
||||||
|
customer-controlled and restricted-network operation.
|
||||||
|
|
||||||
|
### Key Entities
|
||||||
|
|
||||||
|
- **Appliance Readiness**: Overall customer-relevant state, affected capabilities, freshness, alerts,
|
||||||
|
and next actions.
|
||||||
|
- **Operation**: A consequential or long-running activity with actor, scope, lifecycle state, progress,
|
||||||
|
result, and recovery options.
|
||||||
|
- **Entitlement State**: Product category, commercial tier, licensed capabilities, provisioned
|
||||||
|
components, hardware support, and current availability.
|
||||||
|
- **Support Evidence Package**: Redacted product identity, timestamps, correlation information,
|
||||||
|
entitlement summary, health state, and relevant operational history.
|
||||||
|
- **Audit Event**: Actor, time, action category, affected scope, outcome, and correlation reference,
|
||||||
|
excluding protected values.
|
||||||
|
- **Guidance Item**: Locally available contextual instructions associated with a journey, state, or
|
||||||
|
recovery action.
|
||||||
|
|
||||||
|
## Success Criteria *(mandatory)*
|
||||||
|
|
||||||
|
### Measurable Outcomes
|
||||||
|
|
||||||
|
- **SC-001**: At least 90% of representative first-time administrators complete supported setup and
|
||||||
|
reach a verified ready state without undocumented assistance.
|
||||||
|
- **SC-002**: At least 90% of representative administrators can identify overall appliance health,
|
||||||
|
license state, and the correct next action for a degraded condition within two minutes.
|
||||||
|
- **SC-003**: At least 95% of customer-correctable failures tested provide an accurate explanation and
|
||||||
|
a successful safe recovery path without restarting the entire journey.
|
||||||
|
- **SC-004**: The five most common administrative tasks can each be located within three navigation
|
||||||
|
decisions and completed without external documentation by at least 90% of representative users.
|
||||||
|
- **SC-005**: 100% of tested privileged, security-sensitive, licensing, configuration, and lifecycle
|
||||||
|
actions produce the required audit evidence without secret values.
|
||||||
|
- **SC-006**: 100% of representative Workstation and Server tier test cases display consistent product
|
||||||
|
names and capability boundaries across setup, portal, help, and release acceptance evidence.
|
||||||
|
- **SC-007**: All tested consequential operations retain or recover a truthful final state after page
|
||||||
|
refresh, session expiry, or simulated interruption.
|
||||||
|
- **SC-008**: A trained operator can diagnose, safely recover, or prepare a complete escalation package
|
||||||
|
for each agreed common failure scenario within 15 minutes.
|
||||||
|
- **SC-009**: No accepted release contains placeholder content, dead controls, unexplained primary
|
||||||
|
error codes, fabricated production values, or customer-visible legacy product naming.
|
||||||
|
- **SC-010**: Every accepted customer-facing release has traceable evidence for primary journeys,
|
||||||
|
failure states, entitlement boundaries, recovery impact, and packaged artifact identity.
|
||||||
|
- **SC-011**: Phase 1 produces passing setup and readiness evidence on both one representative
|
||||||
|
Workstation and one representative Server configuration.
|
||||||
|
- **SC-012**: Every general release has recorded internal-validation and controlled-pilot evidence,
|
||||||
|
with no unresolved critical customer-safety, task-completion, entitlement, or recovery findings.
|
||||||
|
- **SC-013**: All customer-facing setup and portal pages changed by a phase pass automated checks and
|
||||||
|
representative manual keyboard and assistive-technology checks for WCAG 2.2 Level AA conformance,
|
||||||
|
with no unresolved Level A or Level AA failures at acceptance.
|
||||||
|
- **SC-014**: Under normal representative appliance conditions, readiness is displayed within 10
|
||||||
|
seconds of portal entry in at least 95% of measured trials.
|
||||||
|
- **SC-015**: Local navigation provides visible feedback within 1 second in at least 95% of measured
|
||||||
|
interactions on supported browsers.
|
||||||
|
- **SC-016**: Consequential-operation submission provides acknowledgement or a validation result
|
||||||
|
within 2 seconds in at least 95% of measured trials under normal appliance conditions.
|
||||||
|
|
||||||
|
### Verification Evidence *(mandatory)*
|
||||||
|
|
||||||
|
- **Static/local evidence**: Terminology, navigation, state coverage, authorization, redaction, audit,
|
||||||
|
entitlement, accessibility, and journey checks defined per affected surface.
|
||||||
|
- **Deployment evidence**: A clean supported installation plus upgrade or reconfiguration from a
|
||||||
|
representative existing appliance, including interruption and recovery scenarios.
|
||||||
|
- **Live-appliance evidence**: Role-based setup and daily-administration journeys on representative
|
||||||
|
Workstation and Server configurations, with healthy, empty, restricted, degraded, and failed states.
|
||||||
|
- **Release artifact evidence**: Recorded source revision, package identity, build time, checksum, and
|
||||||
|
confirmation that accepted portal, service, deployment, help, and entitlement content is included.
|
||||||
|
|
||||||
|
## Assumptions
|
||||||
|
|
||||||
|
- This feature hardens the existing Nexus One AI product rather than replacing its architecture.
|
||||||
|
- The first release targets current supported desktop browsers used on the customer network.
|
||||||
|
- Existing authentication, product categories, Server tier identifiers, licensing rules, and customer
|
||||||
|
data remain authoritative unless a later specification explicitly changes them.
|
||||||
|
- The work includes the first-boot setup and customer portal plus the supporting operational behaviour
|
||||||
|
necessary to make those journeys truthful.
|
||||||
|
- Delivery is phased: setup/readiness first, daily administration second, and recovery/support third;
|
||||||
|
this ordering does not remove cross-phase consistency requirements.
|
||||||
|
- Public internet access cannot be assumed after installation.
|
||||||
|
- Exact capacity and performance thresholds will be set per product/tier during planning using
|
||||||
|
validated hardware and workload evidence.
|
||||||
|
- The delivery team will identify the most common administrative tasks and failure scenarios from
|
||||||
|
product telemetry where available, support history, and field experience before acceptance testing.
|
||||||
|
- Percentage-based administrator outcomes use at least 10 representative participants across internal
|
||||||
|
validation and controlled pilot; results report the participant count and product configuration.
|
||||||
|
- The customer-correctable failure corpus contains at least 20 representative cases across input,
|
||||||
|
entitlement, network, service, storage, interruption, and stale-state categories.
|
||||||
|
- Audit completeness is measured against a reviewed inventory of every privileged,
|
||||||
|
security-sensitive, licensing, configuration, lifecycle, removal, and support-evidence action.
|
||||||
|
- Mobile-native applications, a complete visual rebrand, new commercial tiers, and replacement of
|
||||||
|
underlying third-party tools are outside this feature unless separately specified.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- Introducing new commercial product categories or changing purchased entitlement definitions.
|
||||||
|
- Replacing underlying AI, observability, notebook, or document-processing tools solely for visual
|
||||||
|
consistency.
|
||||||
|
- Claiming high availability, redundancy, offline model availability, or recovery objectives that the
|
||||||
|
deployed product has not been designed and validated to provide.
|
||||||
|
- Implementing feature changes during this specification phase.
|
||||||
249
specs/001-enterprise-experience/tasks.md
Normal file
249
specs/001-enterprise-experience/tasks.md
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
# Tasks: Enterprise Appliance Experience Hardening
|
||||||
|
|
||||||
|
**Input**: Design documents from `specs/001-enterprise-experience/`
|
||||||
|
|
||||||
|
**Prerequisites**: `plan.md`, `spec.md`, `research.md`, `data-model.md`,
|
||||||
|
`contracts/customer-experience-contract.md`, `quickstart.md`
|
||||||
|
|
||||||
|
**Tests**: Required by the constitution and specification. Contract, journey, accessibility,
|
||||||
|
deployment, recovery, and artifact-evidence tasks are included.
|
||||||
|
|
||||||
|
**Organization**: Tasks are grouped by user story. Phase 1 product delivery consists of Setup,
|
||||||
|
Foundational, and User Story 1; later story phases remain separately acceptable increments.
|
||||||
|
|
||||||
|
## Format: `[ID] [P?] [Story] Description`
|
||||||
|
|
||||||
|
- **[P]**: Can proceed in parallel because it targets independent files after prerequisites pass.
|
||||||
|
- **[Story]**: Maps directly to the numbered user story in `spec.md`.
|
||||||
|
- Every implementation task names its expected repository path.
|
||||||
|
|
||||||
|
## Phase 1: Setup and Verification Infrastructure
|
||||||
|
|
||||||
|
**Purpose**: Establish safe migration, testing, accessibility, and packaging evidence before product
|
||||||
|
behaviour changes.
|
||||||
|
|
||||||
|
- [x] T001 Create the Phase 1 evidence manifest template in `specs/001-enterprise-experience/evidence/phase1-manifest.md`
|
||||||
|
- [x] T002 [P] Add Python test configuration and fixtures for isolated appliance data in `tests/conftest.py`
|
||||||
|
- [x] T003 [P] Add browser journey and accessibility test configuration in `tests/browser/README.md`
|
||||||
|
- [x] T004 [P] Add shell and Ansible validation commands to `tests/deployment/README.md`
|
||||||
|
- [x] T005 Document the SQLite schema-version and rollback procedure in `specs/001-enterprise-experience/migrations.md`
|
||||||
|
- [x] T006 Record the supported pre-feature appliance baseline and restore fixture requirements in `tests/fixtures/README.md`
|
||||||
|
- [x] T007 Verify no product code changes are required to create the test harness and capture the review in `specs/001-enterprise-experience/evidence/phase1-manifest.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Foundational Customer-Experience Contracts
|
||||||
|
|
||||||
|
**Purpose**: Implement shared contracts that block all customer-journey work.
|
||||||
|
|
||||||
|
**Critical**: No user story implementation begins until these tasks pass review.
|
||||||
|
|
||||||
|
- [x] T008 Add idempotent schema-version and migration runner tests in `tests/backend/test_migrations.py`
|
||||||
|
- [x] T009 Implement additive SQLite schema-version and migration support in `ansible/roles/cezen-backend/files/main.py`
|
||||||
|
- [x] T010 Add customer-state, error-envelope, entitlement-reason, redaction, and allowed/denied-role contract tests in `tests/backend/test_experience_contracts.py`
|
||||||
|
- [x] T011 Implement versioned customer-state and error-envelope helpers in `ansible/roles/cezen-backend/files/main.py`
|
||||||
|
- [x] T012 Add durable Operation lifecycle, transition, conflict, idempotency, and server-side authorization tests in `tests/backend/test_operations.py`
|
||||||
|
- [x] T013 Implement the Operation store and lifecycle rules in `ansible/roles/cezen-backend/files/main.py`
|
||||||
|
- [x] T014 Add audit correlation and secret-redaction tests in `tests/backend/test_audit_redaction.py`
|
||||||
|
- [x] T015 Extend audit helpers with allowlisted detail and correlation IDs in `ansible/roles/cezen-backend/files/main.py`
|
||||||
|
- [x] T016 [P] Create shared accessible state, alert, operation, and confirmation styles in `cezen-portal/style.css`
|
||||||
|
- [x] T017 [P] Create shared browser helpers for state rendering, freshness, errors, focus, and announcements in `cezen-portal/experience.js`
|
||||||
|
- [x] T018 [P] Add canonical customer-facing product and entitlement vocabulary in `cezen-portal/branding.js`
|
||||||
|
- [x] T019 Synchronize canonical license logic into the deployed backend copy and add parity verification in `tests/backend/test_license_parity.py`
|
||||||
|
- [x] T020 Verify the foundational contract against `specs/001-enterprise-experience/contracts/customer-experience-contract.md` and record results in `specs/001-enterprise-experience/evidence/phase1-manifest.md`
|
||||||
|
|
||||||
|
**Checkpoint**: Durable operations, safe migrations, redacted audit evidence, and shared accessible
|
||||||
|
presentation contracts are independently verified.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: User Story 1 — Confident First-Time Setup (Priority: P1) — Phase 1 MVP
|
||||||
|
|
||||||
|
**Goal**: A customer administrator completes or safely resumes setup and reaches a truthful readiness
|
||||||
|
state on both a representative Workstation and Server.
|
||||||
|
|
||||||
|
**Independent Test**: Complete browser and console setup with valid, invalid, interrupted, stale,
|
||||||
|
restricted-network, Workstation, and Server scenarios; refresh or restart during installation and
|
||||||
|
verify the same durable operation, accurate readiness, and safe next action remain available.
|
||||||
|
|
||||||
|
### Tests for User Story 1
|
||||||
|
|
||||||
|
- [x] T021 [P] [US1] Add browser setup validation, duplicate-submit, refresh, interruption, and 2-second acknowledgement timing tests in `tests/browser/test_setup_journey.py`
|
||||||
|
- [x] T022 [P] [US1] Add console setup valid, invalid-license, and resume scenario tests in `tests/deployment/test_firstboot_setup.py`
|
||||||
|
- [x] T023 [P] [US1] Add readiness state, freshness, entitlement-reason, remediation, and 10-second display timing tests in `tests/backend/test_readiness.py`
|
||||||
|
- [x] T024 [P] [US1] Add WCAG 2.2 AA automated and manual-check definitions for setup/readiness in `tests/accessibility/phase1-checklist.md`
|
||||||
|
- [x] T025 [P] [US1] Add Workstation-versus-Server setup matrix fixtures in `tests/fixtures/setup-matrix.json`
|
||||||
|
|
||||||
|
### Implementation for User Story 1
|
||||||
|
|
||||||
|
- [x] T026 [US1] Persist non-secret browser setup progress and durable installation operation state in `autoinstall/websetup/server.py`
|
||||||
|
- [x] T027 [US1] Replace process-local-only progress rendering with resumable operation polling and accessible announcements in `autoinstall/websetup/server.py`
|
||||||
|
- [x] T028 [US1] Preserve safe completed steps and expose retry, resume, reboot, recovery, and escalation outcomes in `autoinstall/firstboot-setup.sh`
|
||||||
|
- [x] T029 [US1] Align browser and console setup labels, validation, Workstation separation, and Server tier reasons across `autoinstall/websetup/server.py` and `autoinstall/firstboot-setup.sh`
|
||||||
|
- [x] T030 [US1] Extend readiness aggregation with canonical state, severity, freshness, customer impact, affected capabilities, and next actions in `ansible/roles/cezen-backend/files/main.py`
|
||||||
|
- [x] T031 [US1] Update the appliance readiness UI to consume the versioned contract and shared experience helpers in `cezen-portal/appliance.html`
|
||||||
|
- [x] T032 [US1] Update the primary landing readiness experience to show truthful current or stale state and highest-value next actions in `cezen-portal/index.html`
|
||||||
|
- [x] T033 [US1] Update health rendering to distinguish unreachable, degraded dependency, unavailable capability, and stale observation in `cezen-portal/health.html`
|
||||||
|
- [x] T034 [US1] Add local setup, interruption, readiness, and escalation guidance in `cezen-portal/quickstart.html` and `cezen-portal/troubleshooting.html`
|
||||||
|
- [x] T035 [US1] Add schema migration, operation recovery, and portal deployment steps to `ansible/roles/cezen-backend/tasks/main.yml` and `ansible/roles/cezen-nginx/tasks/main.yml`
|
||||||
|
- [ ] T036 [US1] Verify clean install, interruption, restricted-network guidance, and WCAG evidence on a representative Workstation and record it in `specs/001-enterprise-experience/evidence/workstation-phase1.md`
|
||||||
|
- [ ] T037 [US1] Verify clean install, interruption, restricted-network guidance, and WCAG evidence on a representative Server and record it in `specs/001-enterprise-experience/evidence/server-phase1.md`
|
||||||
|
- [ ] T038 [US1] Verify source-to-package and source-to-ISO provenance for all Phase 1 surfaces and record checksums in `specs/001-enterprise-experience/evidence/phase1-manifest.md`
|
||||||
|
|
||||||
|
**Checkpoint**: User Story 1 and product Phase 1 pass internal evidence review on Workstation and
|
||||||
|
Server and are eligible for controlled pilot, not yet general release.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: Delivery Phase 2 — User Story 2: Coherent Daily Administration (Priority: P1)
|
||||||
|
|
||||||
|
**Goal**: Administrators understand current appliance state and complete common daily tasks through a
|
||||||
|
consistent, truthful control-plane experience.
|
||||||
|
|
||||||
|
**Independent Test**: A representative administrator locates and completes the five agreed common
|
||||||
|
tasks within three navigation decisions, with consistent loading, empty, ready, degraded, failed,
|
||||||
|
restricted, unavailable, and stale behaviour.
|
||||||
|
|
||||||
|
- [ ] T039 [P] [US2] Define five administration journeys, at least 10 representative participants, and acceptance scoring in `tests/browser/daily-administration.md`
|
||||||
|
- [ ] T040 [P] [US2] Add shared navigation, 1-second feedback timing, status, error, operation, and freshness browser tests in `tests/browser/test_daily_administration.py`
|
||||||
|
- [ ] T041 [US2] Apply the common navigation and state shell to dashboard and appliance journeys in `cezen-portal/dashboard.html` and `cezen-portal/appliance.html`
|
||||||
|
- [ ] T042 [US2] Apply the common operation and entitlement contract to model and knowledge journeys in `cezen-portal/models-admin.html` and `cezen-portal/knowledge.html`
|
||||||
|
- [ ] T043 [US2] Apply role-aware restriction, error, and confirmation behaviour to users and security journeys in `cezen-portal/users.html` and `cezen-portal/security.html`
|
||||||
|
- [ ] T044 [US2] Expose role-filtered recent and active durable operations in `ansible/roles/cezen-backend/files/main.py` and `cezen-portal/appliance.html`
|
||||||
|
- [ ] T045 [US2] Audit all remaining portal pages for placeholder, fabricated, loading, empty, degraded, dead-control, and legacy-name states and record findings in `specs/001-enterprise-experience/evidence/portal-audit.md`
|
||||||
|
- [ ] T046 [US2] Resolve accepted portal-audit findings in affected files under `cezen-portal/`
|
||||||
|
- [ ] T047 [US2] Validate the five administration journeys and WCAG 2.2 AA evidence and record results in `specs/001-enterprise-experience/evidence/phase2-administration.md`
|
||||||
|
|
||||||
|
**Checkpoint**: User Story 2 passes independent task-completion and accessibility acceptance.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: Delivery Phase 3 — User Story 3: Safe Operations and Recovery (Priority: P1)
|
||||||
|
|
||||||
|
**Goal**: Operators diagnose, recover, or escalate common failures using safe actions and redacted,
|
||||||
|
correlated evidence.
|
||||||
|
|
||||||
|
**Independent Test**: For each agreed failure, an operator reaches safe recovery or produces a
|
||||||
|
complete redacted escalation package within 15 minutes.
|
||||||
|
|
||||||
|
- [ ] T048 [P] [US3] Add authorized/denied backup, restore, restart, upgrade, removal, conflict, and interruption tests in `tests/backend/test_recovery_operations.py`
|
||||||
|
- [ ] T049 [P] [US3] Add support-evidence authorization, allowlist, redaction, and checksum tests in `tests/backend/test_support_evidence.py`
|
||||||
|
- [ ] T050 [US3] Model backup, restore, and removal as authorized durable operations with preconditions and recovery outcomes in `ansible/roles/cezen-backend/files/main.py`
|
||||||
|
- [ ] T051 [US3] Add versioned redacted support-evidence generation in `ansible/roles/cezen-backend/files/main.py`
|
||||||
|
- [ ] T052 [US3] Add safe operation progress, removal inventory, recovery, and diagnostic-export experiences in `cezen-portal/appliance.html`
|
||||||
|
- [ ] T053 [US3] Align command-line backup/restore evidence and implement backup-preserving removal in `scripts/cezen-backup.sh` and `scripts/cezen-remove.sh`
|
||||||
|
- [ ] T054 [US3] Add locally available recovery and escalation playbooks in `cezen-portal/troubleshooting.html`
|
||||||
|
- [ ] T055 [US3] Validate the 20-case failure corpus, backup/restore/removal compatibility, interruption, authorization, and 15-minute escalation outcomes in `specs/001-enterprise-experience/evidence/phase3-recovery.md`
|
||||||
|
|
||||||
|
**Checkpoint**: User Story 3 passes independent recovery, redaction, correlation, and support evidence
|
||||||
|
acceptance.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: Cross-Cutting Workstream — User Story 4: Predictable Product Entitlements (Priority: P2)
|
||||||
|
|
||||||
|
**Goal**: Workstation and Server customers see capability states that accurately distinguish license,
|
||||||
|
provisioning, hardware, and runtime causes.
|
||||||
|
|
||||||
|
**Independent Test**: The agreed Workstation and Server S/M/L/Max matrix produces consistent labels,
|
||||||
|
permitted actions, restrictions, and help across setup, portal, backend, deployment, and artifacts.
|
||||||
|
|
||||||
|
- [ ] T056 [P] [US4] Expand valid, missing, expired, mismatch, hardware, and provisioning entitlement fixtures in `tests/fixtures/entitlement-matrix.json`
|
||||||
|
- [ ] T057 [P] [US4] Add cross-copy and cross-surface entitlement matrix tests in `tests/backend/test_entitlement_matrix.py`
|
||||||
|
- [ ] T058 [US4] Return distinct licensed, provisioned, hardware-supported, and currently available reasons in `ansible/roles/cezen-backend/files/main.py`
|
||||||
|
- [ ] T059 [US4] Render commercial labels and reason-specific next actions consistently in `cezen-portal/branding.js` and `cezen-portal/appliance.html`
|
||||||
|
- [ ] T060 [US4] Align installer and deployment role capability decisions in `install.sh`, `autoinstall/firstboot-setup.sh`, and `ansible/{starter,entry,pro,max}.yml`
|
||||||
|
- [ ] T061 [US4] Verify documentation and packaged artifacts against the entitlement matrix in `specs/001-enterprise-experience/evidence/entitlement-matrix.md`
|
||||||
|
|
||||||
|
**Checkpoint**: User Story 4 passes the full product/tier consistency matrix.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7: Delivery Phase 2 Workstream — User Story 5: Guided Evaluation and Handover (Priority: P2)
|
||||||
|
|
||||||
|
**Goal**: Evaluators and new administrators understand readiness, licensed capabilities, first tasks,
|
||||||
|
local help, and support evidence within ten minutes.
|
||||||
|
|
||||||
|
**Independent Test**: A representative evaluator completes the agreed discovery script without a live
|
||||||
|
Cezen walkthrough or public internet.
|
||||||
|
|
||||||
|
- [ ] T062 [P] [US5] Define evaluator and new-administrator discovery scripts in `tests/browser/handover-journey.md`
|
||||||
|
- [ ] T063 [US5] Add role-aware first-use orientation and persistent local help entry points in `cezen-portal/index.html` and `cezen-portal/quickstart.html`
|
||||||
|
- [ ] T064 [US5] Align local FAQ and glossary with current product, entitlement, readiness, and recovery terms in `cezen-portal/faq.html` and `cezen-portal/glossary.html`
|
||||||
|
- [ ] T065 [US5] Add packaged product/build identity and authoritative support references in `cezen-portal/about.html`
|
||||||
|
- [ ] T066 [US5] Validate the ten-minute restricted-network handover journey and record results in `specs/001-enterprise-experience/evidence/phase4-handover.md`
|
||||||
|
|
||||||
|
**Checkpoint**: User Story 5 passes independent evaluation and handover acceptance.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 8: Pilot, Release, and Cross-Cutting Quality
|
||||||
|
|
||||||
|
**Purpose**: Prove the integrated appliance experience before general release.
|
||||||
|
|
||||||
|
- [ ] T067 Run full static, backend, authorization, timing, browser, accessibility, shell, and Ansible validation and record results in `specs/001-enterprise-experience/evidence/release-candidate.md`
|
||||||
|
- [ ] T068 Verify upgrade, rollback, backup/restore, and interruption from the supported prior appliance baseline in `specs/001-enterprise-experience/evidence/release-candidate.md`
|
||||||
|
- [ ] T069 Verify final source revision, packaged copies, ISO identity, build time, size, and SHA-256 checksums in `specs/001-enterprise-experience/evidence/release-candidate.md`
|
||||||
|
- [ ] T070 Conduct the approved controlled customer pilot and record non-sensitive findings in `specs/001-enterprise-experience/evidence/pilot.md`
|
||||||
|
- [ ] T071 Resolve and revalidate all critical customer-safety, task-completion, entitlement, recovery, and WCAG findings in `specs/001-enterprise-experience/evidence/pilot.md`
|
||||||
|
- [ ] T072 Obtain product-owner and delivery/support approval in `specs/001-enterprise-experience/evidence/release-approval.md`
|
||||||
|
- [ ] T073 Review the complete Git diff and explicitly authorize or defer commit, push, deployment, ISO rebuild, and general release in `specs/001-enterprise-experience/evidence/release-approval.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependencies and Execution Order
|
||||||
|
|
||||||
|
### Phase Dependencies
|
||||||
|
|
||||||
|
- Setup (T001–T007) has no product-code dependency.
|
||||||
|
- Foundational contracts (T008–T020) depend on Setup and block every user story.
|
||||||
|
- User Story 1 (T021–T038) depends on Foundational and is the Phase 1 MVP.
|
||||||
|
- User Story 2 (T039–T047) depends on Foundational; its operation-history work depends on User Story 1.
|
||||||
|
- User Story 3 (T048–T055) depends on Foundational and durable Operation support from User Story 1.
|
||||||
|
- User Story 4 (T056–T061) depends on Foundational and can proceed after Phase 1 entitlement contracts stabilize.
|
||||||
|
- User Story 5 (T062–T066) depends on accepted terminology and readiness behaviour from Stories 1 and 4.
|
||||||
|
- User Story 4 is cross-cutting across Delivery Phases 1–3; User Story 5 is accepted within Delivery
|
||||||
|
Phase 2. Neither creates an additional delivery phase.
|
||||||
|
- Pilot/release (T067–T073) depends on the stories included in that release candidate.
|
||||||
|
|
||||||
|
### User Story Dependency Graph
|
||||||
|
|
||||||
|
```text
|
||||||
|
Setup -> Foundation -> US1 Setup/Readiness (Phase 1 MVP)
|
||||||
|
|-> US2 Daily Administration
|
||||||
|
|-> US3 Recovery/Support
|
||||||
|
|-> US4 Entitlement Matrix -> US5 Handover
|
||||||
|
Selected accepted stories -> Pilot -> Joint Approval -> General Release Eligibility
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parallel Opportunities
|
||||||
|
|
||||||
|
- T002–T004 can run in parallel after T001.
|
||||||
|
- T016–T019 can run in parallel after backend contract shapes stabilize.
|
||||||
|
- T021–T025 can be authored in parallel before US1 implementation.
|
||||||
|
- T036 and T037 can run in parallel on separate representative appliances after T026–T035.
|
||||||
|
- Story-specific test authoring for US2, US3, and US4 can proceed in parallel after Foundation.
|
||||||
|
|
||||||
|
## Implementation Strategy
|
||||||
|
|
||||||
|
### Phase 1 MVP First
|
||||||
|
|
||||||
|
1. Complete T001–T020.
|
||||||
|
2. Complete T021–T035 test-first for User Story 1.
|
||||||
|
3. Validate Workstation and Server independently with T036–T038.
|
||||||
|
4. Stop for internal evidence review and controlled-pilot authorization.
|
||||||
|
5. Do not begin general release or later product phases automatically.
|
||||||
|
|
||||||
|
### Incremental Delivery
|
||||||
|
|
||||||
|
Each user story retains its own independent test and evidence checkpoint. Later story phases reuse the
|
||||||
|
same state, operation, entitlement, accessibility, audit, and release contracts without changing
|
||||||
|
their meaning.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Every task that changes behaviour is preceded by an applicable test or evidence definition.
|
||||||
|
- Application files remain unchanged until `$speckit-implement` is explicitly authorized.
|
||||||
|
- Existing untracked `how-it-flows.gif` is unrelated and MUST remain untouched.
|
||||||
|
- Commit, push, deployment, ISO rebuild, pilot, and general release require explicit authorization at
|
||||||
|
their respective gates.
|
||||||
1
tests/__init__.py
Normal file
1
tests/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Nexus One AI test package."""
|
||||||
24
tests/accessibility/phase1-checklist.md
Normal file
24
tests/accessibility/phase1-checklist.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Phase 1 WCAG 2.2 AA Checklist
|
||||||
|
|
||||||
|
Apply to browser setup, console-equivalent instructions, landing readiness, appliance and health
|
||||||
|
pages. Automated checks supplement but do not replace manual review.
|
||||||
|
|
||||||
|
- [ ] Keyboard completes valid setup, validation recovery and interruption recovery.
|
||||||
|
- [ ] Focus order follows the visible journey and remains visible at all times.
|
||||||
|
- [ ] Validation errors are associated with fields and focus moves to the first invalid field.
|
||||||
|
- [ ] Progress, degraded state, failure and completion are announced without stealing focus.
|
||||||
|
- [ ] Step, tier and tool selectors expose name, role, state and keyboard operation.
|
||||||
|
- [ ] Confirmation identifies scope, disruption and recovery implications.
|
||||||
|
- [ ] Status and severity never rely on colour alone.
|
||||||
|
- [ ] Text and controls meet WCAG 2.2 AA contrast requirements.
|
||||||
|
- [ ] Content reflows at 320 CSS pixels and remains usable at 200% zoom.
|
||||||
|
- [ ] Motion respects reduced-motion preferences.
|
||||||
|
- [ ] Page titles, headings, landmarks and labels identify purpose and context.
|
||||||
|
- [ ] Error messages avoid raw codes as primary text and provide a safe next action.
|
||||||
|
- [ ] Workstation and Server labels are consistent and understandable.
|
||||||
|
- [ ] Restricted-network guidance is locally available.
|
||||||
|
- [ ] Automated scan has no unresolved Level A or AA finding.
|
||||||
|
- [ ] Representative screen-reader journey has no unresolved blocker.
|
||||||
|
|
||||||
|
Record browser, operating system, assistive technology, viewport, product configuration, evidence
|
||||||
|
links, reviewer and date for every completed run.
|
||||||
1
tests/backend/__init__.py
Normal file
1
tests/backend/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Backend contract tests."""
|
||||||
21
tests/backend/_source.py
Normal file
21
tests/backend/_source.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import ast
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
MAIN = ROOT / "ansible/roles/cezen-backend/files/main.py"
|
||||||
|
|
||||||
|
|
||||||
|
def load_symbols(*names, globals_dict=None):
|
||||||
|
tree = ast.parse(MAIN.read_text())
|
||||||
|
wanted = []
|
||||||
|
for node in tree.body:
|
||||||
|
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name in names:
|
||||||
|
wanted.append(node)
|
||||||
|
elif isinstance(node, ast.Assign):
|
||||||
|
assigned = {target.id for target in node.targets if isinstance(target, ast.Name)}
|
||||||
|
if assigned.intersection(names):
|
||||||
|
wanted.append(node)
|
||||||
|
namespace = dict(globals_dict or {})
|
||||||
|
exec(compile(ast.Module(body=wanted, type_ignores=[]), str(MAIN), "exec"), namespace)
|
||||||
|
return namespace
|
||||||
18
tests/backend/test_audit_redaction.py
Normal file
18
tests/backend/test_audit_redaction.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from ._source import load_symbols
|
||||||
|
|
||||||
|
|
||||||
|
def _namespace():
|
||||||
|
return load_symbols("_AUDIT_SENSITIVE_KEYS", "_redact_audit_value", "safe_audit_detail", globals_dict={"json": __import__("json")})
|
||||||
|
|
||||||
|
|
||||||
|
def test_structured_audit_detail_redacts_secret_categories():
|
||||||
|
safe = _namespace()["safe_audit_detail"]({"license_key": "abc", "result": "valid", "nested": {"token": "xyz"}})
|
||||||
|
assert "abc" not in safe and "xyz" not in safe
|
||||||
|
assert safe.count("[REDACTED]") == 2
|
||||||
|
assert "valid" in safe
|
||||||
|
|
||||||
|
|
||||||
|
def test_text_audit_detail_redacts_key_value_pairs():
|
||||||
|
safe = _namespace()["safe_audit_detail"]("license_key=abc token:xyz outcome=valid")
|
||||||
|
assert "abc" not in safe and "xyz" not in safe
|
||||||
|
assert "outcome=valid" in safe
|
||||||
24
tests/backend/test_experience_contracts.py
Normal file
24
tests/backend/test_experience_contracts.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
from ._source import load_symbols
|
||||||
|
|
||||||
|
|
||||||
|
def test_customer_state_normalizes_unknown_values():
|
||||||
|
ns = load_symbols("CUSTOMER_STATES", "customer_state", globals_dict={"utcnow": lambda: datetime.now(timezone.utc).isoformat()})
|
||||||
|
payload = ns["customer_state"]("mystery", "Status cannot be observed")
|
||||||
|
assert payload["schema"] == "cezen.customer_state.v1"
|
||||||
|
assert payload["state"] == "unavailable"
|
||||||
|
assert payload["summary"] == "Status cannot be observed"
|
||||||
|
|
||||||
|
|
||||||
|
def test_error_envelope_has_customer_safe_fields():
|
||||||
|
ns = load_symbols("error_envelope")
|
||||||
|
payload = ns["error_envelope"]("service_unavailable", "Model service is unavailable", "Retry after checking health.", affected_scope="local inference", correlation_id="corr-1")
|
||||||
|
assert set(payload) == {"schema", "code", "message", "remediation", "affected_scope", "correlation_id"}
|
||||||
|
assert payload["correlation_id"] == "corr-1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_new_operation_interfaces_require_admin_in_source():
|
||||||
|
source = (__import__("pathlib").Path(__file__).resolve().parents[2] / "ansible/roles/cezen-backend/files/main.py").read_text()
|
||||||
|
assert 'actor.get("role") != "admin"' in source
|
||||||
|
assert '"admin_required"' in source
|
||||||
17
tests/backend/test_license_parity.py
Normal file
17
tests/backend/test_license_parity.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
|
||||||
|
|
||||||
|
def test_canonical_and_deployed_license_helpers_are_identical():
|
||||||
|
canonical = ROOT / "scripts/cezen_license.py"
|
||||||
|
deployed = ROOT / "ansible/roles/cezen-backend/files/cezen_license.py"
|
||||||
|
assert canonical.read_bytes() == deployed.read_bytes()
|
||||||
|
|
||||||
|
|
||||||
|
def test_customer_labels_preserve_compatibility_slugs():
|
||||||
|
source = (ROOT / "scripts/cezen_license.py").read_text()
|
||||||
|
for slug in ("starter", "basic", "pro", "max"):
|
||||||
|
assert slug in source
|
||||||
|
assert 'WORKSTATION_TIER = "workstation"' in source
|
||||||
31
tests/backend/test_migrations.py
Normal file
31
tests/backend/test_migrations.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import sqlite3
|
||||||
|
|
||||||
|
from ._source import load_symbols
|
||||||
|
|
||||||
|
|
||||||
|
def _migration_namespace():
|
||||||
|
return load_symbols("SCHEMA_VERSION", "_column_names", "run_migrations")
|
||||||
|
|
||||||
|
|
||||||
|
def test_migration_is_idempotent_and_creates_operation_contract():
|
||||||
|
db = sqlite3.connect(":memory:")
|
||||||
|
db.execute("CREATE TABLE audit_log (id INTEGER PRIMARY KEY, detail TEXT)")
|
||||||
|
ns = _migration_namespace()
|
||||||
|
assert ns["run_migrations"](db) == 1
|
||||||
|
assert ns["run_migrations"](db) == 1
|
||||||
|
columns = {row[1] for row in db.execute("PRAGMA table_info(operations)")}
|
||||||
|
assert {"id", "correlation_id", "kind", "state", "idempotency_key", "recovery_actions"} <= columns
|
||||||
|
assert "correlation_id" in {row[1] for row in db.execute("PRAGMA table_info(audit_log)")}
|
||||||
|
|
||||||
|
|
||||||
|
def test_newer_schema_is_rejected_without_mutation():
|
||||||
|
db = sqlite3.connect(":memory:")
|
||||||
|
db.execute("CREATE TABLE schema_metadata (key TEXT PRIMARY KEY, value TEXT NOT NULL)")
|
||||||
|
db.execute("INSERT INTO schema_metadata VALUES ('schema_version','999')")
|
||||||
|
ns = _migration_namespace()
|
||||||
|
try:
|
||||||
|
ns["run_migrations"](db)
|
||||||
|
except RuntimeError as exc:
|
||||||
|
assert "newer than supported" in str(exc)
|
||||||
|
else:
|
||||||
|
raise AssertionError("newer schema was accepted")
|
||||||
25
tests/backend/test_operations.py
Normal file
25
tests/backend/test_operations.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import sqlite3
|
||||||
|
|
||||||
|
from ._source import load_symbols
|
||||||
|
|
||||||
|
|
||||||
|
def test_operation_states_include_recovery_and_terminal_states():
|
||||||
|
ns = load_symbols("OPERATION_STATES", "TERMINAL_OPERATION_STATES")
|
||||||
|
assert {"requested", "running", "awaiting_reboot", "recovery_required"} <= ns["OPERATION_STATES"]
|
||||||
|
assert ns["TERMINAL_OPERATION_STATES"] <= ns["OPERATION_STATES"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_operation_schema_prevents_duplicate_kind_and_idempotency_key():
|
||||||
|
ns = load_symbols("SCHEMA_VERSION", "_column_names", "run_migrations")
|
||||||
|
db = sqlite3.connect(":memory:")
|
||||||
|
db.execute("CREATE TABLE audit_log (id INTEGER PRIMARY KEY, detail TEXT)")
|
||||||
|
ns["run_migrations"](db)
|
||||||
|
values = ("1", "c1", "install", "requested", "now", "now", "same")
|
||||||
|
sql = "INSERT INTO operations (id,correlation_id,kind,state,requested_at,updated_at,idempotency_key) VALUES (?,?,?,?,?,?,?)"
|
||||||
|
db.execute(sql, values)
|
||||||
|
try:
|
||||||
|
db.execute(sql, ("2", "c2", "install", "requested", "now", "now", "same"))
|
||||||
|
except sqlite3.IntegrityError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise AssertionError("duplicate idempotent operation accepted")
|
||||||
23
tests/backend/test_readiness.py
Normal file
23
tests/backend/test_readiness.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
MAIN = ROOT / "ansible/roles/cezen-backend/files/main.py"
|
||||||
|
|
||||||
|
|
||||||
|
def test_readiness_contract_has_state_freshness_impact_and_actions():
|
||||||
|
source = MAIN.read_text()
|
||||||
|
assert '"cezen.readiness_report.v2"' in source
|
||||||
|
for field in ("observed_at", "freshness_seconds", "affected_capabilities", "next_actions"):
|
||||||
|
assert field in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_readiness_contract_preserves_entitlement_reasons():
|
||||||
|
source = MAIN.read_text()
|
||||||
|
for reason in ("not_licensed", "not_provisioned", "unsupported_hardware", "degraded", "unavailable"):
|
||||||
|
assert reason in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_readiness_display_target_is_ten_seconds():
|
||||||
|
spec = " ".join((ROOT / "specs/001-enterprise-experience/spec.md").read_text().split())
|
||||||
|
assert "within 10 seconds" in spec
|
||||||
16
tests/browser/README.md
Normal file
16
tests/browser/README.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Browser Journey and Accessibility Tests
|
||||||
|
|
||||||
|
Browser tests MUST run against a disposable local or appliance target, never a production customer.
|
||||||
|
|
||||||
|
Required coverage:
|
||||||
|
|
||||||
|
- supported desktop browser viewport plus 200% zoom/reflow;
|
||||||
|
- keyboard-only primary and failure/recovery journeys;
|
||||||
|
- visible focus, logical order, accessible names, headings and landmarks;
|
||||||
|
- dynamic status, progress, validation and completion announcements;
|
||||||
|
- loading, empty, ready, degraded, failed, restricted, unavailable and stale states;
|
||||||
|
- 1-second navigation feedback and 2-second operation acknowledgement trials;
|
||||||
|
- screenshots and machine-readable results stored outside source control.
|
||||||
|
|
||||||
|
The final browser runner and dependency versions MUST be pinned when implementation selects them.
|
||||||
|
Automated accessibility results never replace the manual WCAG 2.2 AA checklist.
|
||||||
26
tests/browser/test_setup_journey.py
Normal file
26
tests/browser/test_setup_journey.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
WEB_SETUP = ROOT / "autoinstall/websetup/server.py"
|
||||||
|
|
||||||
|
|
||||||
|
def test_web_setup_exposes_validation_and_customer_facing_license_labels():
|
||||||
|
source = WEB_SETUP.read_text()
|
||||||
|
assert "validate_static_network" in source
|
||||||
|
assert "LICENSE_STATUS_LABELS" in source
|
||||||
|
assert "Invalid signature — re-upload a valid signed license" in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_phase1_contract_requires_durable_progress_not_only_process_memory():
|
||||||
|
source = WEB_SETUP.read_text()
|
||||||
|
# This test intentionally fails until T026 replaces process-local-only state.
|
||||||
|
assert "INSTALL_OPERATION_JSON" in source
|
||||||
|
assert "idempotency_key" in source
|
||||||
|
assert "operation_id" in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_setup_acknowledgement_contract_is_explicit():
|
||||||
|
source = WEB_SETUP.read_text()
|
||||||
|
assert "acknowledged_at" in source
|
||||||
|
assert "duplicate_request" in source
|
||||||
34
tests/conftest.py
Normal file
34
tests/conftest.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
BACKEND_MAIN = REPO_ROOT / "ansible/roles/cezen-backend/files/main.py"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def isolated_appliance(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> dict[str, Path]:
|
||||||
|
"""Return isolated appliance paths and never touch /opt/cezen."""
|
||||||
|
data = tmp_path / "data"
|
||||||
|
backups = tmp_path / "backups"
|
||||||
|
data.mkdir()
|
||||||
|
backups.mkdir()
|
||||||
|
monkeypatch.setenv("CEZEN_DATA", str(data))
|
||||||
|
monkeypatch.setenv("CEZEN_BACKUP_DIR", str(backups))
|
||||||
|
monkeypatch.setenv("CEZEN_LICENSE_JSON", str(tmp_path / "license.json"))
|
||||||
|
return {"root": tmp_path, "data": data, "backups": backups}
|
||||||
|
|
||||||
|
|
||||||
|
def load_backend_module(module_name: str = "cezen_backend_test"):
|
||||||
|
"""Load the packaged backend only after fixtures set environment variables."""
|
||||||
|
spec = importlib.util.spec_from_file_location(module_name, BACKEND_MAIN)
|
||||||
|
if spec is None or spec.loader is None:
|
||||||
|
raise RuntimeError(f"Cannot load backend from {BACKEND_MAIN}")
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
return module
|
||||||
18
tests/deployment/README.md
Normal file
18
tests/deployment/README.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Deployment Validation
|
||||||
|
|
||||||
|
Run from the repository root on a safe validation host.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash -n install.sh autoinstall/firstboot-setup.sh autoinstall/build-iso.sh \
|
||||||
|
autoinstall/build-iso-workstation.sh autoinstall/build-iso-starter.sh
|
||||||
|
ansible-playbook --syntax-check ansible/starter.yml
|
||||||
|
ansible-playbook --syntax-check ansible/entry.yml
|
||||||
|
ansible-playbook --syntax-check ansible/pro.yml
|
||||||
|
ansible-playbook --syntax-check ansible/max.yml
|
||||||
|
python3 -m py_compile ansible/roles/cezen-backend/files/main.py \
|
||||||
|
ansible/roles/cezen-backend/files/cezen_license.py autoinstall/websetup/server.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Syntax checks do not prove installation. Phase 1 additionally requires clean-install, interruption,
|
||||||
|
reboot/resume, restricted-network, migration, backup/restore and source-to-artifact evidence on one
|
||||||
|
representative Workstation and one representative Server.
|
||||||
21
tests/deployment/test_firstboot_setup.py
Normal file
21
tests/deployment/test_firstboot_setup.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
FIRSTBOOT = ROOT / "autoinstall/firstboot-setup.sh"
|
||||||
|
|
||||||
|
|
||||||
|
def test_console_setup_preserves_workstation_separation_and_safe_completion():
|
||||||
|
source = FIRSTBOOT.read_text()
|
||||||
|
assert 'WORKSTATION_MODE' in source
|
||||||
|
assert 'TIER_DISPLAY="workstation"' in source
|
||||||
|
assert 'touch /opt/cezen/.setup-done' in source
|
||||||
|
assert source.index('touch /opt/cezen/.setup-done') > source.index('if bash "$AIPACKAGE_DIR/install.sh"')
|
||||||
|
|
||||||
|
|
||||||
|
def test_console_setup_has_resume_and_recovery_contract():
|
||||||
|
source = FIRSTBOOT.read_text()
|
||||||
|
# Intentionally red until T028 adds durable console progress.
|
||||||
|
assert "cezen.setup_operation.v1" in source
|
||||||
|
assert "recovery_required" in source
|
||||||
|
assert "resume" in source.lower()
|
||||||
18
tests/fixtures/README.md
vendored
Normal file
18
tests/fixtures/README.md
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Appliance Test Fixtures
|
||||||
|
|
||||||
|
## Supported Baseline
|
||||||
|
|
||||||
|
Capture the exact prior released revision, package/ISO checksum, Ubuntu version, product category,
|
||||||
|
commercial tier, schema version and enabled components before migration testing. A moving `main`
|
||||||
|
checkout is not a valid baseline.
|
||||||
|
|
||||||
|
## Restore Fixture Requirements
|
||||||
|
|
||||||
|
- Synthetic users, audit events, settings and operational state only.
|
||||||
|
- No real credentials, signed licenses, customer documents or private logs.
|
||||||
|
- Valid checksums and a documented schema/product version.
|
||||||
|
- Representative Workstation and Server install records.
|
||||||
|
- Valid, missing/staging, expired, invalid-signature and mismatch license fixtures using test keys.
|
||||||
|
- Corrupt, incomplete and incompatible backup fixtures for negative testing.
|
||||||
|
|
||||||
|
Fixtures containing secrets or customer content MUST NOT enter source control.
|
||||||
29
tests/fixtures/setup-matrix.json
vendored
Normal file
29
tests/fixtures/setup-matrix.json
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"schema": "cezen.test.setup_matrix.v1",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"id": "representative-workstation",
|
||||||
|
"category": "workstation",
|
||||||
|
"commercial_label": "Nexus One AI Workstation",
|
||||||
|
"compatibility_profile": "starter",
|
||||||
|
"expected_server_tier_menu": false,
|
||||||
|
"journeys": ["valid", "field-staging", "invalid-license", "network-loss", "service-failure", "reboot-resume", "stale-readiness"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "representative-server-m",
|
||||||
|
"category": "server",
|
||||||
|
"commercial_label": "Server M",
|
||||||
|
"compatibility_tier": "basic",
|
||||||
|
"expected_server_tier_menu": true,
|
||||||
|
"journeys": ["valid", "field-staging", "invalid-license", "hardware-restriction", "network-loss", "service-failure", "reboot-resume", "stale-readiness"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"common_assertions": [
|
||||||
|
"non-secret progress persists",
|
||||||
|
"duplicate submission is idempotent",
|
||||||
|
"customer-facing state is truthful",
|
||||||
|
"safe next action is present",
|
||||||
|
"restricted-network help is local",
|
||||||
|
"WCAG 2.2 AA evidence is recorded"
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user