@use-the-fork/spindle 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/build/Assets/Blueprints/Catalog.js +12 -7
  2. package/build/Assets/Blueprints/skills/author-agent/SKILL.md +9 -8
  3. package/build/Assets/Blueprints/skills/author-instruction/SKILL.md +9 -8
  4. package/build/Assets/Blueprints/skills/author-skill/SKILL.md +9 -8
  5. package/build/Assets/Blueprints/skills/grill/SKILL.md +3 -3
  6. package/build/Assets/Blueprints/skills/int-subagent-driven-dev/SKILL.md +6 -6
  7. package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/corrective-rounds.md +1 -1
  8. package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/example-workflow.md +4 -4
  9. package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/full-spec.md +12 -12
  10. package/build/Assets/Blueprints/skills/int-testing-agent-guidance/SKILL.md +2 -2
  11. package/build/Assets/Blueprints/skills/int-writing-skills/SKILL.md +2 -2
  12. package/build/Assets/Blueprints/skills/int-writing-skills/references/testing-skills.md +2 -2
  13. package/build/Assets/Blueprints/skills/ref-php-laravel-saloon-api-integrations/SKILL.md +38 -0
  14. package/build/Assets/Blueprints/skills/ref-php-laravel-saloon-api-integrations/references/concurrency-and-pools.md +73 -0
  15. package/build/Assets/Blueprints/skills/ref-php-laravel-saloon-api-integrations/references/connectors.md +99 -0
  16. package/build/Assets/Blueprints/skills/ref-php-laravel-saloon-api-integrations/references/data-transfer-objects.md +104 -0
  17. package/build/Assets/Blueprints/skills/ref-php-laravel-saloon-api-integrations/references/handling-failures.md +95 -0
  18. package/build/Assets/Blueprints/skills/ref-php-laravel-saloon-api-integrations/references/requests.md +111 -0
  19. package/build/Assets/Blueprints/skills/ref-php-laravel-saloon-api-integrations/references/responses.md +56 -0
  20. package/build/Assets/Blueprints/skills/ref-php-laravel-saloon-api-integrations/references/sending-requests.md +42 -0
  21. package/build/Assets/Blueprints/skills/ref-php-laravel-saloon-api-integrations/references/testing.md +119 -0
  22. package/build/Assets/Blueprints/skills/{author-design → sdd-author-design}/SKILL.md +12 -4
  23. package/build/Assets/Blueprints/skills/{grill-design → sdd-grill-design}/SKILL.md +1 -1
  24. package/build/Assets/Blueprints/skills/{implement-spec → sdd-implement-spec}/SKILL.md +3 -3
  25. package/build/Assets/Blueprints/skills/{to-spec → sdd-to-spec}/SKILL.md +3 -3
  26. package/build/Assets/Blueprints/skills/{to-tickets → sdd-to-tickets}/SKILL.md +3 -3
  27. package/build/Assets/Blueprints/skills/{setup-project → spindle-project-setup}/SKILL.md +4 -4
  28. package/build/Assets/Blueprints/skills/{update-tooling → spindle-tooling-update}/SKILL.md +5 -5
  29. package/build/Assets/Templates/project/copilot-instructions.md +3 -9
  30. package/build/Assets/Templates/project/ticket-tracker.md +5 -5
  31. package/build/BlueprintManagement/Commands/Prompts.js +1 -1
  32. package/build/TemplateRendering/Computed/ComputedVariableProviders.js +2 -0
  33. package/build/TemplateRendering/Computed/Providers/AuthoringFlowchartProvider.js +26 -0
  34. package/package.json +1 -1
@@ -0,0 +1,104 @@
1
+ # Data Transfer Objects
2
+
3
+ A data transfer object (DTO) gives typed fields to API data.
4
+ Define response mapping on the request, or on the connector when all requests share that mapping.
5
+ Retrieve the DTO from the response.
6
+
7
+ ## Define the DTO and mapping
8
+
9
+ ```php
10
+ final class Product
11
+ {
12
+ public function __construct(
13
+ public readonly int $id,
14
+ public readonly string $name,
15
+ public readonly int $priceCents,
16
+ ) {}
17
+ }
18
+ ```
19
+
20
+ ```php
21
+ use Saloon\Http\Request;
22
+ use Saloon\Http\Response;
23
+
24
+ class GetProductRequest extends Request
25
+ {
26
+ // method, constructor, and endpoint ...
27
+
28
+ public function createDtoFromResponse(Response $response): Product
29
+ {
30
+ $data = $response->json();
31
+
32
+ return new Product(
33
+ id: $data['id'],
34
+ name: $data['name'],
35
+ priceCents: $data['price_cents'],
36
+ );
37
+ }
38
+ }
39
+ ```
40
+
41
+ ## Retrieve the DTO
42
+
43
+ ```php
44
+ $response = $connector->send(new GetProductRequest(42));
45
+
46
+ $product = $response->dtoOrFail(); // throws LogicException if the response failed
47
+ $product = $response->dto(); // always maps, even for failed responses
48
+ ```
49
+
50
+ Use `dtoOrFail()` for success-only DTOs. It prevents mapping an error body into `Product`.
51
+ Use `dto()` only when the mapping handles error payloads.
52
+ "Failed" follows `hasRequestFailed()`; see [Handling failures](handling-failures.md).
53
+
54
+ `dto()` returns `mixed`. For static analysis, annotate it with `/** @var Product $product */`, or call the typed method directly: `$request->createDtoFromResponse($response)`.
55
+
56
+ ## DTOs in requests
57
+
58
+ Accept a DTO in a request constructor to build the request body.
59
+
60
+ ```php
61
+ use Saloon\Contracts\Body\HasBody;
62
+ use Saloon\Enums\Method;
63
+ use Saloon\Traits\Body\HasJsonBody;
64
+
65
+ class UpdateProductRequest extends Request implements HasBody
66
+ {
67
+ use HasJsonBody;
68
+
69
+ protected Method $method = Method::PUT;
70
+
71
+ public function __construct(protected readonly Product $product) {}
72
+
73
+ public function resolveEndpoint(): string
74
+ {
75
+ return '/products/' . $this->product->id;
76
+ }
77
+
78
+ protected function defaultBody(): array
79
+ {
80
+ return [
81
+ 'name' => $this->product->name,
82
+ 'price_cents' => $this->product->priceCents,
83
+ ];
84
+ }
85
+ }
86
+ ```
87
+
88
+ ## Keep the source response
89
+
90
+ Implement `WithResponse` and use `HasResponse` to attach the original response to the DTO.
91
+
92
+ ```php
93
+ use Saloon\Contracts\DataObjects\WithResponse;
94
+ use Saloon\Traits\Responses\HasResponse;
95
+
96
+ final class Product implements WithResponse
97
+ {
98
+ use HasResponse;
99
+
100
+ // constructor ...
101
+ }
102
+
103
+ $product->getResponse()->status();
104
+ ```
@@ -0,0 +1,95 @@
1
+ # Handling Failures
2
+
3
+ Saloon separates two kinds of failure:
4
+
5
+ - **HTTP failure**: the API returned 4xx/5xx. Synchronous `send()` returns a `Response` with `failed()` set to `true`, unless throwing is enabled.
6
+ - **Connection failure**: the API returned no response, such as a DNS error or connection timeout. Saloon throws `FatalRequestException`.
7
+
8
+ ```php
9
+ $response = $connector->send(new CreateProductRequest('Widget', 999));
10
+
11
+ if ($response->failed()) {
12
+ $errors = $response->json('errors'); // e.g. a 422 validation payload
13
+ }
14
+ ```
15
+
16
+ ## Throwing on HTTP failures
17
+
18
+ Call `throw()` to throw on a failed response. It returns the response on success.
19
+
20
+ ```php
21
+ $product = $connector->send(new GetProductRequest(42))->throw()->json();
22
+ ```
23
+
24
+ For every request on a connector, add `AlwaysThrowOnErrors`.
25
+
26
+ ```php
27
+ use Saloon\Traits\Plugins\AlwaysThrowOnErrors;
28
+
29
+ class InventoryConnector extends Connector
30
+ {
31
+ use AlwaysThrowOnErrors;
32
+ }
33
+ ```
34
+
35
+ ## Exception hierarchy
36
+
37
+ Catch the most specific type the application needs. `RequestException` exposes the response through `getResponse()`.
38
+
39
+ ```
40
+ SaloonException
41
+ ├── FatalRequestException connection errors, no response
42
+ └── RequestException HTTP 4xx/5xx
43
+ ├── ClientException 4xx
44
+ │ ├── UnauthorizedException (401), PaymentRequiredException (402)
45
+ │ ├── ForbiddenException (403), NotFoundException (404)
46
+ │ ├── MethodNotAllowedException (405), RequestTimeOutException (408)
47
+ │ └── UnprocessableEntityException (422), TooManyRequestsException (429)
48
+ └── ServerException 5xx
49
+ └── InternalServerErrorException (500), ServiceUnavailableException (503), GatewayTimeoutException (504)
50
+ ```
51
+
52
+ ```php
53
+ use Saloon\Exceptions\Request\Statuses\UnprocessableEntityException;
54
+
55
+ try {
56
+ $connector->send(new CreateProductRequest('', 0))->throw();
57
+ } catch (UnprocessableEntityException $e) {
58
+ $errors = $e->getResponse()->json('errors');
59
+ }
60
+ ```
61
+
62
+ ## Async and pools
63
+
64
+ Promises from `sendAsync()` and pools reject on HTTP and connection failures.
65
+ Handle both in `otherwise` or the pool's exception handler. Calling `throw()` is unnecessary.
66
+ For callback examples, read [Sending requests](sending-requests.md) or [Concurrency and pools](concurrency-and-pools.md).
67
+
68
+ ## Custom failure detection
69
+
70
+ Some APIs return 200 with an error in the body. Override `hasRequestFailed()` on the connector or request; returning `null` falls back to the status-code check.
71
+
72
+ ```php
73
+ use Saloon\Http\Response;
74
+
75
+ public function hasRequestFailed(Response $response): ?bool
76
+ {
77
+ return $response->json('status') === 'error';
78
+ }
79
+ ```
80
+
81
+ This changes `failed()`, `throw()`, `dtoOrFail()`, and `AlwaysThrowOnErrors`.
82
+
83
+ ## Custom exception
84
+
85
+ Override `getRequestException()` on the connector or request to throw an application exception. A request's override wins over the connector's.
86
+
87
+ ```php
88
+ use Saloon\Http\Response;
89
+ use Throwable;
90
+
91
+ public function getRequestException(Response $response, ?Throwable $senderException): ?Throwable
92
+ {
93
+ return new InventoryApiException($response->json('message', 'Inventory API error'), $response->status(), $senderException);
94
+ }
95
+ ```
@@ -0,0 +1,111 @@
1
+ # Requests
2
+
3
+ Create a request class for each endpoint operation.
4
+ Saloon appends the endpoint to the connector's base URL.
5
+ Keep request classes beside their connector.
6
+
7
+ ## Minimal request
8
+
9
+ Extend `Saloon\Http\Request`, set `$method`, and implement `resolveEndpoint()`.
10
+
11
+ ```php
12
+ use Saloon\Enums\Method;
13
+ use Saloon\Http\Request;
14
+
15
+ class ListProductsRequest extends Request
16
+ {
17
+ protected Method $method = Method::GET;
18
+
19
+ public function resolveEndpoint(): string
20
+ {
21
+ return '/products';
22
+ }
23
+ }
24
+ ```
25
+
26
+ With the Laravel plugin installed, `php artisan saloon:request` scaffolds one.
27
+
28
+ ## Constructor arguments
29
+
30
+ Pass identifiers and inputs through the constructor.
31
+
32
+ ```php
33
+ class GetProductRequest extends Request
34
+ {
35
+ protected Method $method = Method::GET;
36
+
37
+ public function __construct(protected readonly int $id) {}
38
+
39
+ public function resolveEndpoint(): string
40
+ {
41
+ return '/products/' . $this->id;
42
+ }
43
+ }
44
+ ```
45
+
46
+ ## Query parameters
47
+
48
+ Override `defaultQuery()`, or add to an instance with `$request->query()->add('page', 2)`.
49
+
50
+ ```php
51
+ protected function defaultQuery(): array
52
+ {
53
+ return [
54
+ 'sort' => 'name',
55
+ 'filter[active]' => 'true',
56
+ ];
57
+ }
58
+ ```
59
+
60
+ Saloon builds query strings with `http_build_query`.
61
+
62
+ ## Headers
63
+
64
+ Override `defaultHeaders()`; these merge with the connector's headers. Instance changes use `$request->headers()->add(...)`.
65
+
66
+ ```php
67
+ protected function defaultHeaders(): array
68
+ {
69
+ return ['Accept' => 'application/json'];
70
+ }
71
+ ```
72
+
73
+ ## JSON body
74
+
75
+ Implement `HasBody`, use `HasJsonBody`, and override `defaultBody()`.
76
+
77
+ ```php
78
+ use Saloon\Contracts\Body\HasBody;
79
+ use Saloon\Enums\Method;
80
+ use Saloon\Http\Request;
81
+ use Saloon\Traits\Body\HasJsonBody;
82
+
83
+ class CreateProductRequest extends Request implements HasBody
84
+ {
85
+ use HasJsonBody;
86
+
87
+ protected Method $method = Method::POST;
88
+
89
+ public function __construct(
90
+ protected readonly string $name,
91
+ protected readonly int $priceCents,
92
+ ) {}
93
+
94
+ public function resolveEndpoint(): string
95
+ {
96
+ return '/products';
97
+ }
98
+
99
+ protected function defaultBody(): array
100
+ {
101
+ return [
102
+ 'name' => $this->name,
103
+ 'price_cents' => $this->priceCents,
104
+ ];
105
+ }
106
+ }
107
+ ```
108
+
109
+ ## Timeouts
110
+
111
+ For endpoint-specific timeouts, use the trait and properties shown in [Timeouts](connectors.md#timeouts) on the request.
@@ -0,0 +1,56 @@
1
+ # Responses
2
+
3
+ Synchronous `send()` returns a `Saloon\Http\Response`.
4
+ For HTTP and connection failure behavior, read [Handling failures](handling-failures.md).
5
+
6
+ ## Reading a response
7
+
8
+ ```php
9
+ $response = $connector->send(new GetProductRequest(42));
10
+
11
+ $response->status(); // 200
12
+ $response->header('Content-Type');
13
+ $response->body(); // raw string
14
+ $response->json(); // decoded array
15
+ $response->json('data.name'); // nested key (dot notation)
16
+ $response->json('missing', 'x'); // default when absent
17
+ $response->object(); // stdClass
18
+ $response->collect('data'); // Laravel Collection (requires illuminate/collections)
19
+ ```
20
+
21
+ ## Useful methods
22
+
23
+ | Method | Purpose |
24
+ | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
25
+ | `status()`, `headers()`, `header($name)` | Status code and headers |
26
+ | `body()`, `json()` / `array()`, `object()`, `collect()` | Body as string, array, object, or Collection |
27
+ | `stream()`, `saveBodyToFile($path)` | Large or binary bodies |
28
+ | `xmlReader()`, `dom()` | XML (needs `saloonphp/xml-wrangler`) or HTML (needs `symfony/dom-crawler`) |
29
+ | `ok()`, `successful()`, `redirect()`, `failed()`, `clientError()`, `serverError()` | Status checks; `failed()` is customizable |
30
+ | `throw()` | Throw if the response is considered failed |
31
+ | `dto()`, `dtoOrFail()` | Convert to a DTO; see [Data Transfer Objects](data-transfer-objects.md) |
32
+ | `getPendingRequest()`, `getPsrRequest()`, `getPsrResponse()` | The built request and the raw PSR-7 messages |
33
+
34
+ ## Custom response class
35
+
36
+ Extend `Response` to add API-specific helpers.
37
+ Register the class on the connector for all requests, or on a request for that request only.
38
+
39
+ ```php
40
+ use Saloon\Http\Response;
41
+
42
+ class InventoryResponse extends Response
43
+ {
44
+ public function nextPage(): ?int
45
+ {
46
+ return $this->json('meta.next_page');
47
+ }
48
+ }
49
+
50
+ class InventoryConnector extends Connector
51
+ {
52
+ protected ?string $response = InventoryResponse::class;
53
+ }
54
+ ```
55
+
56
+ Override `resolveResponseClass(): string` instead of the property when the class must be chosen dynamically.
@@ -0,0 +1,42 @@
1
+ # Sending Requests
2
+
3
+ Pass a request to its connector to send it.
4
+
5
+ ## Synchronous
6
+
7
+ ```php
8
+ $connector = new InventoryConnector($baseUrl, $token);
9
+
10
+ $response = $connector->send(new ListProductsRequest);
11
+
12
+ $products = $response->json('data');
13
+ ```
14
+
15
+ `send()` returns a `Saloon\Http\Response`. A 4xx/5xx status does **not** throw by default; see [Handling Failures](handling-failures.md).
16
+
17
+ ## Asynchronous
18
+
19
+ `sendAsync()` returns a Guzzle `PromiseInterface`.
20
+ Successful responses reach `then`. HTTP and connection failures reject the promise and reach `otherwise`.
21
+
22
+ ```php
23
+ use Saloon\Exceptions\Request\FatalRequestException;
24
+ use Saloon\Exceptions\Request\RequestException;
25
+ use Saloon\Http\Response;
26
+
27
+ $promise = $connector->sendAsync(new ListProductsRequest);
28
+
29
+ $promise
30
+ ->then(function (Response $response) {
31
+ // handle success
32
+ })
33
+ ->otherwise(function (FatalRequestException|RequestException $exception) {
34
+ // RequestException: the API returned 4xx/5xx; use $exception->getResponse()
35
+ // FatalRequestException: no response, e.g. connection failure
36
+ });
37
+
38
+ $promise->wait(); // resolve the promise
39
+ ```
40
+
41
+ For concurrent batches, read [Concurrency and pools](concurrency-and-pools.md).
42
+ For failure classification and custom exceptions, read [Handling failures](handling-failures.md).
@@ -0,0 +1,119 @@
1
+ # Testing
2
+
3
+ Keep test runs offline. Use `MockClient` with fake responses or existing fixtures.
4
+ Assert the outgoing request and the application's handling of success and failure responses, including 401, 422, and 500.
5
+
6
+ ## Block network access
7
+
8
+ Block requests without a mock client and prevent automatic recording when a fixture is missing.
9
+ For Pest, add this setup to `tests/Pest.php`.
10
+ For PHPUnit, run it in the base `TestCase::setUp()` after `parent::setUp()`.
11
+
12
+ ```php
13
+ use Saloon\Config;
14
+ use Saloon\MockConfig;
15
+
16
+ Config::preventStrayRequests();
17
+ MockConfig::throwOnMissingFixtures();
18
+ ```
19
+
20
+ `preventStrayRequests()` alone does not stop fixture recording when a mock client is attached.
21
+ `throwOnMissingFixtures()` throws `FixtureMissingException` instead of recording a missing fixture.
22
+
23
+ ## Fake responses
24
+
25
+ `MockResponse::make(body, status, headers)` builds a response. An array body is encoded as JSON.
26
+
27
+ ```php
28
+ use Saloon\Http\Faking\MockClient;
29
+ use Saloon\Http\Faking\MockResponse;
30
+
31
+ $mockClient = new MockClient([
32
+ ListProductsRequest::class => MockResponse::make(['data' => [['id' => 1, 'name' => 'Widget']]], 200),
33
+ CreateProductRequest::class => MockResponse::make(['errors' => ['name' => ['Required']]], 422),
34
+ ]);
35
+ ```
36
+
37
+ Mock keys can also be:
38
+
39
+ - URL patterns with `*` wildcards: `'inventory.example.com/api/v1/products/*' => MockResponse::make(...)`.
40
+ - An unkeyed list, returned in sequence regardless of the request: `[MockResponse::make('first'), MockResponse::make('second')]`.
41
+ - A closure: `fn (PendingRequest $pendingRequest) => MockResponse::make(...)`.
42
+
43
+ ## Attach the mock
44
+
45
+ **Local**: attach the mock to a connector or request you control.
46
+
47
+ ```php
48
+ $connector = new InventoryConnector('https://inventory.example.com/api/v1', 'test-token');
49
+ $connector->withMockClient($mockClient);
50
+ ```
51
+
52
+ **Global**: mock requests sent deep inside application code.
53
+
54
+ ```php
55
+ $mockClient = MockClient::global([
56
+ ListProductsRequest::class => MockResponse::make(['data' => []], 200),
57
+ ]);
58
+ ```
59
+
60
+ Without the Laravel plugin, reset global mocks between tests with `MockClient::destroyGlobal()` in `beforeEach` or `setUp`.
61
+ The Laravel plugin resets them automatically.
62
+
63
+ ## Assertions
64
+
65
+ ```php
66
+ use Saloon\Http\Request;
67
+ use Saloon\Http\Response;
68
+
69
+ $mockClient->assertSent(ListProductsRequest::class);
70
+ $mockClient->assertNotSent(CreateProductRequest::class);
71
+ $mockClient->assertSentCount(1);
72
+ $mockClient->assertSentCount(1, ListProductsRequest::class);
73
+ ```
74
+
75
+ These are separate assertion examples. Use `assertNothingSent()` when no request should leave the application.
76
+ For a connector configured with `test-token`, assert the actual bearer header rather than a masked placeholder:
77
+
78
+ ```php
79
+ $token = 'test-token';
80
+
81
+ $mockClient->assertSent(function (Request $request, Response $response) use ($token) {
82
+ return $request instanceof CreateProductRequest
83
+ && $request->body()->all() === ['name' => 'Widget', 'price_cents' => 999]
84
+ && $response->getPsrRequest()->getHeaderLine('Authorization') === ('Bearer ' . $token);
85
+ });
86
+ ```
87
+
88
+ ## Complete example: a failure path
89
+
90
+ ```php
91
+ it('reports validation errors when the inventory API rejects a product', function () {
92
+ $mockClient = MockClient::global([
93
+ CreateProductRequest::class => MockResponse::make(['errors' => ['name' => ['Required']]], 422),
94
+ ]);
95
+
96
+ $result = app(ProductSync::class)->create(name: '', priceCents: 0);
97
+
98
+ expect($result->errors)->toBe(['name' => ['Required']]);
99
+
100
+ $mockClient->assertSent(fn (Request $request) => $request instanceof CreateProductRequest
101
+ && $request->body()->get('name') === '');
102
+ });
103
+ ```
104
+
105
+ A synchronous 422 returns a response rather than throwing.
106
+ If the code under test calls `throw()` or uses `AlwaysThrowOnErrors`, assert the exception instead.
107
+ See [Handling failures](handling-failures.md).
108
+
109
+ ## Fixtures
110
+
111
+ `MockResponse::fixture('inventory/products')` replays `tests/Fixtures/Saloon/inventory/products.json` when the file exists.
112
+ With the setup above, a missing file fails the test without contacting the API.
113
+ Change the directory with `MockConfig::setFixturePath('tests/fixtures/http')`.
114
+
115
+ Record fixtures only through an explicitly authorized process outside normal test runs and CI.
116
+ That process must omit `throwOnMissingFixtures()` and use credentials restricted to recording.
117
+ Without that guard, a missing fixture triggers a real API call and saves the response.
118
+ Use lowercase fixture paths. Redact secrets before committing fixture files.
119
+ To refresh a fixture, delete only its file in the recording process.
@@ -1,17 +1,25 @@
1
1
  ---
2
- name: author-design
2
+ name: sdd-author-design
3
3
  description: "Use when a user asks to author a design for a feature, workflow, or behavior that has important product decisions to settle."
4
4
  disable-model-invocation: true
5
5
  user-invocable: true
6
6
  metadata:
7
7
  spindle:
8
- version: 1.1.1
8
+ version: 1.1.2
9
9
  ---
10
10
 
11
11
  # Authoring a Design
12
12
 
13
13
  Help the user clarify what they want before turning an idea into a design. A design is optional; when one is created, it is authored and approved before its spec, and tickets follow the spec. Use this for product and project decisions, not as a gate for a clear, bounded task or a user who wants a spec without a design.
14
14
 
15
+ ## Required skills
16
+
17
+ Before drafting or editing, read:
18
+
19
+ - **REQUIRED:** `asd-ste100`
20
+
21
+ Apply the clarity rules from the `asd-ste100` skill. Include a before/after table only when the user requests one.
22
+
15
23
  ## Process
16
24
 
17
25
  1. **Check the brief.** Identify the user's goal, why it matters, what success means, and which important decisions remain open. In an existing project, inspect relevant code and docs first; do not ask for facts the project already answers. If the user asks for a design and the request is specific with no consequential decision open, proceed to the design without unnecessary questioning. **Complete when** the brief is actionable or a concrete unresolved decision is identified.
@@ -21,7 +29,7 @@ Help the user clarify what they want before turning an idea into a design. A des
21
29
  3. **Compare approaches.** Present two or three viable approaches grounded in the project and clarified goal, explain their main trade-offs, recommend one, and ask the user to choose. If only one approach fits, explain why alternatives do not. Wait for the choice before drafting. **Complete when** the user has selected an approach or delegated the choice.
22
30
 
23
31
  4. **Create or select the SDD feature workspace.**
24
- - For a new feature, choose a concise title and author identity, then run `spindle sdd create --title "<feature title>" --author "<author>" --with-design`. Use the returned workspace and spec paths; `design.md` is beside `spec.md`. The command creates both template files, but fill and approve the design before filling the spec.
32
+ - For a new feature, choose a concise title and author identity, then run `{{ computed.spindle_package_manager }} spindle sdd create --title "<feature title>" --author "<author>" --with-design`. Use the returned workspace and spec paths; `design.md` is beside `spec.md`. The command creates both template files, but fill and approve the design before filling the spec.
25
33
  - For an existing numbered SDD feature workspace, reuse it. If `design.md` already exists, preserve it and edit it only as requested. If it is missing, first confirm `spec.md` is still the untouched template; then copy the design template from the Spindle installation's `build/Assets/Templates/sdd/design.md` into that workspace without overwriting existing files. In a source checkout, use `src/Assets/Templates/sdd/design.md`.
26
34
  - If the existing spec has substantive content, stop before adding or revising a design: that would put design after spec. Explain the conflict and ask whether the user wants to continue as an explicit exception or keep the feature spec-only.
27
35
  - Do not create a dated feature directory or a second SDD workspace for the same feature.
@@ -30,7 +38,7 @@ Help the user clarify what they want before turning an idea into a design. A des
30
38
 
31
39
  5. **Write the design.** Use the existing `design.md` scaffold, replacing its `[PLACEHOLDER]` fields with the selected intent, success criteria, scope and constraints, considered approaches and trade-offs, chosen direction, and assumptions or open decisions. Write success criteria as observable outcomes that show the design achieves its intent; include measurable targets where practical without inventing precision. Leave detailed requirements and acceptance scenarios to the subsequent spec. Preserve unresolved decisions explicitly. **Complete when** the design represents the conversation, states observable success outcomes, and distinguishes user decisions from recommendations.
32
40
 
33
- 6. **Get approval.** Share the design path and its content for user approval or correction. Do not begin spec drafting until the user approves the design. If the user opts out of brainstorming or asks for a spec without design, respect that choice; do not create a design as a gate. After approval, ask the user to invoke `/to-spec` to create the spec in the same feature workspace; do not invoke it automatically. **Complete when** the design is approved, corrected for another review, or the user has chosen the spec-only path.
41
+ 6. **Get approval.** Share the design path and its content for user approval or correction. Do not begin spec drafting until the user approves the design. If the user opts out of brainstorming or asks for a spec without design, respect that choice; do not create a design as a gate. After approval, ask the user to invoke `/sdd-to-spec` to create the spec in the same feature workspace; do not invoke it automatically. **Complete when** the design is approved, corrected for another review, or the user has chosen the spec-only path.
34
42
 
35
43
  ## Keep the conversation useful
36
44
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: grill-design
2
+ name: sdd-grill-design
3
3
  description: "Challenge and refine an existing design through an interview, updating the design and relevant domain documentation."
4
4
  argument-hint: "[design.md path or feature directory]"
5
5
  disable-model-invocation: true
@@ -1,6 +1,6 @@
1
1
  ---
2
- name: implement-spec
3
- description: "Implement the result of /to-spec and /to-tickets in code."
2
+ name: sdd-implement-spec
3
+ description: "Implement the result of /sdd-to-spec and /sdd-to-tickets in code."
4
4
  disable-model-invocation: true
5
5
  metadata:
6
6
  spindle:
@@ -9,7 +9,7 @@ metadata:
9
9
 
10
10
  # Implement a Spec
11
11
 
12
- Deliver the supplied spec and all associated tickets in the existing checkout. Require configured tracker guidance; if absent, request `/setup-project`.
12
+ Deliver the supplied spec and all associated tickets in the existing checkout. Require configured tracker guidance; if absent, request `/spindle-project-setup`.
13
13
 
14
14
  ## Required Skills
15
15
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: to-spec
2
+ name: sdd-to-spec
3
3
  description: "Turn an agreed feature request or approved design into a spec in its SDD feature workspace, then publish it to the configured ticket tracker."
4
4
  argument-hint: "[design file path]"
5
5
  disable-model-invocation: true
@@ -30,7 +30,7 @@ An optional design file path may be supplied. Read the entire file before drafti
30
30
 
31
31
  2. **Select or create the SDD feature workspace.**
32
32
  - Reuse the existing numbered `.scratch/<feature-id>-<slug>/` workspace when the selected design or spec belongs to one. Use its `spec.md`; do not create a second workspace for the same feature.
33
- - Otherwise, derive a concise feature title from the agreed request and choose an author identity for this run. Run `spindle sdd create --title "<feature title>" --author "<author>"`; add `--with-design` when the selected design must be retained in the new workspace. If using a design outside `.scratch`, copy that approved source into the newly created `design.md`, replacing only the fresh template. Save the returned `spec` path and continue in that workspace.
33
+ - Otherwise, derive a concise feature title from the agreed request and choose an author identity for this run. Run `{{ computed.spindle_package_manager }} spindle sdd create --title "<feature title>" --author "<author>"`; add `--with-design` when the selected design must be retained in the new workspace. If using a design outside `.scratch`, copy that approved source into the newly created `design.md`, replacing only the fresh template. Save the returned `spec` path and continue in that workspace.
34
34
  - When the workspace has `design.md`, read it fully and use it as the requirements source. If it still contains `[PLACEHOLDER]` markers or has not been approved, pause and ask the user to finish or approve the design before drafting the spec.
35
35
  - The selected SDD workspace's pre-created `spec.md` is the output. Replace its template content with the completed spec; do not create a dated folder or a second spec.
36
36
 
@@ -48,6 +48,6 @@ An optional design file path may be supplied. Read the entire file before drafti
48
48
  - Give each important user story a concise, observable acceptance outcome or example scenario focused on user-visible behavior. Leave implementation-slice completion criteria to tickets; no fixed scenario format is required.
49
49
  - Use domain glossary terms. Do not include file paths or code snippets unless a prototype captures a decision more clearly than prose.
50
50
  - Publish the completed spec according to the configured ticket-tracker guidance. Ask before publishing if the destination or status is unclear.
51
- - Once the spec is published, ask the user to invoke `/to-tickets` for planning. Do not invoke it automatically or start implementation here.
51
+ - Once the spec is published, ask the user to invoke `/sdd-to-tickets` for planning. Do not invoke it automatically or start implementation here.
52
52
 
53
53
  **Complete when:** the spec is published at the feature workspace's `spec.md` path, tracker publication is resolved, and no blocking ambiguity is hidden.
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: to-tickets
2
+ name: sdd-to-tickets
3
3
  description: "Break an approved feature spec into tracer-bullet tickets with explicit blockers, then publish them to the configured ticket tracker."
4
4
  argument-hint: "[spec path or tracker reference]"
5
5
  disable-model-invocation: true
@@ -20,7 +20,7 @@ An optional argument is one spec path or tracker reference. Read the full spec a
20
20
  - Use the spec's existing feature workspace. In Spindle's local tracker, this is a numbered `.scratch/<feature-id>-<slug>/` directory containing `spec.md`, `sdd.json`, and `tickets/`. Never create a dated folder or a second workspace for the same feature.
21
21
  - If `design.md` exists in the feature workspace, read it and confirm it was completed and approved before the spec. If the spec is not approved or a present design is unfinished, stop and resolve that before ticket planning.
22
22
  - A design is optional. Do not require or create one when the approved spec was intentionally written without a design.
23
- - For local SDD tracker work, use the feature's `spec.md` path with `spindle sdd` ticket commands. Otherwise follow the configured tracker's documented conventions.
23
+ - For local SDD tracker work, use the feature's `spec.md` path with `{{ computed.spindle_package_manager }} spindle sdd` ticket commands. Otherwise follow the configured tracker's documented conventions.
24
24
 
25
25
  **Complete when:** one approved spec and its tracker destination are identified; any existing design precedes it and is approved.
26
26
 
@@ -41,7 +41,7 @@ An optional argument is one spec path or tracker reference. Read the full spec a
41
41
  **Complete when:** the user approves the ticket breakdown and blocker relationships.
42
42
 
43
43
  5. **Publish the tickets.**
44
- - For a local Spindle SDD tracker, create each ticket in dependency order with `spindle sdd ticket create --spec <absolute-spec-path>`. Supply its title, reporter, category (`bug` or `enhancement`), and author. Fill the generated ticket's Requirements and Acceptance Criteria using the tracker guidance. After creation, use `spindle sdd ticket update --spec <absolute-spec-path> --ticket <id> --blocked-by <blocker-ids...> --author <author>` to record each dependency; omit `--blocked-by` when there are no blockers. Keep the approved spec and feature ledger authoritative.
44
+ - For a local Spindle SDD tracker, create each ticket in dependency order with `{{ computed.spindle_package_manager }} spindle sdd ticket create --spec <absolute-spec-path>`. Supply its title, reporter, category (`bug` or `enhancement`), and author. Fill the generated ticket's Requirements and Acceptance Criteria using the tracker guidance. After creation, use `{{ computed.spindle_package_manager }} spindle sdd ticket update --spec <absolute-spec-path> --ticket <id> --blocked-by <blocker-ids...> --author <author>` to record each dependency; omit `--blocked-by` when there are no blockers. Keep the approved spec and feature ledger authoritative.
45
45
  - For an external ticket tracker, create one ticket per approved ticket in dependency order and use native blocking relationships when available; otherwise record blockers in each ticket. If the source was an existing ticket, create sub-tickets as required by the tracker.
46
46
  - Apply a status only when the project documents it or the user requests it. Do not close or modify a parent ticket.
47
47
  - Publishing ends here. Tell the user that delivery begins through the project's documented implementation workflow; do not start implementation.