Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ If a design choice can stay agent-agnostic **without losing functionality or pre
- Agent contract: every agent-facing command takes options derived from `JsonCommandOptions` (`--json`). In JSON mode it writes one `CliJsonEnvelope` (`ok`, `data`, `warnings`, `error`) through `ICliJsonWriter` to stdout and nothing else; on an expected failure it writes a failure envelope and throws `CliCommandAbortException` with `PrintMessage = false`. Commands get `ShadeKitCommandContext` (`Output/`) injected: `RunAsync` runs the core call (optionally with the kit store) with the run's cancellation token and turns mapped failures into the failure envelope / `CliCommandAbortException`, and `WriteSuccess` writes the single success envelope or the plain warnings and lines; commands do not use `ICliJsonWriter`, `CliErrorMapper`, or `CliCommandAbort` directly (only `version`, which cannot fail, writes its envelope through `ICliJsonWriter`). Core exceptions are mapped to exit code, error code, and message only in `CliErrorMapper`. Exit codes are grouped by area (10–19 store, 20–29 format, 30–39 features, 40–49 phases and gates, 255 host); 2 (invalid arguments, usage) and 130 (canceled) are outside the ranges; every exit code has a kebab-case error code in `ShadeKitCliErrorCodes`; codes that only appear as warnings are in `ShadeKitCliWarningCodes`.
- Versioned JSON files in `.shadekit/` are read and written only through `IKitStore.ReadJsonAsync`/`WriteJsonAsync`. Each file type has a format id in `FormatIds` and a definition in `FormatRegistry.CreateDefault()`; raising a format version means a new table entry plus an `IFormatMigration` step and a test that migrates a fixture from the previous version. Document models derive from `VersionedDocument`; nested classes need their own `[JsonExtensionData] Dictionary<string, JsonElement>` to keep unknown fields.
- Text file content is held as `TextFileContent` (lines, UTF-8 BOM flag, final-line-ending flag), never as `byte[]`, and is written only through `ITextFileWriter` (`IKitStore.WriteFileAsync` uses it for `.shadekit/`). The writer is the only place that encodes to bytes; it resolves the line ending with `ILineEndingResolver`: git (attributes as git's `convert_attrs`: resolved `text`, legacy `crlf` only while `text` is unspecified; `-text` → no decision, `eol` attribute, `text=input`/`crlf=input` → LF, `core.autocrlf`, `core.eol` for text files; config values and a failing `git` cached per repository root for the resolver's lifetime, i.e. one command run; a `git` that is installed but fails gives a `GitAttributesUnavailableWarning`, a missing one (`GitNotFoundException`) none; `ILineEndingResolver.PrepareAsync` / `IKitStore.PrepareLineEndingsAsync` read the attributes of several files with one `git check-attr`, as `init` and `feature new` do) → `.editorconfig` `end_of_line` (parsed files kept per resolver while their write time, size, and Unix mode are unchanged) → operating system. Paths reach the writer and the resolvers as `RepositoryFile`, checked once where it is created (`RepositoryFile.Resolve`, or `KitStore` for `.shadekit/`). Lines are split at CRLF, LF, and a lone CR. Files, the versioned JSON files included, are read as strict UTF-8; an unreadable `.editorconfig` falls back with an `EditorConfigUnreadableWarning`, one the parser cannot evaluate (e.g. an overflowing numeric-range glob, or a glob that expands into more than 10 000 patterns, checked before the parser expands it) with an `EditorConfigInvalidWarning`.
- External tools (`git`, and `gh` for tracker issues in `feature new`) are started only through the helpers in `src/CreativeCoders.ShadeKit.Core/Processes/` (`PathExecutableSearch`: absolute `PATH` entries only, behind the caching `PathExecutableLocator` base of the tool locators; `ExternalProcessRunner`: absolute path, no shell, process tree killed on cancellation; `ExternalToolRunner`: locate, refuse a missing or relative path, start, map a start failure to the tool's `ExternalToolUnavailableException`), behind one runner interface per tool (`IGitCommandRunner`, `IGhCommandRunner`, both returning `ExternalProcessResult`). HTTP goes through `IHttpClientFactory` with the named client `HttpUrlContentReader.HttpClientName` (`Microsoft.Extensions.Http`), not a typed client, because the services that use it are singletons. Tests never start `git` or `gh` and never send a request: they fake the runner or use `tests/Shared/StubHttpMessageHandler.cs`.
- Core services are registered with `services.AddShadeKitCore()`.

```sh
Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<PackageVersion Include="FakeItEasy" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.13" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.13" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.13" />
<PackageVersion Include="Microsoft.Extensions.TimeProvider.Testing" Version="10.10.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.11.0" />
<PackageVersion Include="Microsoft.Testing.Extensions.CodeCoverage" Version="18.12.0" />
Expand Down
15 changes: 8 additions & 7 deletions docs/feature-packages/02-repository-initialisation.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

| | |
|---|---|
| Status | Draft, 2026-10-14, updated 2026-10-17 (template conventions), 2026-10-18 (implementation state; code review rework: exit codes 14 and 24, `.gitignore` rule, separator line), 2026-10-22 (code review verification of package 03: `trackers` id prefixes validated), 2026-10-23 (rework of file content handling: line endings of `.gitignore`, `CLAUDE.md`, and `AGENTS.md`, strict UTF-8, warnings; code review: `editorconfig-invalid`, legacy `crlf` attribute, `text=input`), and 2026-10-24 (documentation check: exit code 14 also for the `request.md` template, warnings sentence, resolved open point removed). Implemented except the final content of the templates owned by later packages (skeletons until then), the `gates.json` defaults (package 05), and a possible Copilot instruction file (package 08). |
| Status | Draft, 2026-10-14, updated 2026-10-17 (template conventions), 2026-10-18 (implementation state; code review rework: exit codes 14 and 24, `.gitignore` rule, separator line), 2026-10-22 (code review verification of package 03: `trackers` id prefixes validated), 2026-10-23 (rework of file content handling: line endings of `.gitignore`, `CLAUDE.md`, and `AGENTS.md`, strict UTF-8, warnings; code review: `editorconfig-invalid`, legacy `crlf` attribute, `text=input`), and 2026-10-24 (documentation check: exit code 14 also for the `request.md` template, warnings sentence, resolved open point removed), and 2026-10-25 (rework of `feature new` in package 03: the `request.md` template is removed, `init` no longer writes `.shadekit/templates/request.md`, exit code 14 no longer covers it; `trackers` hosts are read with `gh`). Implemented except the final content of the templates owned by later packages (skeletons until then), the `gates.json` defaults (package 05), and a possible Copilot instruction file (package 08). |
| Source | Concept §3.1 (`init`), §4.1, [§9](../../research/shade-kit-concept.md#9-version-drift) (§9.5) |
| Depends on | 01 |
| Enables | 03, 05, 18 and every phase package (they ship templates through this mechanism) |
Expand Down Expand Up @@ -78,7 +78,7 @@ If a write fails after the preconditions, for example because of an IO error, or
"files": [
{ "path": ".shadekit/config.json", "action": "created" },
{ "path": ".shadekit/gates.json", "action": "created" },
{ "path": ".shadekit/templates/request.md", "action": "created" },
{ "path": ".shadekit/templates/discussion.md", "action": "created" },
{ "path": ".shadekit/templates.lock.json", "action": "created" },
{ "path": ".shadekit/features/", "action": "created" },
{ "path": ".gitignore", "action": "appended" },
Expand All @@ -100,7 +100,7 @@ Exit codes owned by this package. **Implemented** (`ShadeKitCliExitCodes`, `Shad
|---|---|---|---|
| 12 | `not-a-git-repository` | The working directory is not inside a git repository. | None |
| 13 | `already-initialized` | `.shadekit` already exists at the repository root. The message points to `shade-kit upgrade`. | None |
| 14 | `path-not-a-file` | `.gitignore`, `CLAUDE.md`, or `AGENTS.md` at the repository root is a directory, or, in `feature new`, the template `.shadekit/templates/request.md` (package 03) (`PathNotAFileException`). The message names the path. **Decided** on 2026-10-18 (code review rework). | None |
| 14 | `path-not-a-file` | `.gitignore`, `CLAUDE.md`, or `AGENTS.md` at the repository root is a directory (`PathNotAFileException`). The message names the path. **Decided** on 2026-10-18 (code review rework). Until 2026-10-25 also a directory at the template `.shadekit/templates/request.md` in `feature new`; the template is removed (package 03, [`request.md`](03-feature-management.md#requestmd)). | None |
| 15 | `kit-root-not-a-directory` | `.shadekit` at the repository root is a file (`KitRootNotADirectoryException`). Also returned by every command that opens the store. The message names the path and points to `shade-kit init`. **Decided** on 2026-10-19 (code review rework). | None |
| 23 | `invalid-managed-section` | The section markers in `CLAUDE.md` or `AGENTS.md` are invalid (see [below](#managed-instruction-section)). The message names the file and the problem. | None |
| 24 | `unsupported-encoding` | `.gitignore`, `CLAUDE.md`, or `AGENTS.md` starts with a UTF-16 LE, UTF-16 BE, UTF-32 LE, or UTF-32 BE byte order mark, or contains a NUL byte, which UTF-16 and UTF-32 without byte order mark have for every ASCII character, or contains bytes that are not valid UTF-8 (`UnsupportedFileEncodingException`); UTF-8 with or without byte order mark is accepted. The message names the file and its encoding, `UTF-16 or UTF-32 without byte order mark` for a file with a NUL byte, `an unknown encoding with bytes that are not valid UTF-8` for any other file that is not valid UTF-8. **Decided** on 2026-10-18 (code review rework), the NUL byte check on 2026-10-19, the strict UTF-8 check on 2026-10-23. | None |
Expand All @@ -118,7 +118,7 @@ The team owns the values; the engine owns the schema. **Decided** (concept §9.1
| `kitVersion` | Kit version that initialised or last upgraded the repository. Informational only; format checks use `schemaVersion`. `init` writes the engine version. |
| `defaultProfile` | Profile used by `feature new` when none is given. Default `feature`. `feature new` stops with exit code 40 (`unknown-profile`) if it is not `feature`, `bugfix`, or `quick` (package 03). **Implemented** (`KitConfig.DefaultProfile`, `FeatureStore`). |
| `outdatedFormatOnRead` | What read commands do with a file in an older format (package 01): `migrate-and-warn` (default) or `stop`. |
| `trackers` | Mapping from issue URL host to id prefix for feature ids. Default: `github.com` → `gh`, which also applies when the field is missing; `{}` turns issue-based ids off, `null` makes the file invalid. **Decided** on 2026-10-19 (code review rework). Every id prefix must consist of lowercase letters a-z and digits joined by single hyphens (`^[a-z0-9]+(-[a-z0-9]+)*$`, the pattern of a feature id), for example `gh` or `jira-1`; otherwise the file is invalid (exit code 22, `invalid-file`), and the message names the host and the prefix. **Decided** on 2026-10-22 (code review verification of package 03, finding 2); before, a prefix such as `GH` or `""` only failed in `feature new` with a misleading `invalid-feature-id`. **Implemented** (`KitConfig.Trackers`); `feature new` uses it for issue-based ids (package 03, **Implemented**). |
| `trackers` | Mapping from issue URL host to id prefix for feature ids. Default: `github.com` → `gh`, which also applies when the field is missing; `{}` turns issue-based ids off, `null` makes the file invalid. **Decided** on 2026-10-19 (code review rework). Every id prefix must consist of lowercase letters a-z and digits joined by single hyphens (`^[a-z0-9]+(-[a-z0-9]+)*$`, the pattern of a feature id), for example `gh` or `jira-1`; otherwise the file is invalid (exit code 22, `invalid-file`), and the message names the host and the prefix. **Decided** on 2026-10-22 (code review verification of package 03, finding 2); before, a prefix such as `GH` or `""` only failed in `feature new` with a misleading `invalid-feature-id`. **Implemented** (`KitConfig.Trackers`); `feature new` uses it for issue-based ids and reads the issue of every URL whose host is in it with `gh` (package 03, **Implemented**). |
| `coverageThreshold` | Optional. Minimum coverage that `verify` requires (package 14). Not written by `init`; absent means "measure and record only". |
| `report` | Whether `finish` writes `report.md` (package 15): `ask` (default, `finish` asks the human), `always`, or `never`. Absent means `ask`. **Decided** on 2026-10-18 (findings clarification). |

Expand All @@ -140,15 +140,14 @@ The team owns the values; the engine owns the schema. **Decided** (concept §9.1

- Shipped templates are copied into `.shadekit/templates/` as plain files. Agents other than Claude Code need them as files in the repository (concept §9.5). **Decided.** **Implemented** (`RepositoryInitializer`).
- No engine code parses the structure of a template or of an artifact created from it (concept §9.5). **Decided.** Every check against artifact content is done by an agent.
- Initial set, owned by the listed packages. Until its owner package is built, a template is a skeleton with headings only; templates change freely until the first release, so this causes no lock-file churn for users. **Implemented:** `spec`, `plan`, `tasks`, and the two checklists come from the template interview (2026-10-17); `request`, `discussion`, `researches`, `review`, `verification`, `playbook`, `report`, and `diagnosis` are skeletons whose guidance comment names the owner package.
- Initial set, owned by the listed packages. Until its owner package is built, a template is a skeleton with headings only; templates change freely until the first release, so this causes no lock-file churn for users. **Implemented:** `spec`, `plan`, `tasks`, and the two checklists come from the template interview (2026-10-17); `discussion`, `researches`, `review`, `verification`, `playbook`, `report`, and `diagnosis` are skeletons whose guidance comment names the owner package.
- In the kit's source, every template is a file `<name>.template.md` in `src/CreativeCoders.ShadeKit.Core/Templates/Resources/`, so it cannot be mistaken for an artifact. The source folder has the same subfolders as `.shadekit/templates/` (the checklists are in `checks/`). The files are embedded into the Core assembly as resources named `templates/<subfolder>/<name>.template.md` (for example `templates/spec.template.md`, `templates/checks/definition-of-ready.template.md`), with `/` on every OS, so the `dotnet` tool and the single-file binaries carry them without extra files. **Decided** on 2026-10-18 (implementation clarification). **Implemented** (`EmbeddedResource` in `CreativeCoders.ShadeKit.Core.csproj`, `IShippedTemplateReader` / `ShippedTemplateReader`).
- `init` writes each template to its path in the repository, given in the table below. The engine keeps this mapping as an explicit list; it does not derive paths from file names. **Decided** on 2026-10-18 (implementation clarification). **Implemented** (`ShippedTemplate`, `ShippedTemplates.All`). Paths in `templates.lock.json` are repository paths.
- Every template is written as UTF-8 without byte order mark. The reader splits the source into lines at every line ending (CRLF, a lone CR, and the other line endings `string.ReplaceLineEndings` recognises), whatever line endings the checkout of the kit's source used. **Decided** on 2026-10-18 (implementation clarification). **Implemented** (`ShippedTemplateReader`, `RepositoryInitializer`).
- The templates and the JSON files of `.shadekit/` (`config.json`, `gates.json`, `templates.lock.json`) are written with the line ending that is decided for each file when it is written, by the same rules as for [`.gitignore`](#gitignore) (git attributes and configuration for the path `.shadekit/...`, then `.editorconfig`, then the operating system); the JSON files end with a line ending. **Decided** on 2026-10-23 (rework of file content handling), replacing "templates are always written with LF line endings" from 2026-10-18. **Implemented** (`IKitStore.WriteFileAsync` / `WriteJsonAsync` through `ITextFileWriter`, `RepositoryInitializer`, `InitializeAsync_KitStoreLineEnding_WritesTemplatesAndJsonFilesWithIt`).

| Template in the repository (`.shadekit/templates/`) | Source in the kit (`src/CreativeCoders.ShadeKit.Core/Templates/Resources/`) | Owner package |
|---|---|---|
| `request.md` | `request.template.md` | 09 |
| `discussion.md` | `discussion.template.md` | 09 |
| `spec.md` | `spec.template.md` | 10 |
| `researches.md` | `researches.template.md` | 06 (shared format), sections filled in 10 and 11 |
Expand All @@ -159,6 +158,8 @@ The team owns the values; the engine owns the schema. **Decided** (concept §9.1
| `diagnosis.md` | `diagnosis.template.md` | 16 |
| `checks/definition-of-ready.md`, `checks/definition-of-done.md` | `checks/definition-of-ready.template.md`, `checks/definition-of-done.template.md` | 05 |

There is no `request.md` template since 2026-10-25: `feature new` writes the request content it loads into a feature's `request.md` without a template (package 03, [`request.md`](03-feature-management.md#requestmd)), so `request.template.md` and its row are removed and `init` no longer writes `.shadekit/templates/request.md`. **Decided** on 2026-10-25 (rework of `feature new`). **Implemented** (`ShippedTemplates`, `All_Catalog_ContainsNoRequestTemplate`, `InitializeAsync_NewRepository_WritesNoRequestTemplate`). A `.shadekit/templates/request.md` written by an older version stays in place and is ignored; package 18 has the open point to remove it.

### Template conventions

Every template follows the same conventions. **Decided** in the template interview on 2026-10-17.
Expand Down Expand Up @@ -192,7 +193,7 @@ Every template follows the same conventions. **Decided** in the template intervi

- `path` is the repository path with `/` as separator. Entries are sorted ordinally by `path`.
- `kitVersion` is the engine version that wrote the template.
- `hash` is `sha256:` followed by the SHA-256, as 64 lowercase hex digits, of the template's lines joined with LF (UTF-8, with a final LF if the file ends with a line ending, and with the UTF-8 byte order mark if the file has one). The hash is independent of the line ending the template is written with or that Git checks it out with (for example CRLF on Windows with `core.autocrlf`), so an unchanged template matches its entry. **Decided** on 2026-10-23 (rework of file content handling), replacing "the exact bytes written, with CRLF counted as LF" from 2026-10-18; the result is the same, so existing lock entries stay valid. **Implemented** (`TemplatesLockEntry.ComputeHash(TextFileContent)`, `ComputeHash_ShippedTemplates_EqualsHashOfWrittenBytesWithCrLfCountedAsLf`, `InitializeAsync_KitStoreLineEndingCrLf_LockHashesEqualThoseOfLf`).
- `hash` is `sha256:` followed by the SHA-256, as 64 lowercase hex digits, of the template's lines joined with LF (UTF-8, with a final LF if the file ends with a line ending, and with the UTF-8 byte order mark if the file has one). The hash is independent of the line ending the template is written with or that Git checks it out with (for example CRLF on Windows with `core.autocrlf`), so an unchanged template matches its entry. **Decided** on 2026-10-23 (rework of file content handling), replacing "the exact bytes written, with CRLF counted as LF" from 2026-10-18; the result is the same, so existing lock entries stay valid. **Implemented** (`TemplatesLockEntry.ComputeHash(TextFileContent)`, `ComputeHash_ShippedTemplates_EqualsHashOfWrittenBytesWithLineEndingsCountedAsLf`, `InitializeAsync_KitStoreLineEndingCrLf_LockHashesEqualThoseOfLf`).
- Unknown fields are kept at the top level and in every entry (package 01).

### `.gitignore`
Expand Down
Loading
Loading