ctxr-cli 0.19.0 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +9 -14
  2. package/dist/commands/adapters-generate.d.ts +2 -14
  3. package/dist/commands/adapters-generate.d.ts.map +1 -1
  4. package/dist/commands/adapters-generate.js +2 -50
  5. package/dist/commands/adapters-generate.js.map +1 -1
  6. package/dist/commands/init.d.ts.map +1 -1
  7. package/dist/commands/init.js +12 -0
  8. package/dist/commands/init.js.map +1 -1
  9. package/dist/commands/update.d.ts.map +1 -1
  10. package/dist/commands/update.js +0 -3
  11. package/dist/commands/update.js.map +1 -1
  12. package/dist/config/defaults.d.ts +10 -7
  13. package/dist/config/defaults.d.ts.map +1 -1
  14. package/dist/config/defaults.js +10 -7
  15. package/dist/config/defaults.js.map +1 -1
  16. package/dist/config/schema.js +3 -3
  17. package/dist/core/adapter-outputs.d.ts +19 -0
  18. package/dist/core/adapter-outputs.d.ts.map +1 -0
  19. package/dist/core/adapter-outputs.js +54 -0
  20. package/dist/core/adapter-outputs.js.map +1 -0
  21. package/dist/core/agents-doc.d.ts.map +1 -1
  22. package/dist/core/agents-doc.js +3 -1
  23. package/dist/core/agents-doc.js.map +1 -1
  24. package/dist/core/checks/write-lifecycle-checks.d.ts +8 -0
  25. package/dist/core/checks/write-lifecycle-checks.d.ts.map +1 -1
  26. package/dist/core/checks/write-lifecycle-checks.js +22 -3
  27. package/dist/core/checks/write-lifecycle-checks.js.map +1 -1
  28. package/dist/core/conventions.d.ts +3 -4
  29. package/dist/core/conventions.d.ts.map +1 -1
  30. package/dist/core/conventions.js +3 -4
  31. package/dist/core/conventions.js.map +1 -1
  32. package/dist/core/reconcile.d.ts +2 -1
  33. package/dist/core/reconcile.d.ts.map +1 -1
  34. package/dist/core/reconcile.js +11 -4
  35. package/dist/core/reconcile.js.map +1 -1
  36. package/dist/core/skills.d.ts +15 -0
  37. package/dist/core/skills.d.ts.map +1 -1
  38. package/dist/core/skills.js +53 -1
  39. package/dist/core/skills.js.map +1 -1
  40. package/dist/version.d.ts +1 -1
  41. package/dist/version.js +1 -1
  42. package/package.json +1 -1
  43. package/templates/agents/canonical.md +4 -2
  44. package/templates/agents/retrieval-leg-routing.md +6 -0
  45. package/templates/notes/Concept.md +2 -1
  46. package/templates/skills/ctxr-capture.md +3 -1
  47. package/templates/skills/ctxr-publish.md +16 -3
  48. package/templates/skills/ctxr-second-opinion/lenses.md +3 -1
  49. package/templates/skills/ctxr-second-opinion/run.mjs +47 -13
  50. package/templates/skills/ctxr-second-opinion.md +182 -0
  51. package/templates/skills/ctxr-session-capture.md +3 -2
@@ -99,9 +99,17 @@ before reporting the page as ready. It answers only what's derivable from the fi
99
99
  in step 1, the form and reader choices in step 5, and factual accuracy stay judgment calls, not checker output.
100
100
 
101
101
  `ctxr publish new` and `ctxr publish check` each end by naming the address `ctxr serve` answers for
102
- this page. Open it and look at the page before you hand it on. Whether the form works, whether the
103
- prose reads, whether it renders at all is exactly what the checks above cannot answer, and it is
104
- cheapest to fix while the page is still yours.
102
+ this page. Look at the page before you hand it on. Whether the form works, whether the prose reads,
103
+ whether it renders at all is exactly what the checks above cannot answer, and it is cheapest to fix
104
+ while the page is still yours.
105
+
106
+ That address may be served from another host, or behind an access proxy, where you cannot reach it.
107
+ You do not need it: from this machine, start `ctxr serve` in the background, open the address it
108
+ prints followed by the page's route in your browser tool, and stop the server when you are done. A
109
+ page still in a session worktree is served there under its previewable route, the same as anywhere
110
+ else. If you have no way to see a rendered page, say in your hand-off that it has not been checked
111
+ visually, so whoever reviews it knows they are the first to look. Never report a page as ready on the
112
+ strength of `ctxr publish check` alone.
105
113
 
106
114
  A page written in a session worktree is previewable rather than published, because the
107
115
  published-pages address reads the store's own checkout and the page has not reached it yet. That
@@ -110,6 +118,11 @@ previewable address stops answering the moment the worktree is reclaimed — the
110
118
  Report the address you actually opened, and say which of the two it is, so nobody is handed a link
111
119
  that is about to change.
112
120
 
121
+ Both addresses are full URLs when the store declares `serve.base_url` in `contexture.yaml`, and bare
122
+ routes such as `/preview/…` when it does not. A bare route is not a link anyone else can open. Do not
123
+ guess a host to put in front of it: hand on the route as it is, and tell the operator that declaring
124
+ `serve.base_url` would make every address a command reports a link.
125
+
113
126
  ## 7. It's a page, not a note
114
127
 
115
128
  The published-pages location is excluded from retrieval by default — a page never becomes a source for
@@ -12,4 +12,6 @@ drop one: a collapsed pair adds cost and no breadth.
12
12
  - **brand-strategist** — long-term positioning, category ownership, how it ages.
13
13
 
14
14
  A store may add its own lens inline as `Name:description`, for example
15
- `"Plainspoken:blunt, substance first, distrusts slogans"`.
15
+ `"Plainspoken:blunt, substance first, distrusts slogans"`. The description may contain commas. A comma starts
16
+ the next lens only when the text after it is exactly a lens name above or begins `Name:`, so keep colons out
17
+ of a description.
@@ -33,25 +33,43 @@ const MAX_ARGV_BYTES = 120_000;
33
33
 
34
34
  const env = (name, fallback) => process.env[`CTXR_SECOND_OPINION_${name}`] || fallback;
35
35
 
36
- /** Model choice lives here, in one table. Override by environment, never by editing a store. */
36
+ /**
37
+ * Model choice lives here, in one table. Override by environment, never by editing a store.
38
+ *
39
+ * Effort is each provider's own recommended default, not one this runner picked:
40
+ * - claude: no --effort at all, so Claude Code applies its own default for the model.
41
+ * - codex: `medium`, the default its model catalog declares (`default_reasoning_level`). It is passed
42
+ * explicitly because the flag cannot simply be left off: with no pin in config.toml, codex resolves to
43
+ * `reasoning effort: none` on a ChatGPT login (seen 2026-10-05), which is no review at all. `low` at the
44
+ * `fast` tier. A machine that pins its own effort in config.toml is overridden by this; set
45
+ * CTXR_SECOND_OPINION_CODEX_EFFORT to use another.
46
+ * - agy: the effort is part of the model name; `(High)` is Gemini's default thinking level.
47
+ * For scale: on one short plan Claude took 35 s at `high` and 514 s at `max`, so a deeper pass is a choice
48
+ * (CTXR_SECOND_OPINION_CLAUDE_EFFORT=max), not something to leave on by default.
49
+ */
37
50
  function tiers() {
38
51
  return {
39
52
  strong: {
40
- claude: { model: env('CLAUDE_MODEL', 'opus'), effort: env('CLAUDE_EFFORT', 'max') },
41
- codex: { effort: env('CODEX_EFFORT', 'xhigh') },
53
+ claude: { model: env('CLAUDE_MODEL', 'opus'), effort: env('CLAUDE_EFFORT', null) },
54
+ codex: { effort: env('CODEX_EFFORT', 'medium') },
42
55
  agy: { model: env('AGY_MODEL', 'Gemini 3.1 Pro (High)') },
43
56
  },
44
57
  fast: {
45
- claude: { model: env('CLAUDE_MODEL', 'haiku'), effort: null },
46
- codex: { effort: env('CODEX_EFFORT', 'medium') },
58
+ claude: { model: env('CLAUDE_MODEL', 'haiku'), effort: env('CLAUDE_EFFORT', null) },
59
+ codex: { effort: env('CODEX_EFFORT', 'low') },
47
60
  agy: { model: env('AGY_MODEL', 'Gemini 3.8 Flash (High)') },
48
61
  },
49
62
  };
50
63
  }
51
64
 
52
- /** The flags this runner passes, so preflight can confirm each CLI still accepts them. */
65
+ /**
66
+ * The flags this runner passes, so preflight can confirm each CLI's --help still lists them. Only flags
67
+ * the help text documents belong here: `claude` still accepts a hidden --max-turns, but with `--tools ""`
68
+ * there is no tool turn to bound, so it is not passed at all. --effort is not listed because it is only
69
+ * passed when the environment asks for one.
70
+ */
53
71
  const FLAGS_USED = {
54
- claude: { helpArgs: ['--help'], flags: ['--max-turns', '--tools', '--model', '--effort', '--no-session-persistence', '--disable-slash-commands', '--output-format'] },
72
+ claude: { helpArgs: ['--help'], flags: ['--tools', '--model', '--no-session-persistence', '--disable-slash-commands', '--output-format'] },
55
73
  codex: { helpArgs: ['exec', '--help'], flags: ['--skip-git-repo-check', '--sandbox', '--ephemeral', '--color', '--config'] },
56
74
  agy: { helpArgs: ['--help'], flags: ['--print', '--sandbox', '--disable-slash-commands', '--model'] },
57
75
  };
@@ -59,7 +77,7 @@ const FLAGS_USED = {
59
77
  class UsageError extends Error {}
60
78
 
61
79
  function parseArgs(argv) {
62
- const opts = { mode: 'critique', tier: 'strong', timeout: 240, only: null, lens: null, out: null, planFile: null, promptFile: null, preflight: false, noWrap: false, seed: null };
80
+ const opts = { mode: 'critique', tier: 'strong', timeout: 600, only: null, lens: null, out: null, planFile: null, promptFile: null, preflight: false, noWrap: false, seed: null };
63
81
  const takesValue = new Set(['--mode', '--tier', '--timeout', '--only', '--lens', '--out', '--plan-file', '--prompt-file', '--seed']);
64
82
  for (let i = 0; i < argv.length; i++) {
65
83
  const a = argv[i];
@@ -146,7 +164,7 @@ const firstLines = (text, n) => text.trim().split('\n').slice(0, n).join(' | ').
146
164
  function buildInvocation(cli, prompt, tier) {
147
165
  const t = tiers()[tier][cli];
148
166
  if (cli === 'claude') {
149
- const args = ['-p', '--max-turns', '1', '--tools', '', '--model', t.model, '--no-session-persistence', '--disable-slash-commands', '--output-format', 'json'];
167
+ const args = ['-p', '--tools', '', '--model', t.model, '--no-session-persistence', '--disable-slash-commands', '--output-format', 'json'];
150
168
  if (t.effort) args.push('--effort', t.effort);
151
169
  return { cmd: 'claude', args, stdin: prompt };
152
170
  }
@@ -209,7 +227,8 @@ export function parseCritique(text) {
209
227
  const findings = [];
210
228
  const blocks = text.split(/^### F\d+\b/m).slice(1);
211
229
  for (const block of blocks) {
212
- const head = /^\s*\[(critical|major|minor)\]\s*(?:confidence\s*=\s*([01](?:\.\d+)?))?/i.exec(block);
230
+ // Brackets around the severity are what the contract asks for, and models drop them: accept both.
231
+ const head = /^\s*\[?(critical|major|minor)\]?\s*(?:confidence\s*[=:]\s*([01](?:\.\d+)?))?/i.exec(block);
213
232
  const evidence = /^Evidence:\s*(.+)$/im.exec(block)?.[1] ?? '';
214
233
  findings.push({
215
234
  severity: head?.[1]?.toLowerCase() ?? null,
@@ -260,13 +279,28 @@ function loadLensRoster() {
260
279
  return roster;
261
280
  }
262
281
 
282
+ /**
283
+ * Splits `--lens` into one string per lens. Lenses are comma-separated, but an inline description may itself
284
+ * contain commas: a fragment starts a NEW lens only when it is exactly a roster name or begins `Name:`;
285
+ * any other fragment continues the lens before it. So `minimalist,House taste:blunt, anti-slogan` is two
286
+ * lenses, and a description must not contain a colon (that would read as the start of another lens).
287
+ */
288
+ export function splitLenses(spec, roster) {
289
+ const lenses = [];
290
+ for (const fragment of spec.split(',').map((f) => f.trim()).filter(Boolean)) {
291
+ const startsLens = roster.has(fragment.toLowerCase()) || /^[^:,]{1,40}:/.test(fragment);
292
+ if (startsLens || lenses.length === 0) lenses.push(fragment);
293
+ else lenses[lenses.length - 1] += `, ${fragment}`;
294
+ }
295
+ return lenses;
296
+ }
297
+
263
298
  function lensBlock(spec) {
264
299
  const roster = loadLensRoster();
265
- const specs = spec.split(',').map((s) => s.trim()).filter(Boolean);
266
300
  const described = [];
267
- for (const s of specs) {
301
+ for (const s of splitLenses(spec, roster)) {
268
302
  if (roster.has(s.toLowerCase())) described.push(`- ${s.toLowerCase()} — ${roster.get(s.toLowerCase())}`);
269
- else if (s.includes(':')) {
303
+ else if (/^[^:,]{1,40}:/.test(s)) {
270
304
  const [name, ...rest] = s.split(':');
271
305
  described.push(`- ${name.trim()} — ${rest.join(':').trim()}`);
272
306
  } else throw new UsageError(`unknown lens "${s}" (known: ${[...roster.keys()].join(', ')}; or pass "Name:description")`);
@@ -0,0 +1,182 @@
1
+ Get an independent read from models built by different labs before you commit to something you cannot
2
+ cheaply take back, or when your own taste is one vote among several. A second opinion from the same model
3
+ family you are running as mostly repeats you, so this skill sends the work to three command-line tools backed
4
+ by three different families and hands you their answers. It does not decide anything and it never carries out
5
+ what it reviews. Weighing the answers is your job, and steps 4 and 5 are how.
6
+
7
+ ## 1. Critique, poll, or just answer?
8
+
9
+ - **Critique** when the question is *will this work?* A plan with several steps, an architectural or
10
+ data-model choice, anything that deletes, force-pushes, sends, spends money, or touches secrets. Also a
11
+ recovery sequence run against a broken system: those plans look fine right up to the step that makes it
12
+ worse.
13
+ - **Poll** when the question is *which of these is better?* and being wrong costs mild regret: a name, a
14
+ framing, a piece of copy, which of several tools fits. If all three agreeing would settle it, it is a poll.
15
+ If you would still want each reviewer's top objections and a suggested edit, it is a critique.
16
+ - **Answer directly** when there is one right answer, when the operator has already decided and wants
17
+ confirmation, or when the work is read-only investigation.
18
+
19
+ Push back when asked for a critique of a taste question. "Which domain suffix should I pick?" is a poll. "Should
20
+ I move my mail to a custom domain?" is a critique: it has steps, an order, and parts that cannot be undone. Say
21
+ which you are running and why in one line before you start.
22
+
23
+ A poll that asks a lens to judge whether something will *work* has become a critique. Stop and use that mode.
24
+
25
+ ## 2. Check the tools before spending anything
26
+
27
+ ```
28
+ node __SKILLS_PATH__/ctxr-second-opinion/run.mjs --preflight
29
+ ```
30
+
31
+ Each of the three should report `OK` within about twenty seconds. The preflight also confirms that each
32
+ tool still accepts the options the runner passes, because a renamed option makes a reviewer silently run at
33
+ default settings and nothing else notices.
34
+
35
+ If one is down, stop and give the operator the two real choices: sign that tool in and re-run, or continue
36
+ with the others by adding `--only` and naming them. Do not quietly run with one model. A single model is not a
37
+ second opinion, and the runner refuses to start with fewer than two.
38
+
39
+ Failure signatures: `Not logged in` means Claude needs `/login`; a `401` or an invalid refresh token from
40
+ Codex means `codex login`; an agy call that hangs usually means its sign-in expired and it needs one
41
+ interactive run.
42
+
43
+ If the plan came from someone other than the operator, leave agy out with `--only claude,codex`. Claude
44
+ runs with no tools at all and Codex in a read-only sandbox. Agy has no mode that removes its ability to act:
45
+ it runs in an empty scratch directory with terminal restrictions, which contains an accident and not an attack.
46
+
47
+ ## 3. Run a critique
48
+
49
+ Write the plan to a file. The reviewers can only cite what is written down, and they cite by step number:
50
+
51
+ ```
52
+ # Plan: <one-line goal>
53
+
54
+ ## Context
55
+ <two to five lines: what is being attempted and what constrains it>
56
+
57
+ ## Steps
58
+ S1. <step>
59
+ S2. <step>
60
+
61
+ ## Assumptions
62
+ - <what the plan takes for granted>
63
+
64
+ ## Reversibility
65
+ - Reversible: <steps>
66
+ - Irreversible: <steps>
67
+ ```
68
+
69
+ Then run it:
70
+
71
+ ```
72
+ node __SKILLS_PATH__/ctxr-second-opinion/run.mjs --mode critique --plan-file <plan> --out <dir>
73
+ ```
74
+
75
+ Three reviewers each read the plan alone: structure and ordering, failure modes and trust boundaries,
76
+ simplicity and cost. None sees another's answer. That is deliberate: reviewers who read each other converge
77
+ on whoever spoke first, and a correct one gets talked out of it. Do not add a second round.
78
+
79
+ The runner prints the letters it wrote, whether each is valid or blind, and any reviewer that failed. It
80
+ never prints which model wrote which letter. A **blind** critique returned a well-formed verdict but cited
81
+ nothing in the plan, so it was written without reading it; it does not count. **Partial** means fewer than
82
+ two valid critiques: say so at the top of your answer and do not call anything a consensus.
83
+
84
+ Add `--tier fast` to drop each family to a cheaper model, for a plan that is small and easy to redo. The
85
+ default tier is right for anything the operator will act on.
86
+
87
+ Each reviewer runs at its provider's own recommended default effort, not one this skill chose. Claude gets no
88
+ effort setting, so Claude Code applies its default for the model. Codex gets `medium`, the default its model
89
+ catalog declares; the runner passes it explicitly because Codex left to itself, with no setting pinned, can
90
+ resolve to no reasoning at all. Gemini's effort is part of its model name, and `High` is its default. Measured on a
91
+ short plan, a default critique took about 40 seconds in all, 30 to 37 seconds per reviewer, and costs a few tens
92
+ of cents. A deeper pass is a choice: set `CTXR_SECOND_OPINION_CLAUDE_EFFORT=max` and Claude alone took over
93
+ eight minutes on the same plan, close enough to the runner's ten-minute limit that you should start such a run
94
+ in the background and tell the operator it is under way. A machine that pins its own Codex effort in its
95
+ configuration is overridden by `medium`; set `CTXR_SECOND_OPINION_CODEX_EFFORT` to use another.
96
+
97
+ ## 4. Synthesize on the letters first
98
+
99
+ Read the `critique-A`, `critique-B` and `critique-C` files and write your synthesis before you open
100
+ `manifest.json`. You share a model family with one of the reviewers, and models rate their own family's
101
+ writing higher. Reading letters first is what keeps that from steering you. Then open the manifest and
102
+ attach roles and models to the letters in the final report.
103
+
104
+ Write it in this order:
105
+
106
+ - **Verdicts.** One line per letter, and any blind or failed reviewer stated plainly, not buried.
107
+ - **Consensus (all agree).** Highest signal. When all three independently flag the same simplification,
108
+ take it whole; do not keep a reduced version of complexity all three called wrong.
109
+ - **Majority (two of three).** Say whether the two agree on the fix or only on the diagnosis. Two
110
+ reviewers who name the same problem often propose different repairs: choose on merit, meaning which
111
+ repair keeps more of what you want (granular rollback, fewer side effects, smaller blast radius), and do
112
+ not default to the majority's.
113
+ - **Evidence-backed dissent.** A lone finding that cites the plan gets its own section and stays there. The
114
+ minority is right often enough that overruling it on your own authority is a net loss. Overrule only by
115
+ showing what the dissent missed.
116
+ - **Vetoes.** A REJECT that carries a critical finding is stated as a veto, first, with its evidence.
117
+ - **Revised plan**, and **what changed and why**: each change tied to the letter and finding that drove it.
118
+ - **Recommendation**: proceed with the revision, revise further, or stop.
119
+
120
+ Where the reviewers pull apart on ceremony, one wanting more safeguards and another fewer, decide by blast
121
+ radius. If being wrong is expensive to undo, side with more safeguards. If it is cheap to undo, side with
122
+ fewer. Where a safeguard needs machinery an earlier round already turned down (a background job, a state file
123
+ kept across sessions, a hook), side with fewer: adding it now defeats the earlier decision.
124
+
125
+ When you have critiqued this plan before, reread that synthesis first. The reviewers only see the current text
126
+ and drift toward their own local optimum. Name any proposal that reverses an earlier decision, and hold the
127
+ earlier line unless the new critique brings evidence the old one lacked.
128
+
129
+ ## 5. Say what you are biased about, then stop
130
+
131
+ State in the report that one reviewer shares your model family, and say when that reviewer's finding is what
132
+ drove the recommendation. Name any reviewer that ran a different model from the one you expected: the
133
+ manifest records the model each tool reported.
134
+
135
+ Then hand the synthesis to the operator and stop. This skill never executes a step of the plan it critiqued.
136
+ If the operator says to go, proceed with the revised plan. If they push back, treat that as a fourth critique
137
+ and run the skill again on the changed plan. It is a new run and not a continuation.
138
+
139
+ To record the outcome in the plan itself, add a section near the top headed `## Revisions after critique`
140
+ that says it overrides anything inconsistent below, one numbered item per change, each naming the finding
141
+ behind it. Leave the original text in place as the record of what was reviewed. Run one more critique after a
142
+ plan has been substantially rewritten and before you carry it out: new wording creates new gaps. Do not rerun
143
+ for a typo.
144
+
145
+ ## 6. Run a poll
146
+
147
+ Write the question and the options to a file, then:
148
+
149
+ ```
150
+ node __SKILLS_PATH__/ctxr-second-opinion/run.mjs --mode poll --prompt-file <question>
151
+ ```
152
+
153
+ The same question goes to all three models, each answering with a pick, reasons, and anything surprising.
154
+ This mode names its files by model, because what you are reading is whether the models agree.
155
+
156
+ To get breadth as well as model diversity, add `--lens` with two or three taste lenses. Each model then
157
+ answers as every lens in one response, giving a lens-by-model grid. The built-in lenses are minimalist,
158
+ marketer, contrarian, end-user and brand-strategist. A store's own lens goes in as `Name:description`, for
159
+ example `--lens "minimalist,Plainspoken:blunt, substance first, distrusts slogans"`. A description may contain
160
+ commas: a comma starts the next lens only when what follows is a built-in lens name or begins `Name:`. So a
161
+ description must not contain a colon. Choose lenses that pull in different
162
+ directions; if two would obviously agree, drop one.
163
+
164
+ Read a lens grid on both axes. Down a column (one lens across the three models) agreement means that
165
+ taste value is robust to the model. Across a row (one model across the lenses) disagreement is where the
166
+ real decision is. Give your own recommendation and say which axis it rests on.
167
+
168
+ Report the answers, where they agree, where they split, and your read. The answers are evidence and not a
169
+ vote: one dissent with a substantive reason can outweigh two agreements. If two of the three agreeing models
170
+ include your own family, say so.
171
+
172
+ ## 7. When something goes wrong
173
+
174
+ - A reviewer that timed out, exited non-zero, or returned no verdict is `failed` and carries its cause in
175
+ the manifest. Report it. Do not rerun that one reviewer on a different model to fill the gap.
176
+ - Do not poll from inside a critique to settle a quick doubt. If you want a gut check mid-critique, answer it
177
+ yourself.
178
+ - Model names change faster than releases. When a default is stale, set
179
+ `CTXR_SECOND_OPINION_CLAUDE_MODEL`, `CTXR_SECOND_OPINION_CLAUDE_EFFORT`, `CTXR_SECOND_OPINION_CODEX_EFFORT`
180
+ or `CTXR_SECOND_OPINION_AGY_MODEL` in the environment for the run. Nothing about the lineup lives in the store's configuration.
181
+ - The scratch files under the output directory hold each reviewer's raw output and are safe to delete when the
182
+ operator has the synthesis. They can contain the plan, so do not commit them.
@@ -42,8 +42,9 @@ self-contradicting document nobody reads. Propose one only when all four hold:
42
42
  - It is a rule for future work, not a fact about past work.
43
43
  - It applied more than once, or the user stated it as a correction.
44
44
  - It is store-wide. A rule about one folder belongs in that folder's `README.md`, not here.
45
- - It is not already in the shipped baseline — read that file first. Restating contexture's own
46
- behavior as a house rule is the most likely way to get this wrong.
45
+ - It is not already in the shipped baseline, the first block of `AGENTS.md`'s "Store conventions"
46
+ section — read it first. Restating contexture's own behavior as a house rule is the most likely way
47
+ to get this wrong.
47
48
 
48
49
  Propose REMOVALS on the same bar: a convention the session contradicted, or that no longer matches how
49
50
  work actually goes, is worse than none — it teaches the next agent something false. The inlined section