chore: adopt the project template and add the Kotlin/Compose skeleton
Period was a bare directory holding one 2,527-line specification, with no git
repository, no tracker and no documentation convention. This is the adoption
from Projects/Template/START-HERE-New-Project.md, plus a project that compiles
so the hooks and future guards have something real to run against.
Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to
docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the
capitalised Docs/ is gone. Every scaffolded document was filled in for Period.
docs/OPERATIONS.md deleted — an offline app is not a deployed service.
DOC_TRUST_MAP.md written last, describing what is actually here, including what
this project deliberately does not have.
Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm")
and cannot see the Android SDK, so the engine is testable without an emulator —
17 tests pass, 12 of them the acceptance cases from PRODUCT_PLAN.md §51.
BaselinePredictionEngine is a robust-median prototype and explicitly not the
product; it exists so Batch 02's replacement can be shown to be better rather
than merely different.
Versions verified against their official sources today rather than inherited
from the specification's own numbers, which that document asks for: Kotlin
2.4.10, AGP 9.3.1, Gradle 9.7.0, Compose BOM 2026.08.00, Room 2.8.4, Hilt
2.60.1. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no
longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk
stays 36, Play's floor from 2026-08-31, and the difference is deliberate.
Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md.
Three hooks in .githooks/, with pre-commit adapted to Gradle.
closes #1
closes #2
2026-08-18 02:16:47 -05:00
# Doc Trust Map — which document to believe
```
Status: Current
Owner: _null
Last reviewed: 2026-08-18
Governs: docs/**
Review trigger: Any doc added, deleted or moved; any change to which doc owns a subject
Fires on: added, deleted, moved
```
> Written last, and describing what is **actually here** rather than what the
> template said should be. Its whole value is being accurate about the others.
## Work items are not in this tree
The sequence of work, the open defects, and the things blocking a release all
live in this repository's **issue tracker** at
chore: rename the project to Privacy: Period Tracker
"Period" was always a working name — PRODUCT_PLAN.md §55 said so. The real
identity arrived with the brand guide and artwork, and this makes the project
call itself by it.
The repository is renamed in place on Forgejo (null/Period ->
null/Privacy-Period-Tracker), which keeps all 30 commits, all 27 issues, all 8
milestones and the four severity labels — verified by counting them on both
sides rather than assuming a rename is lossless. The git remote follows in the
same breath, so this commit's automatic push is what proves the new URL works.
WHAT DELIBERATELY DID NOT MOVE
"Period" is the product name AND the central domain word, and a mass rename
would have turned the data model into nonsense. PeriodRecord, PeriodWriteResult,
confirmPeriodStart, the period_records table and the button that says "Started
period" all describe a menstrual period rather than a product, and all 239
references to them are untouched.
So are the Kotlin package and applicationId. dev.privacyllc.period already reads
correctly under the new name — privacyllc is the company, period is the app —
and changing it would rewrite 68 files, rename the Room schema directory and
break the hardcoded path in schema-guard.sh for no gain. period.db stays for a
sharper reason: renaming a database file orphans the data on every device that
already has it.
Sixteen files changed, and the diff is small on purpose.
THE NAME HAS A SPACE AND THE ARTWORK DOES NOT
Canonical is "Privacy: Period Tracker". The supplied wordmark sets it without
one, so logo.webp and banner.webp now disagree with every document and with the
app itself. BRAND_GUIDE.md §10 is updated to the space form and says plainly
that the drawn mark has not caught up. Filed separately rather than papered
over, because closing that gap needs an artist and not a rename.
2026-08-18 15:54:34 -05:00
[dream.scheller.ltd/null/Privacy-Period-Tracker ](https://dream.scheller.ltd/null/Privacy-Period-Tracker ), not in
chore: adopt the project template and add the Kotlin/Compose skeleton
Period was a bare directory holding one 2,527-line specification, with no git
repository, no tracker and no documentation convention. This is the adoption
from Projects/Template/START-HERE-New-Project.md, plus a project that compiles
so the hooks and future guards have something real to run against.
Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to
docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the
capitalised Docs/ is gone. Every scaffolded document was filled in for Period.
docs/OPERATIONS.md deleted — an offline app is not a deployed service.
DOC_TRUST_MAP.md written last, describing what is actually here, including what
this project deliberately does not have.
Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm")
and cannot see the Android SDK, so the engine is testable without an emulator —
17 tests pass, 12 of them the acceptance cases from PRODUCT_PLAN.md §51.
BaselinePredictionEngine is a robust-median prototype and explicitly not the
product; it exists so Batch 02's replacement can be shown to be better rather
than merely different.
Versions verified against their official sources today rather than inherited
from the specification's own numbers, which that document asks for: Kotlin
2.4.10, AGP 9.3.1, Gradle 9.7.0, Compose BOM 2026.08.00, Room 2.8.4, Hilt
2.60.1. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no
longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk
stays 36, Play's floor from 2026-08-31, and the difference is deliberate.
Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md.
Three hooks in .githooks/, with pre-commit adapted to Gradle.
closes #1
closes #2
2026-08-18 02:16:47 -05:00
`docs/` . Milestones are batches — eight of them, `Batch 01 — Foundation` through
`Batch 08 — Polish` — issues are deliverables, and severity labels are `P0` ,
`P1` , `P2` and `release-blocker` .
This section exists to stop the next contributor starting a fresh markdown
to-do list. A list of things to do in two places is two records that will
disagree, and nothing will say which one is right.
| Question | Answer lives in |
| --- | --- |
| What are we building, and for whom? | [`planning/PROJECT_PLAN.md` ](planning/PROJECT_PLAN.md ) |
| What exactly does V1 do — prediction rules, screens, copy, compliance? | [`planning/PRODUCT_PLAN.md` ](planning/PRODUCT_PLAN.md ) |
| What is the sequence of work? | milestones in the tracker |
| What is left in this batch? | open issues under that milestone |
| What is broken right now? | issues labelled `P0` / `P1` / `P2` |
| What makes a release wrong? | issues labelled `release-blocker` |
| What is the next action? | the `nextAction` field on the project at privacyllc.dev — the newest entry in [`history/DEVELOPMENT_LOG.md` ](history/DEVELOPMENT_LOG.md ) says what it was *then* |
| What is blocking us? | the tracker, for the work; the blockers table at privacyllc.dev, for the stakeholder-facing version |
| What do I do when a piece of work is finished? | [`WORK_CYCLE.md` ](WORK_CYCLE.md ) |
| What happened, and when? | [`history/DEVELOPMENT_LOG.md` ](history/DEVELOPMENT_LOG.md ) |
| Why is it built this way, and what was rejected? | [`history/HISTORY.md` ](history/HISTORY.md ) |
| Did QA pass, and what does the tester think? | [`qa/ClaudeReport.md` ](qa/ClaudeReport.md ) |
| What did QA actually reach? | [`qa/ClaudeQACoverage.md` ](qa/ClaudeQACoverage.md ) |
| What is a QA round? | [`qa/ClaudeQAPlan.md` ](qa/ClaudeQAPlan.md ) |
| How is it built — modules, boundaries, data shapes, migrations? | [`architecture/README.md` ](architecture/README.md ) |
| What should it feel like, and what words does it use? | [`design/README.md` ](design/README.md ) |
docs: correct 57 claims the code and tracker disagreed with
Every document in the tree audited against the source, the tracker and git
history, each finding then given to a second reader tasked with refuting it.
74 raised, 12 refuted, 57 applied. No code changed.
THE README DESCRIBED A SKELETON
Its Status table — the one place a claim about what is built is allowed to live
— still read "there is no usable app yet", with Not built against Room, the four
core screens, fertility and notifications, and No round run against QA. Five
batches had shipped and three QA rounds had run.
TWO DOCUMENTS WERE SILENTLY NEVER FIRING
architecture/README.md and design/README.md wrote Governs: as prose ("the Gradle
module graph", "the design tokens in core/designsystem"). Neither contains a
path token, so doc-triggers.py reduced them to globs matching nothing, and one
real glob apiece made them look path-governing rather than subject-governing —
the state the script's own header calls invisible. Editing a Room entity never
fired the document owning the migration table. Both now fire, proved by running
the script.
SECURITY.md CLAIMED FOUR UNBUILT PROTECTIONS
App lock listed among what works offline; biometric/PIN gating described as
protecting app launch; the incognito launcher as existing; Play Billing in the
third parties table without the "not yet integrated" marker its neighbours
carry. All are Batch 06/07 work.
The advertising boundary was overstated in SECURITY.md and the README alike:
both said the ads module declares no dependency and a guard proves it. There is
no ads module. The pre-declared ":core:ads" to emptySet() rule is stricter than
the sentence it replaced and matches nothing until Batch 07, which is why the
guard is proved by injection rather than trusted.
SMALLER, EACH A REAL TRAP
WORK_CYCLE.md pointed at docs/architecture/scripts/forgejo-issue.py, a template
path absent here — missed by doc-claims.sh, which reads backticked prose and not
fenced blocks. ClaudeReport.md's Round notes said "No rounds yet" after three
rounds because ClaudeQAPlan.md's after-a-round list never named that section;
the playbook is fixed first. The instrumented-test count was eight in three
places and is four. HISTORY.md said the repository had no code and that nothing
had been tried and dropped, when three approaches had.
DELIBERATELY UNCHANGED
ClaudeReport.md's last verified build SHA stays at 0451fbe — no round has run
since, and moving it would claim a verification nobody performed. Every
DEVELOPMENT_LOG entry stays as written.
Guards: ./gradlew check, schema-guard.sh, doc-claims.sh (235 claimed paths, all
present), doc-triggers.py, and a link sweep over 21 markdown files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 16:33:14 -05:00
| What colour, logo or type do I use? | [`design/BRAND_GUIDE.md` ](design/BRAND_GUIDE.md ) — it outranks [`planning/PRODUCT_PLAN.md` ](planning/PRODUCT_PLAN.md ) §39 where they differ |
chore: adopt the project template and add the Kotlin/Compose skeleton
Period was a bare directory holding one 2,527-line specification, with no git
repository, no tracker and no documentation convention. This is the adoption
from Projects/Template/START-HERE-New-Project.md, plus a project that compiles
so the hooks and future guards have something real to run against.
Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to
docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the
capitalised Docs/ is gone. Every scaffolded document was filled in for Period.
docs/OPERATIONS.md deleted — an offline app is not a deployed service.
DOC_TRUST_MAP.md written last, describing what is actually here, including what
this project deliberately does not have.
Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm")
and cannot see the Android SDK, so the engine is testable without an emulator —
17 tests pass, 12 of them the acceptance cases from PRODUCT_PLAN.md §51.
BaselinePredictionEngine is a robust-median prototype and explicitly not the
product; it exists so Batch 02's replacement can be shown to be better rather
than merely different.
Versions verified against their official sources today rather than inherited
from the specification's own numbers, which that document asks for: Kotlin
2.4.10, AGP 9.3.1, Gradle 9.7.0, Compose BOM 2026.08.00, Room 2.8.4, Hilt
2.60.1. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no
longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk
stays 36, Play's floor from 2026-08-31, and the difference is deliberate.
Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md.
Three hooks in .githooks/, with pre-commit adapted to Gradle.
closes #1
closes #2
2026-08-18 02:16:47 -05:00
| What is protected, from whom, and what must never be logged? | [`security/SECURITY.md` ](security/SECURITY.md ) |
| What is checked before a Play release? | [`security/SECURITY_CHECKLIST.md` ](security/SECURITY_CHECKLIST.md ) |
| Which script do I run, and can it stop me? | [`TOOLS.md` ](TOOLS.md ) — the signpost; [`architecture/README.md` ](architecture/README.md ) has the table |
| What runs before a commit? | [`architecture/githooks/README.md` ](architecture/githooks/README.md ), and the hooks themselves in `.githooks/` |
| How do I write a check that will actually catch something? | [`architecture/GUARDS.md` ](architecture/GUARDS.md ) |
**Next action and blockers are recorded at the end of every piece of work, not
when somebody asks.** [`WORK_CYCLE.md` ](WORK_CYCLE.md ) holds that procedure and
the reason each step is in it.
## The two planning documents, and which owns what
Unusually, this project has two — worth stating plainly, because a reader who
does not know which is which will pick the wrong one.
| | Owns | Length |
| --- | --- | --- |
| [`planning/PRODUCT_PLAN.md` ](planning/PRODUCT_PLAN.md ) | the **V1 specification** : prediction requirements and their acceptance cases, the data model, every screen and its copy, notification modes, monetization, artwork, Play compliance | ~2,500 lines |
| [`planning/PROJECT_PLAN.md` ](planning/PROJECT_PLAN.md ) | the **argument** : what this is, who for, what it deliberately is not, the stack and the reason for each choice, what success looks like, known risks | ~110 lines |
`PRODUCT_PLAN.md` is the document this project started as — it existed before
the repository did. Everything else in `docs/` points **into** it rather than
copying out of it, and that is deliberate: a second copy of a fertility
disclaimer or a notification string is how two versions of a promise come to
exist. `architecture/` , `design/` and `qa/` each name the sections of it they
own.
Where the two disagree, `PRODUCT_PLAN.md` is the specification and
`PROJECT_PLAN.md` is the reasoning — resolve it rather than letting both stand.
## Folder layout
| Folder | Contents |
| --- | --- |
| `docs/planning/` | `PRODUCT_PLAN` — the V1 specification. `PROJECT_PLAN` — the vision. Neither is the schedule; that is the tracker. |
| `docs/qa/` | `ClaudeQAPlan` (playbook, passes A– H), `ClaudeQACoverage` (what each pass reached), `ClaudeReport` (the verdict) |
| `docs/architecture/` | modules, boundaries, data shapes, the migration table; `GUARDS.md` ; `githooks/README.md` |
docs: eleven app screenshots, from a seeded demo history
Privacy: Period Tracker is the only Privacy LLC product without a page on
privacyllc.dev — /work/bill-tracker, /work/closer-couples and /work/fruit-fall
exist, /work/privacy-period-tracker returns 404 — and until now no screenshot of
this app existed anywhere in the repository.
Eleven frames covering all seven onboarding steps plus Today, Calendar, Insights
and Settings, in the house style those pages use: portrait, captioned, no device
frames, no decorative background.
SEEDED, NOT REAL
Six invented starts — 8 Mar, 5 Apr, 3 May, 31 May, 28 Jun, 26 Jul 2026 — giving
gaps of 28/29/28/28/29 days. On a 19 August clock that is cycle day 25, and the
engine earns "period likely in 4 days, most likely 23 August, expected 22-24
August, confidence High". Nothing shown is real data, which #32 states is not a
nicety: a screenshot of a period tracker is a screenshot of health data.
Today was reshot once. The first seeding landed the capture day on the estimated
ovulation date, so the hero read "estimated ovulation in 0 days" — true, and a
poor lead. Shifting the history ten days gives the period countdown, which is the
screen the app is built around.
CHECKED, NOT ASSUMED
The status bar is SysUI demo mode so all eleven are identical, and the top strip
of every frame was cropped and compared in one image to confirm what #32 warns
about: no notification appears in any of them. The not-contraception line is
visible in the Today and Calendar frames. Light mode throughout, which also
avoids #44's wrong dark Welcome illustration.
520 KB for the set, 1080x2400 WebP, each far under the 512 KB ceiling.
The README carries the seeded dates, the demo-mode and text-input-picker recipe,
and the rules every frame must satisfy — so the next capture reproduces this one
rather than re-deriving it. Also corrects a stale `splash.png` in the trust map,
which became splash.webp when it was converted.
These are not the three marks privacyllc.dev renders for the project card; that
folder says plainly that screenshots do not belong in it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 01:47:52 -05:00
| `docs/design/` | `README.md` — tone, the four rules that settle arguments, and which specification sections own each surface. `BRAND_GUIDE.md` — the owner-supplied visual identity, which outranks `PRODUCT_PLAN.md` §39 on colour. `brand/` — the owner's source art, including the emblem the launcher icon is cut from. `dist/` — the onboarding illustration sources, and `splash.webp` , the lockup `docs/data/img/logo.webp` and `banner.webp` are generated from. `screenshots/` — captured app screenshots, for the product page on privacyllc.dev and the Play listing |
chore: adopt the project template and add the Kotlin/Compose skeleton
Period was a bare directory holding one 2,527-line specification, with no git
repository, no tracker and no documentation convention. This is the adoption
from Projects/Template/START-HERE-New-Project.md, plus a project that compiles
so the hooks and future guards have something real to run against.
Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to
docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the
capitalised Docs/ is gone. Every scaffolded document was filled in for Period.
docs/OPERATIONS.md deleted — an offline app is not a deployed service.
DOC_TRUST_MAP.md written last, describing what is actually here, including what
this project deliberately does not have.
Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm")
and cannot see the Android SDK, so the engine is testable without an emulator —
17 tests pass, 12 of them the acceptance cases from PRODUCT_PLAN.md §51.
BaselinePredictionEngine is a robust-median prototype and explicitly not the
product; it exists so Batch 02's replacement can be shown to be better rather
than merely different.
Versions verified against their official sources today rather than inherited
from the specification's own numbers, which that document asks for: Kotlin
2.4.10, AGP 9.3.1, Gradle 9.7.0, Compose BOM 2026.08.00, Room 2.8.4, Hilt
2.60.1. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no
longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk
stays 36, Play's floor from 2026-08-31, and the difference is deliberate.
Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md.
Three hooks in .githooks/, with pre-commit adapted to Gradle.
closes #1
closes #2
2026-08-18 02:16:47 -05:00
| `docs/security/` | `SECURITY` — threat model, the advertising boundary, logging rules. `SECURITY_CHECKLIST` — the pre-release list |
| `docs/history/` | `DEVELOPMENT_LOG` (dated, append-only), `HISTORY` (decisions and their reasons), `BATCH_LEDGER` (archived) |
docs: correct 57 claims the code and tracker disagreed with
Every document in the tree audited against the source, the tracker and git
history, each finding then given to a second reader tasked with refuting it.
74 raised, 12 refuted, 57 applied. No code changed.
THE README DESCRIBED A SKELETON
Its Status table — the one place a claim about what is built is allowed to live
— still read "there is no usable app yet", with Not built against Room, the four
core screens, fertility and notifications, and No round run against QA. Five
batches had shipped and three QA rounds had run.
TWO DOCUMENTS WERE SILENTLY NEVER FIRING
architecture/README.md and design/README.md wrote Governs: as prose ("the Gradle
module graph", "the design tokens in core/designsystem"). Neither contains a
path token, so doc-triggers.py reduced them to globs matching nothing, and one
real glob apiece made them look path-governing rather than subject-governing —
the state the script's own header calls invisible. Editing a Room entity never
fired the document owning the migration table. Both now fire, proved by running
the script.
SECURITY.md CLAIMED FOUR UNBUILT PROTECTIONS
App lock listed among what works offline; biometric/PIN gating described as
protecting app launch; the incognito launcher as existing; Play Billing in the
third parties table without the "not yet integrated" marker its neighbours
carry. All are Batch 06/07 work.
The advertising boundary was overstated in SECURITY.md and the README alike:
both said the ads module declares no dependency and a guard proves it. There is
no ads module. The pre-declared ":core:ads" to emptySet() rule is stricter than
the sentence it replaced and matches nothing until Batch 07, which is why the
guard is proved by injection rather than trusted.
SMALLER, EACH A REAL TRAP
WORK_CYCLE.md pointed at docs/architecture/scripts/forgejo-issue.py, a template
path absent here — missed by doc-claims.sh, which reads backticked prose and not
fenced blocks. ClaudeReport.md's Round notes said "No rounds yet" after three
rounds because ClaudeQAPlan.md's after-a-round list never named that section;
the playbook is fixed first. The instrumented-test count was eight in three
places and is four. HISTORY.md said the repository had no code and that nothing
had been tried and dropped, when three approaches had.
DELIBERATELY UNCHANGED
ClaudeReport.md's last verified build SHA stays at 0451fbe — no round has run
since, and moving it would claim a verification nobody performed. Every
DEVELOPMENT_LOG entry stays as written.
Guards: ./gradlew check, schema-guard.sh, doc-claims.sh (235 claimed paths, all
present), doc-triggers.py, and a link sweep over 21 markdown files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 16:33:14 -05:00
| `docs/data/` | `README.md` — the rule, and why this folder cannot be exempted. `img/README.md` — the three required names, their dimensions and the 512 KB ceiling. `img/icon.webp` , `img/logo.webp` , `img/banner.webp` — the marks privacyllc.dev renders |
chore: adopt the project template and add the Kotlin/Compose skeleton
Period was a bare directory holding one 2,527-line specification, with no git
repository, no tracker and no documentation convention. This is the adoption
from Projects/Template/START-HERE-New-Project.md, plus a project that compiles
so the hooks and future guards have something real to run against.
Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to
docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the
capitalised Docs/ is gone. Every scaffolded document was filled in for Period.
docs/OPERATIONS.md deleted — an offline app is not a deployed service.
DOC_TRUST_MAP.md written last, describing what is actually here, including what
this project deliberately does not have.
Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm")
and cannot see the Android SDK, so the engine is testable without an emulator —
17 tests pass, 12 of them the acceptance cases from PRODUCT_PLAN.md §51.
BaselinePredictionEngine is a robust-median prototype and explicitly not the
product; it exists so Batch 02's replacement can be shown to be better rather
than merely different.
Versions verified against their official sources today rather than inherited
from the specification's own numbers, which that document asks for: Kotlin
2.4.10, AGP 9.3.1, Gradle 9.7.0, Compose BOM 2026.08.00, Room 2.8.4, Hilt
2.60.1. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no
longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk
stays 36, Play's floor from 2026-08-31, and the difference is deliberate.
Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md.
Three hooks in .githooks/, with pre-commit adapted to Gradle.
closes #1
closes #2
2026-08-18 02:16:47 -05:00
| `docs/` root | this map; `WORK_CYCLE` ; `TOOLS` |
`README.md` stays at the repository root; it is the landing page and moving it
breaks that. Everything else lives under `docs/` .
## What this project deliberately does not have
Recorded here because an absence somebody chose and an absence nobody noticed
look identical from outside, and only one of them is fine.
2026-08-18 02:18:25 -05:00
- **docs/OPERATIONS.md — deleted.** Named without backticks, because
`doc-claims.sh` reads a backticked path as a claim the file is there and this
docs: correct 57 claims the code and tracker disagreed with
Every document in the tree audited against the source, the tracker and git
history, each finding then given to a second reader tasked with refuting it.
74 raised, 12 refuted, 57 applied. No code changed.
THE README DESCRIBED A SKELETON
Its Status table — the one place a claim about what is built is allowed to live
— still read "there is no usable app yet", with Not built against Room, the four
core screens, fertility and notifications, and No round run against QA. Five
batches had shipped and three QA rounds had run.
TWO DOCUMENTS WERE SILENTLY NEVER FIRING
architecture/README.md and design/README.md wrote Governs: as prose ("the Gradle
module graph", "the design tokens in core/designsystem"). Neither contains a
path token, so doc-triggers.py reduced them to globs matching nothing, and one
real glob apiece made them look path-governing rather than subject-governing —
the state the script's own header calls invisible. Editing a Room entity never
fired the document owning the migration table. Both now fire, proved by running
the script.
SECURITY.md CLAIMED FOUR UNBUILT PROTECTIONS
App lock listed among what works offline; biometric/PIN gating described as
protecting app launch; the incognito launcher as existing; Play Billing in the
third parties table without the "not yet integrated" marker its neighbours
carry. All are Batch 06/07 work.
The advertising boundary was overstated in SECURITY.md and the README alike:
both said the ads module declares no dependency and a guard proves it. There is
no ads module. The pre-declared ":core:ads" to emptySet() rule is stricter than
the sentence it replaced and matches nothing until Batch 07, which is why the
guard is proved by injection rather than trusted.
SMALLER, EACH A REAL TRAP
WORK_CYCLE.md pointed at docs/architecture/scripts/forgejo-issue.py, a template
path absent here — missed by doc-claims.sh, which reads backticked prose and not
fenced blocks. ClaudeReport.md's Round notes said "No rounds yet" after three
rounds because ClaudeQAPlan.md's after-a-round list never named that section;
the playbook is fixed first. The instrumented-test count was eight in three
places and is four. HISTORY.md said the repository had no code and that nothing
had been tried and dropped, when three approaches had.
DELIBERATELY UNCHANGED
ClaudeReport.md's last verified build SHA stays at 0451fbe — no round has run
since, and moving it would claim a verification nobody performed. Every
DEVELOPMENT_LOG entry stays as written.
Guards: ./gradlew check, schema-guard.sh, doc-claims.sh (235 claimed paths, all
present), doc-triggers.py, and a link sweep over 21 markdown files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 16:33:14 -05:00
one says the opposite. Privacy: Period Tracker is an offline-first Android app
chore: adopt the project template and add the Kotlin/Compose skeleton
Period was a bare directory holding one 2,527-line specification, with no git
repository, no tracker and no documentation convention. This is the adoption
from Projects/Template/START-HERE-New-Project.md, plus a project that compiles
so the hooks and future guards have something real to run against.
Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to
docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the
capitalised Docs/ is gone. Every scaffolded document was filled in for Period.
docs/OPERATIONS.md deleted — an offline app is not a deployed service.
DOC_TRUST_MAP.md written last, describing what is actually here, including what
this project deliberately does not have.
Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm")
and cannot see the Android SDK, so the engine is testable without an emulator —
17 tests pass, 12 of them the acceptance cases from PRODUCT_PLAN.md §51.
BaselinePredictionEngine is a robust-median prototype and explicitly not the
product; it exists so Batch 02's replacement can be shown to be better rather
than merely different.
Versions verified against their official sources today rather than inherited
from the specification's own numbers, which that document asks for: Kotlin
2.4.10, AGP 9.3.1, Gradle 9.7.0, Compose BOM 2026.08.00, Room 2.8.4, Hilt
2.60.1. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no
longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk
stays 36, Play's floor from 2026-08-31, and the difference is deliberate.
Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md.
Three hooks in .githooks/, with pre-commit adapted to Gradle.
closes #1
closes #2
2026-08-18 02:16:47 -05:00
distributed through Google Play. There is no host, no container, no uptime and
no restore path of ours. An empty runbook reads as one nobody wrote rather
than one that never applied.
2026-08-18 02:18:25 -05:00
- **docs/planning/FUTURE.md — never created**, and named bare for the same
reason. The Command Center's docs
chore: adopt the project template and add the Kotlin/Compose skeleton
Period was a bare directory holding one 2,527-line specification, with no git
repository, no tracker and no documentation convention. This is the adoption
from Projects/Template/START-HERE-New-Project.md, plus a project that compiles
so the hooks and future guards have something real to run against.
Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to
docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the
capitalised Docs/ is gone. Every scaffolded document was filled in for Period.
docs/OPERATIONS.md deleted — an offline app is not a deployed service.
DOC_TRUST_MAP.md written last, describing what is actually here, including what
this project deliberately does not have.
Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm")
and cannot see the Android SDK, so the engine is testable without an emulator —
17 tests pass, 12 of them the acceptance cases from PRODUCT_PLAN.md §51.
BaselinePredictionEngine is a robust-median prototype and explicitly not the
product; it exists so Batch 02's replacement can be shown to be better rather
than merely different.
Versions verified against their official sources today rather than inherited
from the specification's own numbers, which that document asks for: Kotlin
2.4.10, AGP 9.3.1, Gradle 9.7.0, Compose BOM 2026.08.00, Room 2.8.4, Hilt
2.60.1. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no
longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk
stays 36, Play's floor from 2026-08-31, and the difference is deliberate.
Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md.
Three hooks in .githooks/, with pre-commit adapted to Gradle.
closes #1
closes #2
2026-08-18 02:16:47 -05:00
report looks for a batch ledger there and reports it **missing** for every
tracker-first project. That is the expected state; creating the file to turn
the line green would rebuild the second record
[`history/BATCH_LEDGER.md` ](history/BATCH_LEDGER.md ) was archived for.
- **Most of the template's scripts.** Six were taken; the rest assume a deployed
Node or Postgres service. [`TOOLS.md` ](TOOLS.md ) names each one and why.
`doc-claims.sh` noting a document that mentions a script this project does not
have is expected, not a failure.
- **A master copy of the hooks under `docs/architecture/githooks/` .** The
docs: correct 57 claims the code and tracker disagreed with
Every document in the tree audited against the source, the tracker and git
history, each finding then given to a second reader tasked with refuting it.
74 raised, 12 refuted, 57 applied. No code changed.
THE README DESCRIBED A SKELETON
Its Status table — the one place a claim about what is built is allowed to live
— still read "there is no usable app yet", with Not built against Room, the four
core screens, fertility and notifications, and No round run against QA. Five
batches had shipped and three QA rounds had run.
TWO DOCUMENTS WERE SILENTLY NEVER FIRING
architecture/README.md and design/README.md wrote Governs: as prose ("the Gradle
module graph", "the design tokens in core/designsystem"). Neither contains a
path token, so doc-triggers.py reduced them to globs matching nothing, and one
real glob apiece made them look path-governing rather than subject-governing —
the state the script's own header calls invisible. Editing a Room entity never
fired the document owning the migration table. Both now fire, proved by running
the script.
SECURITY.md CLAIMED FOUR UNBUILT PROTECTIONS
App lock listed among what works offline; biometric/PIN gating described as
protecting app launch; the incognito launcher as existing; Play Billing in the
third parties table without the "not yet integrated" marker its neighbours
carry. All are Batch 06/07 work.
The advertising boundary was overstated in SECURITY.md and the README alike:
both said the ads module declares no dependency and a guard proves it. There is
no ads module. The pre-declared ":core:ads" to emptySet() rule is stricter than
the sentence it replaced and matches nothing until Batch 07, which is why the
guard is proved by injection rather than trusted.
SMALLER, EACH A REAL TRAP
WORK_CYCLE.md pointed at docs/architecture/scripts/forgejo-issue.py, a template
path absent here — missed by doc-claims.sh, which reads backticked prose and not
fenced blocks. ClaudeReport.md's Round notes said "No rounds yet" after three
rounds because ClaudeQAPlan.md's after-a-round list never named that section;
the playbook is fixed first. The instrumented-test count was eight in three
places and is four. HISTORY.md said the repository had no code and that nothing
had been tried and dropped, when three approaches had.
DELIBERATELY UNCHANGED
ClaudeReport.md's last verified build SHA stays at 0451fbe — no round has run
since, and moving it would claim a verification nobody performed. Every
DEVELOPMENT_LOG entry stays as written.
Guards: ./gradlew check, schema-guard.sh, doc-claims.sh (235 claimed paths, all
present), doc-triggers.py, and a link sweep over 21 markdown files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 16:33:14 -05:00
template keeps one and installs copies; this repository keeps only `.githooks/` ,
chore: adopt the project template and add the Kotlin/Compose skeleton
Period was a bare directory holding one 2,527-line specification, with no git
repository, no tracker and no documentation convention. This is the adoption
from Projects/Template/START-HERE-New-Project.md, plus a project that compiles
so the hooks and future guards have something real to run against.
Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to
docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the
capitalised Docs/ is gone. Every scaffolded document was filled in for Period.
docs/OPERATIONS.md deleted — an offline app is not a deployed service.
DOC_TRUST_MAP.md written last, describing what is actually here, including what
this project deliberately does not have.
Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm")
and cannot see the Android SDK, so the engine is testable without an emulator —
17 tests pass, 12 of them the acceptance cases from PRODUCT_PLAN.md §51.
BaselinePredictionEngine is a robust-median prototype and explicitly not the
product; it exists so Batch 02's replacement can be shown to be better rather
than merely different.
Versions verified against their official sources today rather than inherited
from the specification's own numbers, which that document asks for: Kotlin
2.4.10, AGP 9.3.1, Gradle 9.7.0, Compose BOM 2026.08.00, Room 2.8.4, Hilt
2.60.1. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no
longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk
stays 36, Play's floor from 2026-08-31, and the difference is deliberate.
Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md.
Three hooks in .githooks/, with pre-commit adapted to Gradle.
closes #1
closes #2
2026-08-18 02:16:47 -05:00
because `pre-commit` here is adapted for Gradle and a second copy would drift.
- **No `Exempt:` declarations.** Every document this convention asks for is
either present in git or listed above as deliberately deleted.
## Source-of-truth ladder
When two sources disagree, believe them in this order:
1. **Verified code behaviour** — read the source, run `./gradlew test`
2. **The issue tracker** — for anything about state of work: what is open, what
closed, when, and by which commit
3. **Specialist docs** — authoritative for their own subject only
4. **History and logs** — a record of *then* , not a description of *now*
A document that describes work as done is describing what was true when it was
written. The tracker is describing now. The `Status:` table in
[`../README.md` ](../README.md ) is the one place a claim about what is built is
allowed to live, and every row of it cites its evidence.
## The status header
Every document in this tree opens with one, immediately after its H1 — and so
do the documents at the repository root, which `scripts/doc-triggers.py` reads
alongside this tree.
```
Status: Current | Draft | Superseded | Archived
Owner: < who maintains this >
Last reviewed: < YYYY-MM-DD >
Governs: < paths or subject this document is authoritative for >
Review trigger: < the change that should send someone back to this file >
Fires on: < optional — added , deleted , moved , changed >
```
**Exactly those four status words.** A checker reads them, and a document whose
status falls outside the list is reported as having an unknown one rather than
being quietly accepted.
**Review trigger** is the line that matters. "Last reviewed" ages on its own and
a reader cannot tell a current document from an abandoned one by looking at it;
a trigger names the event that should bring somebody back. It is also checked: a
header carrying `Status` without `Review trigger` is reported as incomplete —
that combination looks finished and is not.
**Governs** is a comma-separated list, and an entry may explain itself after the
glob with a **spaced dash** — `docs/data/** — the assets privacyllc.dev renders` .
`scripts/doc-triggers.py` cuts the entry there and reads the globs from the left.
**Use that form and no other.** A gloss in parentheses, or after a colon, is not
recognised: the whole entry becomes the glob, matches nothing, and the document
is silently never fired — not reported as skipped either, because it still looks
like a path.
**Fires on** is optional and only for the case where `Governs:` is much broader
than the trigger. This file is the extreme of that gap: it governs `docs/**` ,
the broadest glob here, while its trigger is one of the narrowest. Omit the line
unless it is genuinely needed — absent means fire on every kind, which is what
almost every document wants.
## Declaring a document deliberately absent
A repository may decide it will not keep one of these documents in git. Say so
**here**, one line per path, anywhere in this file:
```
Exempt: < the path > — < why , in a few words >
```
**Write the real path only when you mean it.** Outside a fenced block, an
`Exempt:` line naming a real document is not an example — it is a declaration,
and the checker will report that document as deliberately absent. Keep
illustrations fenced, and use placeholders anyway, as the form above does.
docs: correct 57 claims the code and tracker disagreed with
Every document in the tree audited against the source, the tracker and git
history, each finding then given to a second reader tasked with refuting it.
74 raised, 12 refuted, 57 applied. No code changed.
THE README DESCRIBED A SKELETON
Its Status table — the one place a claim about what is built is allowed to live
— still read "there is no usable app yet", with Not built against Room, the four
core screens, fertility and notifications, and No round run against QA. Five
batches had shipped and three QA rounds had run.
TWO DOCUMENTS WERE SILENTLY NEVER FIRING
architecture/README.md and design/README.md wrote Governs: as prose ("the Gradle
module graph", "the design tokens in core/designsystem"). Neither contains a
path token, so doc-triggers.py reduced them to globs matching nothing, and one
real glob apiece made them look path-governing rather than subject-governing —
the state the script's own header calls invisible. Editing a Room entity never
fired the document owning the migration table. Both now fire, proved by running
the script.
SECURITY.md CLAIMED FOUR UNBUILT PROTECTIONS
App lock listed among what works offline; biometric/PIN gating described as
protecting app launch; the incognito launcher as existing; Play Billing in the
third parties table without the "not yet integrated" marker its neighbours
carry. All are Batch 06/07 work.
The advertising boundary was overstated in SECURITY.md and the README alike:
both said the ads module declares no dependency and a guard proves it. There is
no ads module. The pre-declared ":core:ads" to emptySet() rule is stricter than
the sentence it replaced and matches nothing until Batch 07, which is why the
guard is proved by injection rather than trusted.
SMALLER, EACH A REAL TRAP
WORK_CYCLE.md pointed at docs/architecture/scripts/forgejo-issue.py, a template
path absent here — missed by doc-claims.sh, which reads backticked prose and not
fenced blocks. ClaudeReport.md's Round notes said "No rounds yet" after three
rounds because ClaudeQAPlan.md's after-a-round list never named that section;
the playbook is fixed first. The instrumented-test count was eight in three
places and is four. HISTORY.md said the repository had no code and that nothing
had been tried and dropped, when three approaches had.
DELIBERATELY UNCHANGED
ClaudeReport.md's last verified build SHA stays at 0451fbe — no round has run
since, and moving it would claim a verification nobody performed. Every
DEVELOPMENT_LOG entry stays as written.
Guards: ./gradlew check, schema-guard.sh, doc-claims.sh (235 claimed paths, all
present), doc-triggers.py, and a link sweep over 21 markdown files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 16:33:14 -05:00
This repository currently declares none. Where a document is genuinely not applicable it
chore: adopt the project template and add the Kotlin/Compose skeleton
Period was a bare directory holding one 2,527-line specification, with no git
repository, no tracker and no documentation convention. This is the adoption
from Projects/Template/START-HERE-New-Project.md, plus a project that compiles
so the hooks and future guards have something real to run against.
Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to
docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the
capitalised Docs/ is gone. Every scaffolded document was filled in for Period.
docs/OPERATIONS.md deleted — an offline app is not a deployed service.
DOC_TRUST_MAP.md written last, describing what is actually here, including what
this project deliberately does not have.
Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm")
and cannot see the Android SDK, so the engine is testable without an emulator —
17 tests pass, 12 of them the acceptance cases from PRODUCT_PLAN.md §51.
BaselinePredictionEngine is a robust-median prototype and explicitly not the
product; it exists so Batch 02's replacement can be shown to be better rather
than merely different.
Versions verified against their official sources today rather than inherited
from the specification's own numbers, which that document asks for: Kotlin
2.4.10, AGP 9.3.1, Gradle 9.7.0, Compose BOM 2026.08.00, Room 2.8.4, Hilt
2.60.1. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no
longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk
stays 36, Play's floor from 2026-08-31, and the difference is deliberate.
Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md.
Three hooks in .githooks/, with pre-commit adapted to Gradle.
closes #1
closes #2
2026-08-18 02:16:47 -05:00
has been **deleted** and recorded above instead, which is the honest form: an
exemption says "kept elsewhere", and `OPERATIONS.md` is not kept anywhere.
**`docs/data/` and `docs/data/img/` cannot be exempted.** A declaration naming
either is refused rather than honoured, because the Command Center renders what
is in them — an exemption would produce a project card with no icon and nothing
docs: correct 57 claims the code and tracker disagreed with
Every document in the tree audited against the source, the tracker and git
history, each finding then given to a second reader tasked with refuting it.
74 raised, 12 refuted, 57 applied. No code changed.
THE README DESCRIBED A SKELETON
Its Status table — the one place a claim about what is built is allowed to live
— still read "there is no usable app yet", with Not built against Room, the four
core screens, fertility and notifications, and No round run against QA. Five
batches had shipped and three QA rounds had run.
TWO DOCUMENTS WERE SILENTLY NEVER FIRING
architecture/README.md and design/README.md wrote Governs: as prose ("the Gradle
module graph", "the design tokens in core/designsystem"). Neither contains a
path token, so doc-triggers.py reduced them to globs matching nothing, and one
real glob apiece made them look path-governing rather than subject-governing —
the state the script's own header calls invisible. Editing a Room entity never
fired the document owning the migration table. Both now fire, proved by running
the script.
SECURITY.md CLAIMED FOUR UNBUILT PROTECTIONS
App lock listed among what works offline; biometric/PIN gating described as
protecting app launch; the incognito launcher as existing; Play Billing in the
third parties table without the "not yet integrated" marker its neighbours
carry. All are Batch 06/07 work.
The advertising boundary was overstated in SECURITY.md and the README alike:
both said the ads module declares no dependency and a guard proves it. There is
no ads module. The pre-declared ":core:ads" to emptySet() rule is stricter than
the sentence it replaced and matches nothing until Batch 07, which is why the
guard is proved by injection rather than trusted.
SMALLER, EACH A REAL TRAP
WORK_CYCLE.md pointed at docs/architecture/scripts/forgejo-issue.py, a template
path absent here — missed by doc-claims.sh, which reads backticked prose and not
fenced blocks. ClaudeReport.md's Round notes said "No rounds yet" after three
rounds because ClaudeQAPlan.md's after-a-round list never named that section;
the playbook is fixed first. The instrumented-test count was eight in three
places and is four. HISTORY.md said the repository had no code and that nothing
had been tried and dropped, when three approaches had.
DELIBERATELY UNCHANGED
ClaudeReport.md's last verified build SHA stays at 0451fbe — no round has run
since, and moving it would claim a verification nobody performed. Every
DEVELOPMENT_LOG entry stays as written.
Guards: ./gradlew check, schema-guard.sh, doc-claims.sh (235 claimed paths, all
present), doc-triggers.py, and a link sweep over 21 markdown files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 16:33:14 -05:00
explaining why. If one of them is ever missing, the mechanism is a filed issue —
[`data/README.md` ](data/README.md ) says what to file and why a placeholder is
worse than a gap — never an exemption.