Skip to content

Commit aa01aa6

Browse files
authored
Merge pull request #914 from opsmill/develop
Merge develop into infrahub-develop
2 parents 6614764 + a9a6893 commit aa01aa6

50 files changed

Lines changed: 5672 additions & 18 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.specify/scripts/bash/common.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ get_current_branch() {
2828

2929
# For non-git repos, try to find the latest feature directory
3030
local repo_root=$(get_repo_root)
31-
local specs_dir="$repo_root/specs"
31+
local specs_dir="$repo_root/dev/specs"
3232

3333
if [[ -d "$specs_dir" ]]; then
3434
local latest_feature=""
@@ -81,14 +81,14 @@ check_feature_branch() {
8181
return 0
8282
}
8383

84-
get_feature_dir() { echo "$1/specs/$2"; }
84+
get_feature_dir() { echo "$1/dev/specs/$2"; }
8585

8686
# Find feature directory by numeric prefix instead of exact branch match
8787
# This allows multiple branches to work on the same spec (e.g., 004-fix-bug, 004-add-feature)
8888
find_feature_dir_by_prefix() {
8989
local repo_root="$1"
9090
local branch_name="$2"
91-
local specs_dir="$repo_root/specs"
91+
local specs_dir="$repo_root/dev/specs"
9292

9393
# Extract numeric prefix from branch (e.g., "004" from "004-whatever")
9494
if [[ ! "$branch_name" =~ ^([0-9]{3})- ]]; then
@@ -99,7 +99,7 @@ find_feature_dir_by_prefix() {
9999

100100
local prefix="${BASH_REMATCH[1]}"
101101

102-
# Search for directories in specs/ that start with this prefix
102+
# Search for directories in dev/specs/ that start with this prefix
103103
local matches=()
104104
if [[ -d "$specs_dir" ]]; then
105105
for dir in "$specs_dir"/"$prefix"-*; do

.specify/templates/plan-template.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Implementation Plan: [FEATURE]
22

33
**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link]
4-
**Input**: Feature specification from `/specs/[###-feature-name]/spec.md`
4+
**Input**: Feature specification from `/dev/specs/[###-feature-name]/spec.md`
55

66
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
77

@@ -38,7 +38,7 @@
3838
### Documentation (this feature)
3939

4040
```text
41-
specs/[###-feature]/
41+
dev/specs/[###-feature]/
4242
├── plan.md # This file (/speckit.plan command output)
4343
├── research.md # Phase 0 output (/speckit.plan command)
4444
├── data-model.md # Phase 1 output (/speckit.plan command)

.specify/templates/tasks-template.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: "Task list template for feature implementation"
55

66
# Tasks: [FEATURE NAME]
77

8-
**Input**: Design documents from `/specs/[###-feature-name]/`
8+
**Input**: Design documents from `/dev/specs/[###-feature-name]/`
99
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/
1010

1111
**Tests**: The examples below include test tasks. Tests are OPTIONAL - only include them if explicitly requested in the feature specification.

.vale/styles/spelling-exceptions.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ validators
133133
Version Control
134134
Vitest
135135
VLANs
136+
yaml
136137
Yaml
137138
yamllint
138139
YouTube

dev/commands/speckit.specify.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Given that feature description, do this:
4747
b. Find the highest feature number across all sources for the short-name:
4848
- Remote branches: `git ls-remote --heads origin | grep -E 'refs/heads/[0-9]+-<short-name>$'`
4949
- Local branches: `git branch | grep -E '^[* ]*[0-9]+-<short-name>$'`
50-
- Specs directories: Check for directories matching `specs/[0-9]+-<short-name>`
50+
- Specs directories: Check for directories matching `dev/specs/[0-9]+-<short-name>`
5151

5252
c. Determine the next available number:
5353
- Extract all numbers from all three sources
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Specification Quality Checklist: End-User CLI (`infrahubctl` command)
2+
3+
**Purpose**: Validate specification completeness and quality before proceeding to planning
4+
**Created**: 2026-03-28
5+
**Feature**: [spec.md](../spec.md)
6+
7+
## Content Quality
8+
9+
- [x] No implementation details (languages, frameworks, APIs)
10+
- [x] Focused on user value and business needs
11+
- [x] Written for non-technical stakeholders
12+
- [x] All mandatory sections completed
13+
14+
## Requirement Completeness
15+
16+
- [x] No [NEEDS CLARIFICATION] markers remain
17+
- [x] Requirements are testable and unambiguous
18+
- [x] Success criteria are measurable
19+
- [x] Success criteria are technology-agnostic (no implementation details)
20+
- [x] All acceptance scenarios are defined
21+
- [x] Edge cases are identified
22+
- [x] Scope is clearly bounded
23+
- [x] Dependencies and assumptions identified
24+
25+
## Feature Readiness
26+
27+
- [x] All functional requirements have clear acceptance criteria
28+
- [x] User scenarios cover primary flows
29+
- [x] Feature meets measurable outcomes defined in Success Criteria
30+
- [x] No implementation details leak into specification
31+
32+
## Notes
33+
34+
- All items pass validation. Spec is ready for `/speckit.clarify` or `/speckit.plan`.
35+
- Assumptions section documents reasonable defaults for unspecified details.
36+
- CLI command examples in acceptance scenarios use generic syntax (not framework-specific).
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# CLI Command Contracts
2+
3+
## Global Options
4+
5+
All commands accept:
6+
7+
- `--branch TEXT` — Target Infrahub branch (default: from config)
8+
- `--config-file PATH` — Configuration file path (default: infrahubctl.toml)
9+
- `--output [table|json|csv|yaml]` — Output format (default: table if TTY, json if piped)
10+
11+
## `infrahubctl get <kind> [identifier]`
12+
13+
**List mode** (no identifier):
14+
15+
- Input: kind (positional), --filter (repeatable), --limit INT, --offset INT
16+
- Output: Table with columns for each attribute + relationship (display names)
17+
- Exit 0: results found | Exit 80: no results (empty list) | Exit 1: invalid kind
18+
19+
**Detail mode** (with identifier):
20+
21+
- Input: kind (positional), identifier (positional — UUID or display name)
22+
- Output: Key-value display of all attributes, relationships, metadata
23+
- Exit 0: found | Exit 1: not found
24+
25+
**Filters**: `--filter name__value="spine01"` (repeatable)
26+
27+
## `infrahubctl create <kind>`
28+
29+
- Input: kind (positional), --set key=value (repeatable), --file PATH
30+
- --set and --file are mutually exclusive
31+
- Output: Confirmation with created object ID and display label
32+
- Exit 0: created | Exit 1: validation error | Exit 1: server error
33+
34+
**File input**: JSON or YAML in Infrahub Object format
35+
(`apiVersion: infrahub.app/v1`)
36+
37+
## `infrahubctl update <kind> <identifier>`
38+
39+
- Input: kind (positional), identifier (positional), --set key=value
40+
(repeatable), --file PATH
41+
- --set and --file are mutually exclusive
42+
- Output: Confirmation with old → new values for changed fields
43+
- Exit 0: updated | Exit 1: not found | Exit 1: validation error
44+
45+
## `infrahubctl delete <kind> <identifier>`
46+
47+
- Input: kind (positional), identifier (positional), --yes (skip confirmation)
48+
- Output: Confirmation prompt (unless --yes), then success message
49+
- Exit 0: deleted | Exit 1: not found | Exit 1: dependency conflict
50+
51+
## `infrahubctl schema list`
52+
53+
- Input: --filter TEXT (substring match on kind name)
54+
- Output: Table with columns: Namespace, Name, Kind, Description
55+
- Exit 0: always (empty table if no matches)
56+
57+
## `infrahubctl schema show <kind>`
58+
59+
- Input: kind (positional)
60+
- Output: Formatted display of:
61+
- Kind metadata (namespace, label, description, display_labels, HFID)
62+
- Attributes table (name, type, required, default, description)
63+
- Relationships table (name, peer kind, cardinality, optional)
64+
- Exit 0: found | Exit 1: invalid kind
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Data Model: End-User CLI
2+
3+
This feature does not introduce new persistent data entities. It operates on
4+
Infrahub's existing data model (Kinds, Nodes, Attributes, Relationships) via
5+
the SDK client.
6+
7+
The CLI introduces transient structures for formatting and serialization:
8+
9+
## Output Format Envelope
10+
11+
Used when serializing query results to YAML output format.
12+
13+
**Fields**:
14+
15+
- `apiVersion` (str): Always `"infrahub.app/v1"`
16+
- `kind` (str): Always `"Object"`
17+
- `spec.kind` (str): The Infrahub Kind being exported (e.g., `"InfraDevice"`)
18+
- `spec.data` (list[dict]): Array of serialized node objects
19+
20+
Each node in `spec.data` contains:
21+
22+
- Attribute fields as `key: value` pairs
23+
- Relationship fields as `key: display_name` (single) or
24+
`key: {data: [list]}` (many)
25+
26+
This structure matches the existing `InfrahubObjectFileData` model in
27+
`infrahub_sdk/spec/object.py` and is round-trippable with `ObjectFile`.
28+
29+
## Set Flag Parser
30+
31+
Parses `--set key=value` arguments into a dict suitable for SDK calls.
32+
33+
**Input**: List of `"key=value"` strings from CLI
34+
**Output**: `dict[str, str | list[str]]`
35+
36+
Validation rules:
37+
38+
- Key MUST exist as an attribute or relationship name in the target Kind's schema
39+
- Value is a string; the SDK handles type coercion
40+
- For relationships (cardinality ONE), value is the HFID or UUID of the target node (e.g., `--set site=DC1`)
41+
- For relationships (cardinality MANY), value is a JSON array of HFID arrays (e.g., `--set tags=[["blue"], ["red"]]`). Each inner array is an HFID supporting multi-component keys (e.g., `[["Cisco", "NX-OS"]]`). The parser detects `[...]` and parses as JSON.
42+
43+
**Relationship resolution**: The CLI passes relationship values through to the
44+
SDK as HFID references. The SDK/server is responsible for resolving HFIDs to
45+
internal IDs. The CLI MUST NOT perform its own lookup round-trips.
46+
47+
**SDK dependencies**:
48+
49+
- [opsmill/infrahub-sdk-python#267](https://github.com/opsmill/infrahub-sdk-python/issues/267)`rebuild_hfid_from_data()`: reconstruct HFID from flat user data based on schema definition
50+
- [opsmill/infrahub-sdk-python#272](https://github.com/opsmill/infrahub-sdk-python/issues/272)`node.update(data)`: update attributes and relationships from a dict (eliminates manual per-field mutation)
51+
52+
## Filter Parser
53+
54+
Parses `--filter key=value` arguments into kwargs for `client.filters()`.
55+
56+
**Input**: List of `"attribute__value=x"` strings from CLI
57+
**Output**: `dict[str, Any]` passed as `**kwargs`
58+
59+
Validation rules:
60+
61+
- Key MUST follow the `attribute__value` or `relationship__id` pattern
62+
- Invalid keys produce a validation error with available field names

0 commit comments

Comments
 (0)