Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
740aff3
Order and match index names in the database's collation
ChrisJollyAU Oct 8, 2026
d426888
List BigBinary (0x11) in the appendix's column type table
ChrisJollyAU Oct 8, 2026
3308d53
Create attachment and multi-value columns in Core
ChrisJollyAU Oct 8, 2026
687a2ea
Write MSysComplexColumns and the complex-type templates as user TDEFs
ChrisJollyAU Oct 8, 2026
33b74a7
Tidy the format spec down to what the format is
ChrisJollyAU Oct 9, 2026
79ca377
Add tidy-format-spec skill; remove audit report
ChrisJollyAU Oct 10, 2026
6ec5831
Match ACE on stored conversions, index keys and seeks; fix the audit …
ChrisJollyAU Oct 12, 2026
750edf5
Retype a column to Yes/No as ACE does
ChrisJollyAU Oct 12, 2026
45290d9
Run the stored-text tests under the culture as it is
ChrisJollyAU Oct 12, 2026
d749d3d
Write rows, refuse read-only writes and raise the format as ACE does
ChrisJollyAU Oct 13, 2026
610bac6
Apply defaults, convert dates and report missing objects as ACE does
ChrisJollyAU Oct 13, 2026
9da8945
Match ACE byte for byte across a wider whole-file parity corpus
ChrisJollyAU Oct 13, 2026
863239b
Accept table constraints among the columns and hold CHECK constraints…
ChrisJollyAU Oct 14, 2026
92f59e7
Move to .NET 11 RC2 and read the OLE epoch back as default through Li…
ChrisJollyAU Oct 14, 2026
d4777b7
Take a DEFAULT that starts with = and store a default as written
ChrisJollyAU Oct 14, 2026
d6340d5
Manage package versions centrally and record which engines enforce a …
ChrisJollyAU Oct 15, 2026
da2372c
Store, read back and run UNION queries as ACE does
ChrisJollyAU Oct 15, 2026
08f1a0d
Share an index's data block with an identical one, as ACE does
ChrisJollyAU Oct 15, 2026
2b5e265
Allocate pages through ACE's shared reservations and owner groups, an…
ChrisJollyAU Oct 15, 2026
db3d320
Re-lay ALTER COLUMN rows through one persistent buffer, as ACE does
ChrisJollyAU Oct 16, 2026
a0445fb
Compile System.Threading into EFCore.Jet.Data only where it is used
ChrisJollyAU Oct 16, 2026
e10c60b
Read a decimal-point literal as ACE does, and work numbers out ACE's …
ChrisJollyAU Oct 16, 2026
c3570a8
Give a Decimal result ACE's precision, and build a make-table and an …
ChrisJollyAU Oct 16, 2026
55506f9
Record the ACE equality-lookup defect LibRed does not reproduce
ChrisJollyAU Oct 16, 2026
15b139c
Never hand out a page a reservation or owner group holds past the end…
ChrisJollyAU Oct 16, 2026
9200e1a
Never hand out a held page in a reference-form global map either
ChrisJollyAU Oct 16, 2026
15333f4
Check that no page belongs to two owners, and slide a long-value free…
ChrisJollyAU Oct 16, 2026
41a4dfe
Expect a replaced memo chain's pages back at once, as ACE measures, n…
ChrisJollyAU Oct 16, 2026
0646c50
Let the insert parity probe report a page past the original end, and …
ChrisJollyAU Oct 16, 2026
66d53c9
Never decorrelate a subquery body that has an OFFSET
ChrisJollyAU Oct 16, 2026
cb99a0f
test baseline
ChrisJollyAU Oct 16, 2026
2d3f660
Run IN externaldatabase and TRANSFORM, and select and name columns as…
ChrisJollyAU Oct 17, 2026
87ce5a5
Append to another file with INSERT INTO … IN, and read back the store…
ChrisJollyAU Oct 17, 2026
8f432a1
Compare with a subquery's rows: x op ANY | SOME | ALL (subquery)
ChrisJollyAU Oct 17, 2026
bb33e6c
Break a contraction where a lowercase letter meets an uppercase one
ChrisJollyAU Oct 17, 2026
1725770
Read stored queries through an INSERT, UPDATE or DELETE as a SELECT does
ChrisJollyAU Oct 17, 2026
5bd6686
Tidy the format spec down to what the format is
ChrisJollyAU Oct 17, 2026
1872aff
Name a parent's incoming relationship blocks past fifteen as ACE does
ChrisJollyAU Oct 17, 2026
ba8b1da
Measure an index leaf's shared prefix over the row pointers too
ChrisJollyAU Oct 17, 2026
01b3810
Keep a long-value page in the free map by the room ACE counts
ChrisJollyAU Oct 17, 2026
6172bb6
Convert a free-pages map to a reference map where its window cannot s…
ChrisJollyAU Oct 17, 2026
98f3c78
Empty a complex-column table in one go on a DELETE with no WHERE
ChrisJollyAU Oct 17, 2026
24454b1
Track what the docs still call unverified, and pin what was settled
ChrisJollyAU Oct 17, 2026
b83b639
Give the AccessTests suites the times they take now
ChrisJollyAU Oct 17, 2026
6a00e50
Re-base an empty usage map on the first page it marks, as ACE does
ChrisJollyAU Oct 17, 2026
3044830
Keep a usage-map holder's 4-byte reserve when a table's map grows
ChrisJollyAU Oct 17, 2026
e753da2
Mark a new data page free only if its first row leaves room for another
ChrisJollyAU Oct 17, 2026
04aa875
Place a new global bitmap past held pages as ACE does - verified
ChrisJollyAU Oct 17, 2026
517c702
Place an index's pages across two groups as ACE does - verified
ChrisJollyAU Oct 17, 2026
70615e6
Convert an owned map given a page below its start, as ACE does - veri…
ChrisJollyAU Oct 17, 2026
47b58fd
List every named index over a shared data block - verified
ChrisJollyAU Oct 17, 2026
cfae345
Accept two output columns aliased alike
ChrisJollyAU Oct 17, 2026
dab551a
Settle five verification-backlog items against ACE - verified
ChrisJollyAU Oct 17, 2026
9cf7afe
Read a pass-through query's connect string and SQL, and find version …
ChrisJollyAU Oct 18, 2026
8ac2e25
Keep an emptied long-value page owned when its column has no free map…
ChrisJollyAU Oct 18, 2026
45d2275
Match ACE on the calculated and write-once column bits, rewrite the T…
ChrisJollyAU Oct 18, 2026
8957721
Validate the minor version byte, refuse reads of an over-cap record, …
ChrisJollyAU Oct 18, 2026
9d8afc0
Free pages when ACE frees them, take ACE's phantom long-value page, a…
ChrisJollyAU Oct 18, 2026
068d162
Keep what ACE keeps through a rollback, place a reference map's bitma…
ChrisJollyAU Oct 19, 2026
cb81ca4
Grow a free-pages map as an owned map grows, build an index's pages i…
ChrisJollyAU Oct 19, 2026
bde568f
Keep an emptied page only where the free-pages map marks nothing belo…
ChrisJollyAU Oct 19, 2026
d753095
Write a new table's catalog row before its index roots - verified
ChrisJollyAU Oct 19, 2026
aca0618
Skip the BIGINT and DATETIME2 ACE cross-checks on an engine without t…
ChrisJollyAU Oct 19, 2026
ad6098f
Restore LibRed benchmark performance and retain DDL catalog metadata
ChrisJollyAU Oct 20, 2026
d24d7e0
Record full LibRed benchmark run for ad6098fb
ChrisJollyAU Oct 20, 2026
f8abb1a
Restore LibRed benchmark parity across query and write paths
ChrisJollyAU Oct 20, 2026
2219c80
Record full LibRed benchmark run for f8abb1a7
ChrisJollyAU Oct 20, 2026
444893a
Refuse ungrouped columns and multi-row scalar subqueries, match ACE o…
ChrisJollyAU Oct 21, 2026
6fddcc1
Read the definition page once per insert
ChrisJollyAU Oct 21, 2026
6391aaa
Tidy the format spec down to what the format is
ChrisJollyAU Oct 21, 2026
a49facf
Mask stored-query LvExtra outside parameter facets and skip backtick …
ChrisJollyAU Oct 22, 2026
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
21 changes: 20 additions & 1 deletion .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,26 @@
]
},
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "echo 'HOUSE RULES from CLAUDE.md, re-read on this message. They take priority over defaults: check every command, edit and test run against each of them before making it.'; echo; tr -d '\\r' < \"$CLAUDE_PROJECT_DIR/CLAUDE.md\" | awk 'BEGIN{RS=\"\";ORS=\"\\n\\n\"} /^## /{w=($0 ~ /^## Working in This Repo/)} w || /^\\*\\*(The user runs the suites|Only re-run|When you do run a suite)/ || /^> \\*\\*Rule/ || /^`src\\/Directory\\.Build\\.props` also turns on/ {print}'"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "in=$(cat); printf '%s' \"$in\" | grep -qE '\"file_path\"[[:space:]]*:[[:space:]]*\"[^\"]*[\\\\/]test[\\\\/]' || exit 0; echo '{\"decision\":\"block\",\"reason\":\"You just edited a test. CLAUDE.md: never change a test because a change makes it fail. If this edit changes what an existing test expects, you must already have measured what ACE produces for that exact query, on the schema and data of that test (not a similar expression, another table, or a function ACE does not have), and the test must need to change to match ACE exactly. Tell the user what was measured. If you have not, revert this edit now and fix the code instead. A new test pinning ACE-measured behaviour is fine.\"}'"
}
]
},
{
"matcher": "Write",
"hooks": [
Expand All @@ -41,7 +60,7 @@
"hooks": [
{
"type": "command",
"command": "in=$(cat); body=$(printf '%s' \"$in\" | sed 's/\"scratchpad_dir\":\"[^\"]*\"//g; s/\"transcript_path\":\"[^\"]*\"//g; s/\"cwd\":\"[^\"]*\"//g; s/\"description\":\"[^\"]*\"//g'); if printf '%s' \"$body\" | grep -qE '(^|[;&|(\")])[[:space:]]*(python[0-9.]*|pythonw|py)([[:space:]]|\")|sed[[:space:]]+-[a-zA-Z]*i|sed[[:space:]]+--in-place|>[[:space:]]*[^[:space:];&|]*\\.(cs|md|csproj|props|targets|sln|json|ps1|g4)|tee[[:space:]][^;&|]*\\.(cs|md|csproj|props|targets|sln|json|ps1|g4)'; then echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"Shell edits to source files (and Python) are disallowed: they bypass file checkpointing, so /rewind cannot undo them. Use Edit or Write instead. Writing logs to the scratchpad is fine.\"}}'; elif printf '%s' \"$body\" | grep -qE '(^|[;&|(\")])[[:space:]]*git[[:space:]]+(-[^[:space:]]+[[:space:]]+([^-[:space:]][^[:space:]]*[[:space:]]+)?){0,3}(grep|cat-file)([[:space:]]|\"|$)'; then echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"git grep and git cat-file read repository files through the shell - the same thing the read rule covers, just spelled to pass the git allow-list. Use Grep for content, Glob for filenames, Read for file contents. git diff, git log and git show stay allowed for reviewing history.\"}}'; elif printf '%s' \"$body\" | grep -qE '(^|[;&(\")]|\\\\\\\\n)[[:space:]]*(grep|egrep|fgrep|rg|cat|head|tail|ls|dir|find|fd|fdfind|Get-Content|gc|Get-ChildItem|gci|Select-String|sls)([[:space:]]|\")'; then if ! printf '%s' \"$body\" | grep -qE '(scratchpad|\\.log|\\$log|/tmp/)'; then echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"Reading files through the shell costs a permission prompt and bypasses the dedicated tools. Use Grep for content, Glob for filenames, Read for file contents. Shell text tools stay allowed on scratchpad logs (paths containing scratchpad, .log or $log).\"}}'; elif ! printf '%s' \"$body\" | grep -qE '([;&`]|[$]\\()'; then echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"allow\",\"permissionDecisionReason\":\"Pre-approved: a shell text tool reading a scratchpad/log path, as a single command with no chaining.\"}}'; fi; elif printf '%s' \"$body\" | grep -qE 'dotnet[[:space:]]+test' && printf '%s' \"$body\" | grep -qE 'LibRed\\.(Core|Engine)\\.AccessTests'; then if ! printf '%s' \"$body\" | grep -qE '(--filter|\"run_in_background\\\"?[[:space:]]*:[[:space:]]*true)'; then echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"LibRed.Core.AccessTests takes about 7 minutes and LibRed.Engine.AccessTests about 1.5. Run it with --filter for the tests that cover the change, or pass run_in_background true and keep working while it runs. A whole suite in the foreground blocks the session for minutes at a time.\"}}'; fi; fi; true"
"command": "in=$(cat); body=$(printf '%s' \"$in\" | sed 's/\"scratchpad_dir\":\"[^\"]*\"//g; s/\"transcript_path\":\"[^\"]*\"//g; s/\"cwd\":\"[^\"]*\"//g; s/\"description\":\"[^\"]*\"//g'); if printf '%s' \"$body\" | grep -qE '(^|[;&|(\")])[[:space:]]*(python[0-9.]*|pythonw|py)([[:space:]]|\")|sed[[:space:]]+-[a-zA-Z]*i|sed[[:space:]]+--in-place|>[[:space:]]*[^[:space:];&|]*\\.(cs|md|csproj|props|targets|sln|json|ps1|g4)|tee[[:space:]][^;&|]*\\.(cs|md|csproj|props|targets|sln|json|ps1|g4)'; then echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"Shell edits to source files (and Python) are disallowed: they bypass file checkpointing, so /rewind cannot undo them. Use Edit or Write instead. Writing logs to the scratchpad is fine.\"}}'; elif printf '%s' \"$body\" | grep -qE '(^|[;&|(\")])[[:space:]]*git[[:space:]]+(-[^[:space:]]+[[:space:]]+([^-[:space:]][^[:space:]]*[[:space:]]+)?){0,3}(grep|cat-file)([[:space:]]|\"|$)'; then echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"git grep and git cat-file read repository files through the shell - the same thing the read rule covers, just spelled to pass the git allow-list. Use Grep for content, Glob for filenames, Read for file contents. git diff, git log and git show stay allowed for reviewing history.\"}}'; elif printf '%s' \"$body\" | grep -qE '(^|[;&(\")]|\\\\\\\\n)[[:space:]]*(grep|egrep|fgrep|rg|cat|head|tail|ls|dir|find|fd|fdfind|Get-Content|gc|Get-ChildItem|gci|Select-String|sls)([[:space:]]|\")'; then if ! printf '%s' \"$body\" | grep -qE '(scratchpad|\\.log|\\$log|/tmp/)'; then echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"Reading files through the shell costs a permission prompt and bypasses the dedicated tools. Use Grep for content, Glob for filenames, Read for file contents. Shell text tools stay allowed on scratchpad logs (paths containing scratchpad, .log or $log).\"}}'; elif ! printf '%s' \"$body\" | grep -qE '([;&`]|[$]\\()'; then echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"allow\",\"permissionDecisionReason\":\"Pre-approved: a shell text tool reading a scratchpad/log path, as a single command with no chaining.\"}}'; fi; elif printf '%s' \"$body\" | grep -qE 'dotnet[[:space:]]+test' && printf '%s' \"$body\" | grep -qE 'LibRed\\.(Core|Engine)\\.AccessTests'; then if ! printf '%s' \"$body\" | grep -qE '(--filter|\"run_in_background\\\"?[[:space:]]*:[[:space:]]*true)'; then echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"LibRed.Core.AccessTests takes about 12 minutes and LibRed.Engine.AccessTests about 4.5. Run it with --filter for the tests that cover the change, or pass run_in_background true and keep working while it runs. A whole suite in the foreground blocks the session for minutes at a time.\"}}'; fi; fi; true"
}
]
}
Expand Down
70 changes: 70 additions & 0 deletions .claude/skills/tidy-format-spec/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
name: tidy-format-spec
description: Tidy the LibRed on-disk format spec (src/LibRed/docs/format/*.md) down to what the format IS, stripping the record of how facts were found while keeping status markers. Use when asked to tidy/clean up the format docs, remove research-record cruft, or run the periodic format-spec cleanup.
---

# Tidy the format spec down to what the format is

The files under `src/LibRed/docs/format/` are LibRed's reference for the on-disk Jet 4 / ACE
format. Over time they re-accumulate the *record of how each fact was found* mixed into the facts
themselves. This skill is the periodic pass that strips that back out.

## The governing rule

**The docs state what the format IS — not how it was verified, or what it was verified against.**

### REMOVE (research record — how/against-what a fact was found)
- Fixture / sample file names: `Northwind.accdb`, `WideTable.accdb`, `complex1.accdb`, `[Order Details]`, a "generated 200-column table", "~150 MB file", etc.
- Corpus counts and sample sizes: "in 754 of 1,676 tables", "33 carry 0, 5 carry 1", "17 of 17 extractable", "no order departs in more than 47 of 193 sample values".
- Methodology: "verified by diffing before/after a `CREATE TABLE`", "pages touched", "the tests check it after every insert", "read back what LibRed produced", whole-file-diff descriptions.
- Test / probe names: `ComplexDuplicateValueProbeTest`, "see the probe below", "What the probes settled".
- Dated sweeps: "as of 2026-..", "a sweep in ..".
- Historical / self-referential corrections: "was a live bug", "an earlier revision said", "previously thought", "used to say". REMOVE these **unless** a reader would naturally reach the wrong reading on their own — then restate it as a positive RULE, not as history (e.g. the "fold between bytes" note in page-03-04).

### KEEP (do not strip)
- **Verified / inferred / unverified / assumed / "measured" / "not reconciled with ACE" status markers.** Bare `(verified)` and `inferred` tags STAY — a fact's reliability is part of what is known about the format. This is the #1 thing people wrongly strip. The prior passes keep lines like "Measured both ways." verbatim.
- Every offset, size, structure, and mechanic.
- Code symbol names that describe how a format rule is enforced (`FindPageWithRoom`, `TryAppend`, `RowCodec`, `LongValueStore`, …).
- The brief Provenance skeleton in README (mdbtools consulted upstream; write-side checked against Access via OLE DB) — in its trimmed form; don't expand or delete it.
- A note that the spec *disagrees* with mdbtools on a point (that's a format statement), but not "we corroborated against mdbtools".

**When unsure whether a phrase is a format fact or a research record, KEEP it**, and surface it to the user as a judgment call rather than deleting unilaterally. Large illustrative tables (e.g. the collation departure table, the recomputation step table) are the user's call — ask; do not gut them.

## Worked examples (study these first)

Run `git show <hash> -- src/LibRed/docs/format/` on each — the commit message defines the task and the diff shows the exact standard:
- `56951944` — the original pass ("Tidy the format spec down to what the format is").
- `33b74a7b` — the second pass; also resolved two contradictions (chain stamp, `colCount`).

## Process

1. **Calibrate.** Read the two worked-example commits above (message + diff).
2. **Scope the drift.** Most new violations are material added since the last tidy commit:
`git diff 33b74a7b --stat -- src/LibRed/docs/format/` (use the latest tidy commit). The heaviest files are usually `system-catalog.md`, `page-03-04-index-btree.md`, `page-00-database.md`, `page-05-usage-maps.md`.
3. **Edit.** Apply the rule above. A useful locator grep for candidates:
`(?i)\.accdb|northwind|in \d+ of \d+|corpus|pages touched|diffing|was a (live )?bug|earlier revision|a sweep|as of 20|ProbeTest|read.?back`
Then read each hit in context — many `.accdb`/`.mdb` mentions are legitimate file-format references, and many `measured`/`verified` hits are status markers to keep.
4. **Resolve contradictions against the code, not by guessing.** The passes also fix contradictions they surface (e.g. the same field described two ways in two files). The source of truth is `src/LibRed/LibRed.Core/` — read the actual read/write code (`RowCodec`, `LongValueStore`, `TableDefinition`, the `Formats/` offset constants) and make the docs match it. The README `appendix-structures.md` is the most likely to carry a stale restatement.
5. **Keep the spec-sync rule in mind** (CLAUDE.md): any claim must be verifiable against real files or ACE; this pass does not invent facts, only removes the research record around them.

## How to run it efficiently

- This is a large pass (10+ files). Split the files into groups and delegate each group to a
parallel subagent (Agent tool), giving each the rule above and the worked-example commit hashes
to study. Keep the findings/edits; review each file's `git diff` yourself afterwards.
- **Do the shell-heavy / judgment-heavy files yourself** rather than via an agent. Subagents
running `git show`/`git diff` can trigger permission popups for the user; the main session's git
reads are pre-allowed. Use `Read`/`Grep`/`Edit`, not shell text tools (the repo hooks deny
`cat`/`grep`/`git grep` through the shell).
- The functional/doc change needs no build or test run — it's docs only.

## Suggested file groups (by drift weight)
- README.md + appendix-structures.md
- page-00 / page-01 / page-02a / page-02b
- page-02c / page-02d / page-02e / page-03-04 / page-05 / page-08 / page-09
- long-values / data-types / system-catalog (heaviest)

## Finishing
- Review the full `git diff -- src/LibRed/docs/format/`; confirm no offsets/sizes/facts changed, only the research-record wrapper.
- Flag borderline large tables and any contradictions to the user before committing.
- Commit with a message in the style of `56951944` / `33b74a7b`, listing any contradictions resolved.
20 changes: 20 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"mcpServers": {
"microsoft-learn": {
"type": "http",
"url": "https://learn.microsoft.com/api/mcp"
},
"nuget": {
"type": "stdio",
"command": "dotnet",
"args": ["dnx", "NuGet.Mcp.Server", "--source", "https://api.nuget.org/v3/index.json", "--yes"]
},
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": {
"Authorization": "Bearer ${GITHUB_PAT}"
}
}
}
}
Loading
Loading