Skip to content

Commit 1a880bf

Browse files
authored
Merge pull request #152 from ActiveMemory/design/pd-m4-conventions
pd-m4: teach progressive disclosure the convention kind
2 parents 1225dbf + 4a7bd71 commit 1a880bf

247 files changed

Lines changed: 3022 additions & 1290 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.context/CONVENTIONS.md

Lines changed: 6 additions & 374 deletions
Large diffs are not rendered by default.

.context/DECISIONS.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,20 @@ For significant decisions:
4545
4646
-->
4747

48+
## [2026-07-19-100259] M4 conventions digestion: curated ## -section taxonomy, unified into the entry-kind mover
49+
50+
**Status**: Accepted
51+
52+
**Context**: CONVENTIONS.md is the last unbounded canonical root (390 lines, 18 stable curated ## sections). The M3 mover refuses the convention kind (ErrApplyNotEntryKind). The spec assumed conventions accrete as ### entries under a ## Recent staging zone, but the real file is a curated ## -section taxonomy with no ## Recent and no timestamps — the blocking TBD for M4.
53+
54+
**Decision**: M4 conventions digestion: curated ## -section taxonomy, unified into the entry-kind mover
55+
56+
**Rationale**: Approach A (unify) over a separate convention path or a one-time script: the file already fits the entry-kind preamble|staging|## Themes shape, so parametrizing the mover by a per-kind entry prefix (## for conventions vs ## [) removes special-case code instead of adding a parallel path. Identity = section title (no timestamp). Keep the scanner dumb — no fence detection (a rabbit hole in a destructive parser); byte-conservation plus the human-gated plan review make a dumb scanner safe against false ## boundaries. Curated-taxonomy matches what conventions are: stable, not accreting.
57+
58+
**Consequence**: Retire parseConvention, ConventionLinePrefix and HeadingRecent (## Recent); the three-self-similar-tiers claim becomes literally true. New sentinel ErrDuplicateStagedTitle (fail-loud on duplicate ## titles). heading.ParseEntryBlocks stays untouched (zero regression to the live LEARNINGS/DECISIONS path). ctx convention add joins the unified prepend anchor (insert before the first ## section, skipping the structural ## Themes; fallback AfterHeader), landing new conventions newest-first in staging above ## Themes — correcting the old AppendAtEnd inconsistency (decisions/learnings already prepend). This add-path change is IN M4 scope. specs/progressive-disclosure.md revised accordingly.
59+
60+
---
61+
4862
## Themes
4963

5064
- package-structure-and-quality-gates — Package taxonomy & quality gates: write/ output, internal/err, config/ constants, doc.go floor, AST audit tests, log split, GraphBuilder interface → [package-structure-and-quality-gates](decisions/package-structure-and-quality-gates.md)

.context/LEARNINGS.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,26 @@ DO NOT UPDATE FOR:
1515
-->
1616

1717

18+
## [2026-07-19-210439] The disclosure parser is a deliberately dumb line-scanner (skips <!-- --> comments, not code fences)
19+
20+
**Context**: M4 unifies conventions into the ## -section entry model, where ## collides far more than ## [. Fence detection was considered and rejected.
21+
22+
**Lesson**: Fence detection (nested/tilde/indented fences) is a rabbit hole in a parser that does destructive moves. Safety comes from byte-conservation (a mis-cut is a reversible mis-grouping, not loss) plus the human-gated plan review (a false entry shows up at inspect).
23+
24+
**Application**: Don't add fence-awareness to the disclosure scanner; a stray ## inside a fenced code block is fixed at authoring time (rewrite that one line), never by the tool.
25+
26+
---
27+
28+
## [2026-07-19-210439] Measurement gates surface a real bug in every disclosure milestone
29+
30+
**Context**: pd-m1's add-path measurement gate flushed the insert.AfterHeader tail-truncation bug; pd-m3's T17 real rollout surfaced the DECISIONS-template comment-parse bug (a ## [ inside <!-- --> read as staging, so Validate returned ErrStagingUnparsable once the fold emptied real staging).
31+
32+
**Lesson**: For the disclosure work, driving the REAL fold — not unit tests — is where bugs hide. The measurement gate is not ceremony.
33+
34+
**Application**: Treat pd-m4 T20 (drive the digest on the real CONVENTIONS.md) as the authoritative gate; expect it to find something; verify conservation + all invariants on the real file before T21's human-gated rollout.
35+
36+
---
37+
1838
---
1939

2040
## Themes

.context/TASKS.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2998,7 +2998,35 @@ not agent discipline — the clobber risk class the M1 guards exist for.
29982998

29992999
- [x] [E2] `ctx disclosure apply` CLI — reads plan JSON, refuses non-knowledge files + convention kind, write-safe on error, doc.go + wiring guards (T10–T13). Plan: specs/plans/pd-m3.md #priority:medium #session:f706d9de #branch:design/progressive-disclosure #added:2026-07-18
30003000

3001-
- [ ] [E3] `ctx-digest` apply-path skill + copilot sync; MEASUREMENT GATE T16: driven apply on a realistic fixture moves entries + writes gists losslessly; real LEARNINGS→DECISIONS rollout is human-gated (T17); milestone gate (T14–T18). Plan: specs/plans/pd-m3.md #priority:medium #session:f706d9de #branch:design/progressive-disclosure #added:2026-07-18
3001+
- [x] [E3] `ctx-digest` apply-path skill + copilot sync; MEASUREMENT GATE T16: driven apply on a realistic fixture moves entries + writes gists losslessly; real LEARNINGS→DECISIONS rollout is human-gated (T17); milestone gate (T14–T18). Plan: specs/plans/pd-m3.md #priority:medium #session:f706d9de #branch:design/progressive-disclosure #added:2026-07-18
3002+
3003+
### Progressive disclosure — Milestone 4 (CONVENTIONS)
3004+
3005+
Plan: `specs/plans/pd-m4.md` · Spec: `specs/progressive-disclosure.md`
3006+
Milestone 4 teaches the mover the **convention kind**: CONVENTIONS.md's 18
3007+
curated `## ` sections fold into per-theme tier-1 files under the same
3008+
`preamble | staging | ## Themes` layout as entry kinds (per-kind entry
3009+
prefix `## ` vs `## [`, title identity). Retires the `### `-under-`##
3010+
Recent` model; `ctx convention add` prepends above `## Themes`.
3011+
3012+
**Completion rule**: an epic is `[x]` only when every task in its range is
3013+
`[x]`/`[o]` in `specs/plans/pd-m4.md` (the plan is the source of truth).
3014+
Epics partition T01–T22: E1[T01–04] E2[T05–09] E3[T10–12] E4[T13–15]
3015+
E5[T16–17] E6[T18–19] E7[T20–22] = 22.
3016+
3017+
- [x] [E1] Config + vocabulary: `EntryPrefix(Kind)`, retire `ConventionLinePrefix`/`HeadingRecent`, `ErrDuplicateStagedTitle`, `ThemeDir(convention)` (T01–T04). Plan: specs/plans/pd-m4.md #priority:medium #session:8bc7532d #branch:design/pd-m4-conventions #added:2026-07-19
3018+
3019+
- [x] [E2] Parser unification: convention `## `-enumerator (title identity), prefix-parametrized `parseEntryKind`, delete `parseConvention`, kind-aware inspect, `SplitStaging` on `## ` (T05–T09). Plan: specs/plans/pd-m4.md #priority:medium #session:8bc7532d #branch:design/pd-m4-conventions #added:2026-07-19
3020+
3021+
- [x] [E3] Validate: generalize entry-below-themes to per-kind prefix, dup-title fail-loud (`ErrDuplicateStagedTitle`), rule-3 via enumerator (T10–T12). Plan: specs/plans/pd-m4.md #priority:medium #session:8bc7532d #branch:design/pd-m4-conventions #added:2026-07-19
3022+
3023+
- [x] [E4] Mover: lift `ErrApplyNotEntryKind` for conventions, Apply end-to-end + title-only identity through the plan (T13–T15). Plan: specs/plans/pd-m4.md #priority:medium #session:8bc7532d #branch:design/pd-m4-conventions #added:2026-07-19
3024+
3025+
- [x] [E5] Add-path: `ctx convention add` prepends above `## Themes` (was AppendAtEnd) + post-fold invariant test (T16–T17). Plan: specs/plans/pd-m4.md #priority:medium #session:8bc7532d #branch:design/pd-m4-conventions #added:2026-07-19
3026+
3027+
- [x] [E6] `ctx-digest` skill — convention path + copilot sync (T18–T19). Plan: specs/plans/pd-m4.md #priority:medium #session:8bc7532d #branch:design/pd-m4-conventions #added:2026-07-19
3028+
3029+
- [x] [E7] MEASUREMENT GATE T20 (drive digest on realistic CONVENTIONS fixture); real CONVENTIONS rollout human-gated (T21); milestone gate (T20–T22). Plan: specs/plans/pd-m4.md #priority:medium #session:8bc7532d #branch:design/pd-m4-conventions #added:2026-07-19
30023030

30033031
- [ ] Progressive disclosure for canonical context files: the growth warnings (LEARNINGS/DECISIONS/CONVENTIONS over threshold) are NOT redundancy — consolidation only got LEARNINGS 98→88 because the entries are distinct, dense signal. The real lever is a structural pass: canonical files carry a tight summary/index and detail loads on demand (via `ctx index`/`ctx search` projection + an archive/detail tier). Manual design exercise first (/ctx-brainstorm → spec), then codify the repeatable procedure as a new skill (e.g. /ctx-progressive-disclosure). This exercise IS the baseline for the skill. #priority:medium #session:87e465a0 #branch:main #added:2026-07-16
30043032
DESIGN DONE 2026-07-16 (session 87e465a0): /ctx-brainstorm run to

.context/conventions/cli.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# cli
2+
3+
## CLI Structure
4+
5+
- **CLI package taxonomy**: Every package under `internal/cli/` follows:
6+
parent.go (Cmd wiring), doc.go, `cmd/root/` or `cmd/<sub>/`
7+
(implementation), `core/` (shared helpers)
8+
- **cmd/ directories**: Only cmd.go, run.go, and tests — helpers and
9+
output go to `core/`
10+
- **core/ structs**: Consolidated into a single `types.go` file
11+
- **User-facing text via assets**: All text routed through
12+
`internal/assets` with YAML-backed TextDescKeys — no inline strings
13+
in `core/` or `cmd/` packages
14+
- **config/ doc.go**: Every package under `internal/config/` must have
15+
a doc.go with the project header and a one-line package comment
16+
- **DescKey prefix**: Not CmdDescKey — `cmd.DescKeyFoo` not
17+
`cmd.CmdDescKeyFoo` (Go package hygiene, avoids stutter)
18+
- **Cobra Use: fields**: Must reference `cmd.Use*` constants, never raw
19+
strings or `cmd.DescKey*`
20+
- **Run functions exported PascalCase**: `Run`, `RunImport`,
21+
`RunArchive` etc. No private `runXXX` variants
22+
- **write/ packages write to stdio only**: Functions take
23+
`*cobra.Command`, not `io.Writer`. Exception: `write/rc` writes to
24+
`os.Stderr` because rc loads before cobra
25+
- **Package directory names singular**: Unless Go convention requires
26+
plural
27+
- **Import grouping**: stdlib — blank line — external deps (cobra,
28+
yaml) — blank line — ctx imports. Three groups, always in this order
29+
- **camelCase import aliases**: `cFlag` not `cflag`, `cfgFmt` not
30+
`cfgfmt`
31+
- **Icons and symbols as token constants**: Not unicode escapes
32+
- **Cross-cutting domain types in internal/entity**: Types used by one
33+
package stay in that package; types used across packages go to entity
34+
35+
- Warn format strings centralized in config/warn/ — use warn.Close,
36+
warn.Write, warn.Remove, warn.Mkdir, warn.Rename, warn.Walk, warn.Getwd,
37+
warn.Readdir, warn.Marshal instead of inline format strings in log.Warn calls
38+
39+
- Nav frontmatter title: fields must not contain ctx — frontmatter does not
40+
support backticks, so the brand stays out of nav titles entirely (Hub, not The
41+
ctx Hub). Body headings can use `ctx` since markdown supports backticks.
42+
43+
- CLI flags and slash-commands inside headings or admonition titles must be
44+
backticked: `--keep-frontmatter=false`, `/ctx-reflect`. The title-case engine
45+
in hack/title-case-headings.py protects these patterns automatically, but
46+
authors should still backtick at write time for clarity.
47+
48+
- File extensions inside headings must be backticked when title-case
49+
capitalization would otherwise apply: write `CONSTITUTION.md`, not
50+
CONSTITUTION.Md. The title-case engine refuses to capitalize lowercase tokens
51+
following a literal . dot, but explicit backticks remain the clearest signal.
52+
- New editor integrations include an MCP-merge test covering: create / empty
53+
file / preserve existing keys / skip when registered / reject malformed JSON
54+
55+
- Substrate vs. artifact placement: cognitive substrate (consumed and mutated
56+
via ctx-mediated paths — `ctx agent`, `ctx decision add`, `/ctx-kb-ingest`,
57+
`/ctx-handover`, ceremonies) lives under `.context/`; project artifacts (read
58+
and edited directly by humans — `specs/`, `CLAUDE.md`, `GETTING_STARTED.md`,
59+
`docs/`) live at the project root; tool config and tool homes (`.ctxrc`,
60+
`.claude/`) live at root by dotfile/tool convention. The kb is substrate, not
61+
artifact: direct file edits remain possible per Invariant 1, but the
62+
skill-mediated path is the discipline. Rationale recorded in DECISIONS.md.
63+
64+
## User-Facing Surface Completeness
65+
66+
When a change adds or alters a user-facing surface — a new
67+
`ctx` subcommand, a new flag, an observable behavior change,
68+
a new exit shape, a new output line — the work is **not
69+
complete** until every one of the following has been updated
70+
in the same commit (or the same stacked PR, with the user's
71+
explicit OK):
72+
73+
- `internal/assets/commands/commands.yaml` and
74+
`examples.yaml` for the subcommand description and example
75+
- `internal/assets/claude/skills/ctx-<area>/SKILL.md` so the
76+
agent knows the surface exists and when to trigger it
77+
- `internal/assets/integrations/copilot-cli/skills/<...>` if
78+
a parallel skill exists for the integration
79+
- `docs/recipes/<related-recipe>.md` for any recipe that
80+
already demonstrates the broader feature; consider a new
81+
recipe if the surface is its own workflow shape
82+
- `docs/cli/<command>.md` if a per-command CLI doc page
83+
exists for this surface
84+
85+
Splitting these into a "Phase 2 / follow-up commit / future
86+
sweep" is **deferral** in the Constitution's sense, no matter
87+
how the phase is labeled. Docs are part of the deliverable,
88+
not a separable improvement. The "I can create a follow-up
89+
task" prohibition applies verbatim.
90+
91+
Acceptable exceptions (state them in the commit body):
92+
93+
- The surface is internal-only (no human user encounters it).
94+
- A recipe / skill genuinely does not exist for this feature
95+
area and writing one is itself a larger separable piece of
96+
work (then file the spec for that piece in the same commit,
97+
do not just defer).
98+
99+
The Self-check before declaring a feature commit complete is:
100+
*"If a user runs `ctx help` or asks `/ctx-<area>` to do this
101+
new thing today, will the help text / skill / recipe match
102+
what the code does?"* If no, the commit is not complete.
103+

.context/conventions/code-style.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# code style
2+
3+
## Naming
4+
5+
- **Constants use semantic prefixes**: Group related constants with prefixes
6+
- `Dir*` for directories (`DirContext`, `DirArchive`)
7+
- `File*` for file paths (`FileSettings`, `FileClaudeMd`)
8+
- `Filename*` for file names only (`FilenameTask`, `FilenameDecision`)
9+
- `*Type*` for enum-like values (`UpdateTypeTask`, `UpdateTypeDecision`)
10+
- **Package name = folder name**: Go canonical pattern
11+
- `package initialize` in `initialize/` folder
12+
- Never `package initcmd` in `init/` folder
13+
- **Go package names: lowercase, no underscores, no
14+
mixedCaps**: per the [Effective Go](https://go.dev/blog/package-names)
15+
guidance and the stdlib precedent (`strconv`, `httptest`,
16+
`bufio`). Apply to the directory too — `internal/flagbind/`,
17+
not `internal/flag_bind/`. Filenames may use underscores
18+
(`foo_test.go` is canonical); package names may not. When in
19+
doubt, find the closest stdlib analogue and copy its shape.
20+
- **Maps reference constants**: Use constants as keys, not literals
21+
- `map[string]X{ConstKey: value}` not `map[string]X{"literal": value}`
22+
23+
## Casing
24+
25+
- **Proper nouns keep their casing** in comments, strings, and docs
26+
- `Markdown` not `markdown` (it's a language name)
27+
- `YAML`, `JSON`, `TOML` — always uppercase
28+
- `GitHub`, `JavaScript`, `PostgreSQL` — match official casing
29+
- Exception: code fence language identifiers are lowercase (`` ```markdown ``)
30+
31+
## Predicates
32+
33+
- **No Is/Has/Can prefixes**: `Completed()` not
34+
`IsCompleted()`, `Empty()` not `IsEmpty()`
35+
- Applies to all bool-returning funcs and methods, exported or
36+
not (`topicNested`, not `hasNestedTopic`)
37+
38+
## Line Width
39+
40+
- **Target ~80 characters**: Highly encouraged, not a hard limit
41+
- Some lines will naturally exceed it (long strings,
42+
struct tags, URLs) — that's fine
43+
- Drift accumulates silently, especially in test code
44+
- Break at natural points: function arguments, struct fields, chained calls
45+
46+
## Patterns
47+
48+
- **Centralize magic strings**: All repeated literals
49+
belong in a `config` or `constants` package
50+
- If a string appears in 3+ files, it needs a constant
51+
- If a string is used for comparison, it needs a constant
52+
- **Path construction**: Always use stdlib path joining
53+
- Go: `filepath.Join(dir, file)`
54+
- Python: `os.path.join(dir, file)`
55+
- Node: `path.join(dir, file)`
56+
- Never: `dir + "/" + file`
57+
- **Constants reference constants**: Self-referential definitions
58+
- `FileType[UpdateTypeTask] = FilenameTask` not
59+
`FileType["task"] = "TASKS.md"`
60+
- **No error variable shadowing**: Use descriptive names
61+
when multiple errors exist in a function
62+
- `readErr`, `writeErr`, `indexErr` — not repeated `err` / `err :=`
63+
- Shadowed `err` silently disconnects from the outer
64+
variable, causing subtle bugs
65+
- **Colocate related code**: Group by feature, not by type
66+
- `session/run.go`, `session/types.go`, `session/parse.go`
67+
- Not: `runners/session.go`, `types/session.go`, `parsers/session.go`
68+
69+
## Duplication
70+
71+
- **Non-test code**: Apply the rule of three — extract
72+
when a block appears 3+ times
73+
- Watch for copy-paste during task-focused sessions
74+
where the agent prioritizes completion over shape
75+
- **Test code**: Some duplication is acceptable for readability
76+
- When the same setup/assertion block appears 3+ times, extract a test helper
77+
- Use `t.Helper()` so failure messages point to the caller, not the helper
78+
79+
## Error Handling
80+
81+
- **Zero silent error discard**: Handle every error, never suppress with
82+
`_ =` or `//nolint:errcheck`. Production: defer-close logs to stderr
83+
via `log.Warn()`. Test: `t.Fatal(err)` for setup, `t.Log(err)` for
84+
cleanup. For gosec false positives: fix the code rather than adding
85+
nolint markers — the goal is zero golangci-lint suppressions
86+
- **Error constructors in internal/err**: Never in per-package err.go
87+
files — eliminates the broken-window pattern where agents add local
88+
errors when they see a local err.go exists
89+
- **Identity sentinels are `entity.Sentinel` consts, not
90+
`errors.New`**: Declare `errors.Is` targets as
91+
`const ErrX = entity.Sentinel(text.DescKey...)`. The
92+
user-facing text lives in `commands/text/errors.yaml` keyed by
93+
`err.<pkg>.<name>`; the sentinel's `Error()` resolves it via
94+
`desc.Text` at call time. Never write
95+
`var ErrX = errors.New("english")` — the English leaks into
96+
`.Error()` output and bypasses localization. Never add an
97+
`ErrMsg* = "english"` const layer in `internal/config/<pkg>/`
98+
to back the sentinel; that layer is dead text once the typed
99+
Sentinel does the lookup itself.
100+
- **Parameterised errors use typed structs**: When the error
101+
needs to carry fields (path, name, etc.), define a struct in
102+
`internal/err/<area>/` with a pointer-receiver `Error()` and
103+
optional `Is(error) bool` for sentinel-compatibility. See
104+
`internal/err/context.NotFoundError` for the canonical shape.
105+

0 commit comments

Comments
 (0)