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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- `repokit-check.ps1` no longer fails START-HERE rows that declare planned-but-not-yet-created
paths (#31, ADR-0010): a backticked token suffixed `(planned)` skips the existence assertion
— the standard prescribes not pre-creating empty directories — while the marker covers only
the token it follows, so an unmarked broken path in the same row still fails. Documented in
the standard's *Variance declarations* (*Planned paths*) and covered by two smoke cases.

## [0.6.0] - 2026-08-13

### Added
Expand Down
32 changes: 32 additions & 0 deletions docs/adr/0010-planned-start-here-paths.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# ADR-0010: "(planned)" START-HERE paths — declare layout before it exists

- **Status:** accepted
- **Date:** 2026-08-13

## Context

Field report #31: `the-standard.md` prescribes not pre-creating empty directories ("directories
are created when first populated"), but `repokit-check.ps1` asserts that every path-like token
in the START-HERE map resolves. A row declaring a monorepo's planned component layout
(`firmware/`, `app/`) therefore failed the self-check — following one rule broke the other. The
observed workaround (rewording the row to avoid path-like tokens) hid useful, greppable path
names from the map.

## Decision

A per-token marker, not a per-row or label-allowlist rule: a backticked token immediately
followed by `(planned)` is skipped by the self-check's existence assertion. Chosen over the
issue's alternative (only asserting rows whose label is in the known required set) because that
would silently stop checking *every* documentation row — the existence check on declared paths
is most of the self-check's value, and the marker keeps it on by default with an explicit,
visible opt-out per token. The marker covers only the token it follows, so one planned entry
cannot mask an unrelated broken path in the same row. Documented in the standard's *Variance
declarations* (as *Planned paths*), covered by two smoke cases (pass without the directory;
unmarked broken path beside a planned one still fails).

## Consequences

Planned layout is declarable in the map with real path tokens, and the check stays honest: an
existing-path claim is still verified everywhere the marker is absent. The marker is trust-based
— nothing forces its removal when the path becomes real; a stale `(planned)` on an existing path
merely skips a check that would pass, so the failure mode is benign.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
# Verifies the repo's *declared* structure actually exists (see the RepoKit standard,
# "Variance declarations"):
# 1. AGENTS.md (canonical agent file) exists; CLAUDE.md (loader shim) exists and imports it.
# 2. Every path named in the START-HERE map resolves to an existing file or directory.
# 2. Every path named in the START-HERE map resolves to an existing file or directory. A
# token suffixed "(planned)" declares a path the repo has not grown into yet and is
# skipped — the standard says not to pre-create empty directories.
# 3. A changelog exists at the default location or a declared one.
# 4. An ADR directory (or declared substitute) exists.
# 5. A resume-state row exists in the START-HERE map (docs/CHECKPOINT.md or a declared
Expand Down Expand Up @@ -71,14 +73,19 @@ if ($dataRows.Count -eq 0) {
} else {
Pass "START-HERE map found ($($dataRows.Count) rows)"
foreach ($row in $dataRows) {
$tokens = [regex]::Matches($row, '`([^`]+)`') | ForEach-Object { $_.Groups[1].Value }
foreach ($tok in $tokens) {
if ($tok.Contains('/') -and $tok -match '^[A-Za-z0-9_.-]+(/[A-Za-z0-9_.-]+)*/?$') {
if (Test-Path -LiteralPath (Join-Path $RepoRoot $tok.TrimEnd('/'))) {
Pass "START-HERE path resolves: $tok"
} else {
Fail "START-HERE path does not resolve: $tok"
}
foreach ($m in [regex]::Matches($row, '`([^`]+)`')) {
$tok = $m.Groups[1].Value
if (-not ($tok.Contains('/') -and $tok -match '^[A-Za-z0-9_.-]+(/[A-Za-z0-9_.-]+)*/?$')) { continue }
# "(planned)" right after the closing backtick marks a declared-but-not-yet-created
# path; it covers only the token it follows.
if ($row.Substring($m.Index + $m.Length) -match '^\s*\(planned\)') {
Pass "START-HERE path declared planned - not required to exist yet: $tok"
continue
}
if (Test-Path -LiteralPath (Join-Path $RepoRoot $tok.TrimEnd('/'))) {
Pass "START-HERE path resolves: $tok"
} else {
Fail "START-HERE path does not resolve: $tok"
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions plugins/repokit/skills/repo-standard/standard/the-standard.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@ it locally before a release, or add one CI line:
run: ./scripts/repokit-check.ps1
```

**Planned paths.** A map row may name layout the repo has not grown into yet (say, a monorepo's
`firmware/` and `app/` components) — declaring intent is useful, and the standard says *not* to
pre-create empty directories. Suffix the backticked token with `(planned)` —
`` `firmware/` (planned) `` — and the self-check skips that token's existence assertion. The
marker covers only the token it follows; remove it when the path becomes real.

**Adoption marker — retro-adopted repos.** A repo that adopts the standard mid-life snaps to the
conventions at the adoption commit; nothing before it should ever be re-flagged by an audit.
Declare the compliance horizon with one line in `AGENTS.md`, right under the START-HERE map:
Expand Down
18 changes: 17 additions & 1 deletion scripts/smoke_test_repokit_check.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
# FAILS on each drift case from the retrospective (refs #16): missing shim, non-importing shim,
# broken START-HERE path, missing changelog, missing ADR dir, missing resume-state row — plus
# the author-identity cases (refs #25/#26): handle + noreply passes, a real name or personal
# email fails, and a declared variance row switches the check off.
# email fails, and a declared variance row switches the check off — plus the "(planned)"
# marker cases (refs #31): a planned path needn't exist, and the marker covers only the token
# it follows.

$ErrorActionPreference = 'Stop'

Expand Down Expand Up @@ -113,6 +115,20 @@ Assert-Check 'declared author-identity variance passes' $d 0
Assert-Check 'web-flow identity passes' (New-GitFixture 'GitHub' 'noreply@github.com') 0
Assert-Check 'real name on the web-flow email fails' (New-GitFixture 'Octo Cat' 'noreply@github.com') 1

# --- "(planned)" marker cases (refs #31) -----------------------------------------------------

# 14. A row may declare planned-but-not-yet-created paths — the standard says not to pre-create
# empty directories, so the marker skips the existence assertion.
$d = New-Fixture
Add-Content (Join-Path $d 'AGENTS.md') '| Planned component layout | `firmware/` (planned), `app/` (planned) |'
Assert-Check 'planned paths pass without existing' $d 0

# 15. The marker covers only the token it follows — an unmarked broken path in the same row
# still fails.
$d = New-Fixture
Add-Content (Join-Path $d 'AGENTS.md') '| Mixed row | `missing/dir` and `future/dir` (planned) |'
Assert-Check 'unmarked broken path beside a planned one still fails' $d 1

if ($script:failed -gt 0) { Write-Host "smoke_test_repokit_check: $script:failed failure(s)"; exit 1 }
Write-Host 'smoke_test_repokit_check: all cases passed'
# Explicit success exit: the last negative case leaves $LASTEXITCODE = 1, and GitHub's pwsh
Expand Down