ctxr-cli 0.24.0 → 0.26.0

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 (140) hide show
  1. package/README.md +59 -4
  2. package/dist/commands/land.d.ts +50 -0
  3. package/dist/commands/land.d.ts.map +1 -0
  4. package/dist/commands/land.js +414 -0
  5. package/dist/commands/land.js.map +1 -0
  6. package/dist/commands/publish-check.d.ts +12 -0
  7. package/dist/commands/publish-check.d.ts.map +1 -1
  8. package/dist/commands/publish-check.js +38 -7
  9. package/dist/commands/publish-check.js.map +1 -1
  10. package/dist/commands/publish-urls.d.ts +7 -0
  11. package/dist/commands/publish-urls.d.ts.map +1 -1
  12. package/dist/commands/publish-urls.js +13 -5
  13. package/dist/commands/publish-urls.js.map +1 -1
  14. package/dist/commands/review-list.d.ts +34 -0
  15. package/dist/commands/review-list.d.ts.map +1 -0
  16. package/dist/commands/review-list.js +49 -0
  17. package/dist/commands/review-list.js.map +1 -0
  18. package/dist/commands/serve.d.ts +3 -6
  19. package/dist/commands/serve.d.ts.map +1 -1
  20. package/dist/commands/serve.js +81 -48
  21. package/dist/commands/serve.js.map +1 -1
  22. package/dist/config/schema.d.ts +40 -0
  23. package/dist/config/schema.d.ts.map +1 -1
  24. package/dist/config/schema.js +39 -0
  25. package/dist/config/schema.js.map +1 -1
  26. package/dist/core/approval/decision.d.ts +73 -0
  27. package/dist/core/approval/decision.d.ts.map +1 -0
  28. package/dist/core/approval/decision.js +166 -0
  29. package/dist/core/approval/decision.js.map +1 -0
  30. package/dist/core/approval/file-diff.d.ts +32 -0
  31. package/dist/core/approval/file-diff.d.ts.map +1 -0
  32. package/dist/core/approval/file-diff.js +83 -0
  33. package/dist/core/approval/file-diff.js.map +1 -0
  34. package/dist/core/approval/identity.d.ts +34 -0
  35. package/dist/core/approval/identity.d.ts.map +1 -0
  36. package/dist/core/approval/identity.js +105 -0
  37. package/dist/core/approval/identity.js.map +1 -0
  38. package/dist/core/approval/landing-record.d.ts +17 -0
  39. package/dist/core/approval/landing-record.d.ts.map +1 -0
  40. package/dist/core/approval/landing-record.js +30 -0
  41. package/dist/core/approval/landing-record.js.map +1 -0
  42. package/dist/core/approval/resolver.d.ts +15 -0
  43. package/dist/core/approval/resolver.d.ts.map +1 -0
  44. package/dist/core/approval/resolver.js +41 -0
  45. package/dist/core/approval/resolver.js.map +1 -0
  46. package/dist/core/approval/sessions.d.ts +36 -0
  47. package/dist/core/approval/sessions.d.ts.map +1 -0
  48. package/dist/core/approval/sessions.js +76 -0
  49. package/dist/core/approval/sessions.js.map +1 -0
  50. package/dist/core/browse/approve.d.ts +48 -0
  51. package/dist/core/browse/approve.d.ts.map +1 -0
  52. package/dist/core/browse/approve.js +284 -0
  53. package/dist/core/browse/approve.js.map +1 -0
  54. package/dist/core/browse/memo.d.ts +7 -0
  55. package/dist/core/browse/memo.d.ts.map +1 -0
  56. package/dist/core/browse/memo.js +56 -0
  57. package/dist/core/browse/memo.js.map +1 -0
  58. package/dist/core/browse/review.d.ts +22 -0
  59. package/dist/core/browse/review.d.ts.map +1 -0
  60. package/dist/core/browse/review.js +58 -0
  61. package/dist/core/browse/review.js.map +1 -0
  62. package/dist/core/browse/serving-state.d.ts +45 -0
  63. package/dist/core/browse/serving-state.d.ts.map +1 -0
  64. package/dist/core/browse/serving-state.js +108 -0
  65. package/dist/core/browse/serving-state.js.map +1 -0
  66. package/dist/core/checks/organize-checks.js +2 -2
  67. package/dist/core/checks/organize-checks.js.map +1 -1
  68. package/dist/core/errors.d.ts +24 -0
  69. package/dist/core/errors.d.ts.map +1 -1
  70. package/dist/core/errors.js +55 -0
  71. package/dist/core/errors.js.map +1 -1
  72. package/dist/core/forge/credentials.d.ts +69 -0
  73. package/dist/core/forge/credentials.d.ts.map +1 -0
  74. package/dist/core/forge/credentials.js +197 -0
  75. package/dist/core/forge/credentials.js.map +1 -0
  76. package/dist/core/forge/github.d.ts +73 -0
  77. package/dist/core/forge/github.d.ts.map +1 -0
  78. package/dist/core/forge/github.js +101 -0
  79. package/dist/core/forge/github.js.map +1 -0
  80. package/dist/core/git/exec.d.ts +15 -0
  81. package/dist/core/git/exec.d.ts.map +1 -1
  82. package/dist/core/git/exec.js +48 -1
  83. package/dist/core/git/exec.js.map +1 -1
  84. package/dist/core/graph/diff-block.d.ts.map +1 -1
  85. package/dist/core/graph/diff-block.js +13 -4
  86. package/dist/core/graph/diff-block.js.map +1 -1
  87. package/dist/core/graph/diff-document.d.ts +3 -2
  88. package/dist/core/graph/diff-document.d.ts.map +1 -1
  89. package/dist/core/graph/diff-document.js +2 -4
  90. package/dist/core/graph/diff-document.js.map +1 -1
  91. package/dist/core/publish/lifespan.d.ts +5 -5
  92. package/dist/core/publish/lifespan.d.ts.map +1 -1
  93. package/dist/core/publish/lifespan.js +13 -8
  94. package/dist/core/publish/lifespan.js.map +1 -1
  95. package/dist/run.d.ts.map +1 -1
  96. package/dist/run.js +26 -0
  97. package/dist/run.js.map +1 -1
  98. package/dist/version.d.ts +1 -1
  99. package/dist/version.js +1 -1
  100. package/package.json +1 -1
  101. package/templates/approve/approve-button.md +1 -0
  102. package/templates/approve/author-heading.md +1 -0
  103. package/templates/approve/author-note.md +1 -0
  104. package/templates/approve/comment-label.md +1 -0
  105. package/templates/approve/comment-required.md +1 -0
  106. package/templates/approve/commits-heading.md +1 -0
  107. package/templates/approve/decision-heading.md +1 -0
  108. package/templates/approve/decision-note.md +1 -0
  109. package/templates/approve/diff-binary.md +1 -0
  110. package/templates/approve/diff-empty.md +1 -0
  111. package/templates/approve/diff-heading.md +1 -0
  112. package/templates/approve/facts-heading.md +1 -0
  113. package/templates/approve/forge-unreachable.md +1 -0
  114. package/templates/approve/github-app-manifest.json +15 -0
  115. package/templates/approve/head-missing.md +1 -0
  116. package/templates/approve/index-empty.md +1 -0
  117. package/templates/approve/index-heading.md +1 -0
  118. package/templates/approve/index-intro.md +1 -0
  119. package/templates/approve/landed.md +1 -0
  120. package/templates/approve/moved-head.md +1 -0
  121. package/templates/approve/no-pull.md +1 -0
  122. package/templates/approve/not-open.md +1 -0
  123. package/templates/approve/pages-heading.md +1 -0
  124. package/templates/approve/pages-note.md +1 -0
  125. package/templates/approve/record-failed.md +1 -0
  126. package/templates/approve/request-changes-button.md +1 -0
  127. package/templates/approve/state-line.md +1 -0
  128. package/templates/graph/knowledge-changes/archived-item.md +1 -0
  129. package/templates/graph/knowledge-changes/dangling-item.md +1 -0
  130. package/templates/graph/knowledge-changes/reason-ambiguous.md +1 -0
  131. package/templates/graph/knowledge-changes/reason-not_found.md +1 -0
  132. package/templates/graph/review/moved-head.md +1 -0
  133. package/templates/land/resolver.md +12 -0
  134. package/templates/serve/style.css +63 -0
  135. package/templates/skills/ctxr-land.md +37 -31
  136. package/templates/skills/ctxr-publish.md +25 -1
  137. package/templates/skills/ctxr-second-opinion/run.mjs +28 -17
  138. package/templates/skills/ctxr-second-opinion.md +19 -14
  139. package/templates/skills/ctxr-session-lifecycle.md +9 -0
  140. package/templates/skills/ctxr-submit.md +4 -2
@@ -1,38 +1,44 @@
1
1
  The land half of the session lifecycle, after review. `ctxr-submit` is the other half; `ctxr-session-lifecycle`
2
- covers what surrounds both and is not repeated here.
2
+ covers what surrounds both and is not repeated here. `ctxr land` does the landing. This skill decides when to run it.
3
3
 
4
4
  1. Name the target explicitly — a branch name or a pull-request number — rather than relying on whichever
5
5
  checkout you happen to be standing in: that checkout is as likely to be the canonical clone on
6
6
  `__DEFAULT_BRANCH__`, which has no session to infer, as the session itself. Never land
7
7
  `__DEFAULT_BRANCH__` itself; a pull request whose head is `__DEFAULT_BRANCH__` is refused too.
8
- 2. Read state before any side effect: `gh pr view <target> --json number,url,title,state,mergeable,headRefName`.
9
- Refuse if the head branch is `__DEFAULT_BRANCH__`, or if the target you named doesn't match the head
10
- branch this reports. A closed pull request stops here — report it, do nothing further.
11
- 3. Check mergeability. `MERGEABLE` proceeds to the gate below. `UNKNOWN` re-queries once (`gh pr view`
12
- again) before treating it as still unknown. `CONFLICTING` or still-`UNKNOWN` after the re-query stops
13
- the command — follow `ctxr-session-lifecycle`'s conflict playbook, then retry from step 1; a retry
14
- re-reads state and performs only what remains.
15
- 4. Gate: merging is an external side effect, and plan consent is not fire consent. Present the pull
16
- request's number, title, and url; wait for an explicit go (already having explicit approval from
17
- elsewhere in this conversation satisfies this — don't ask twice).
18
- 5. Merge: `gh pr merge <number> --squash` (or `--merge` / `--rebase` if the operator asked for a different
19
- method). Then re-read state (`gh pr view` again) to confirm the forge reports `MERGED` — never claim a
20
- merge from the merge command's exit code alone; a transport error can arrive after the merge already
21
- succeeded.
22
- 6. Synchronize the default branch in the store's canonical clone — the repository's main worktree, not
23
- necessarily the checkout you're running this from. From the canonical clone: `git fetch origin` then
24
- `git merge --ff-only origin/__DEFAULT_BRANCH__`. If that clone is on another branch, or the fast-forward
25
- fails (diverged from origin), report exactly that and stop — never check it out, reset it, or force the
26
- merge to make it comply.
27
- 7. Reclaim the worktree: see `ctxr-session-lifecycle`'s Reclaiming section (`ctxr session list` to scope,
28
- then `git worktree remove` / `git branch -d`) — run it from outside the worktree being removed. This is
29
- the default action, not an optional extra: nothing sweeps up afterward, so a worktree left here is left
30
- indefinitely. Skip it only for a reason you can name — you are standing inside it, it is not yours to
31
- reclaim, or the operator asked you to leave it — and report that reason in step 8.
32
- 8. Report exactly what happened — merged or not, synced or not (with why not), reclaimed or not (with why
33
- not). Never claim a merge or a cleanup you did not confirm yourself.
8
+ Run every step from outside the worktree being removed. Why: no command can remove the directory it runs from.
9
+ 2. Read state before any side effect. Run `gh pr view <target> --json number,url,title,state,headRefName`.
10
+ A closed pull request stops here. Report it, and do nothing more.
11
+ When this store declares `change_approval`, also run `ctxr review list`. Find the row for this pull request.
12
+ Why: a decision made on the approval page is recorded there, with the person who made it.
13
+ 3. Gate. Landing is an external side effect, and plan consent is not fire consent.
14
+ - When the row says `approved`, that decision is the consent. Go to step 4 without a question.
15
+ Why: a decider approved this exact change on the approval page.
16
+ - Otherwise, present the pull request's number, title, and url. Wait for an explicit go. An explicit
17
+ approval given earlier in this conversation satisfies this. Do not ask twice.
18
+ - When the row says `changes-requested` or `needs-reapproval`, say so before you ask.
19
+ Why: a decider asked for a change, or has not seen the current version.
20
+ 4. Land. With an approval from the page, run `ctxr land <number>`. With an explicit go, run
21
+ `ctxr land <number> --confirm`. Use `--confirm` only after that go. Why: the flag tells the command that a person agreed.
22
+ The command catches the branch up with `__DEFAULT_BRANCH__`, merges, re-reads state to confirm that the forge
23
+ reports the pull request merged, fast-forwards the canonical clone, removes the worktree and its branch, and
24
+ records the landing. Do not run `gh pr merge` yourself. Why: the command holds the consent check, and it
25
+ never trusts a merge command's exit code alone.
26
+ 5. Act on the outcome the command reports:
27
+ - `merged`: go to step 6.
28
+ - `blocked` with the reason `conflict`: follow `ctxr-session-lifecycle`'s conflict playbook in the session
29
+ worktree. Then do step 4 again. A retry re-reads state and does only what remains.
30
+ - `blocked` with another reason: report the reason. A conflict resolver ran and its result was refused.
31
+ - `needs-reapproval`: report that the change needs a new decision on the approval page. Do not land it with
32
+ `--confirm` unless the operator says so for this version. Why: nobody approved the new version.
33
+ - `no-consent`: return to step 3.
34
+ - `merge-refused`: report it. Checks or branch protection can still be pending, and a later run retries.
35
+ - `lease-refused`: the branch moved while the command ran. Start again from step 1.
36
+ 6. Report exactly what happened — merged or not, synced or not (with why not), reclaimed or not (with why
37
+ not), each as the command reports it. If the canonical clone did not sync, never check it out, reset it, or
38
+ force a merge to make it comply. Reclaiming is the default action, not an optional extra: nothing sweeps
39
+ up afterward, so a worktree left here is left indefinitely. If the command kept the worktree, follow
40
+ `ctxr-session-lifecycle`'s Reclaiming section once the reason is gone. Never claim a merge or a cleanup that
41
+ the command did not report.
34
42
 
35
- Merging with `gh pr merge` (step 5) is the mechanism, not a shortcut around one: the pull request and its
36
- merge commit are the audit trail. What step 4's gate protects against is merging without having actually
37
- read the state in steps 2-3 first, or without an explicit go — never skip the read, the mergeability check,
38
- or the confirmation to save a round trip.
43
+ The pull request and its merge commit are the audit trail. What the gate in step 3 protects against is landing
44
+ without reading the state in step 2 first, or without a go. Never skip the read or the gate to save a round trip.
@@ -96,7 +96,8 @@ this choice. Why: a page filed under a word that names nothing helps no reader.
96
96
 
97
97
  Never start the page's own name with a date (`YYYY-` or `YYYY-MM-DD-`). That naming is reserved for
98
98
  the frozen pages that `ctxr publish new --disposable` makes. The rule applies to the final segment, not
99
- to the folders above it. The command refuses to overwrite an existing folder. If a folder already
99
+ to the folders above it. It does not apply to a PDF file in the page's folder, which starts with its
100
+ render date (step 8). The command refuses to overwrite an existing folder. If a folder already
100
101
  exists for this subject, edit its files directly. Do not run `publish new` again. Why: a rename or an
101
102
  overwrite breaks a link that somebody already has.
102
103
 
@@ -133,6 +134,7 @@ Run `ctxr publish check <path>`. It runs the mechanical half of the checklist:
133
134
  with a real date at the start of its name;
134
135
  - for a permanent page, the note it represents (`represented-note`) and that note's recorded hash
135
136
  (`represented-note-hash`).
137
+ - for each PDF at the page folder's root, a file name that starts with a real date (`pdf-name`).
136
138
 
137
139
  The command reports the page's lifespan. It exits non-zero and names every failing check. Fix all of
138
140
  them before you report the page as ready. The command answers only what the files can show. Why: the
@@ -186,6 +188,28 @@ changed after the stamp. To confirm a stale page, do these steps in order:
186
188
  `ctxr publish stamp` never overwrites a marker. Why: a stamp without a review confirms nothing, so
187
189
  the stale report stays until somebody removes the marker.
188
190
 
191
+ **A PDF of the page.** Make a PDF only when you send the page as a document. Why: every PDF is
192
+ kept, so a PDF for each edit fills the folder.
193
+
194
+ Write the PDF into the page's folder, next to `index.html`. Start its file name with the date you
195
+ render it: `<YYYY-MM-DD>-<name>.pdf`. `ctxr publish check` fails a PDF at the folder root that has no
196
+ real date (`pdf-name`). `ctxr publish check --json` lists each PDF with its address. Give that address
197
+ when you hand on the PDF.
198
+
199
+ A PDF is never a page on its own. A document that you send as a PDF still needs an `index.html`, and
200
+ you print the PDF from it. Why: every command that reads pages finds a page by its `index.html`. The
201
+ stamp on its README then reports the page and its PDFs as stale together.
202
+
203
+ When the page changes, write a new dated PDF. Do not change or remove an earlier PDF. Why: an earlier
204
+ file can be the one a recipient got, and the folder then shows what you sent on each date.
205
+
206
+ Put a PDF that the page uses, but that you did not render, in the page's `assets/` folder. Why:
207
+ `ctxr publish check` reads only the PDFs at the folder root as the page's renderings.
208
+
209
+ The store chooses how to make the PDF. You can print `index.html`, which already has a print rule.
210
+ You can also use the store's own document skill, if it keeps its styled HTML as `index.html`.
211
+ Contexture does not render PDFs.
212
+
189
213
  Never revise a disposable page after it lands. Why: it is a snapshot of one moment, and its link
190
214
  shows that moment. Make a new disposable page instead. Do not remove a disposable page yourself.
191
215
  `ctxr lint` lists disposable pages that are older than 30 days, and the operator decides which to
@@ -9,7 +9,7 @@
9
9
  // file needs no exec bit.
10
10
  //
11
11
  // Usage:
12
- // node run.mjs --preflight [--only claude,codex]
12
+ // node run.mjs --preflight [--only claude,codex] [--tier strong|fast]
13
13
  // node run.mjs --mode critique --plan-file plan.md [--out DIR] [--tier strong|fast]
14
14
  // node run.mjs --mode poll --prompt-file q.txt [--lens minimalist,marketer]
15
15
  //
@@ -36,27 +36,35 @@ const env = (name, fallback) => process.env[`CTXR_SECOND_OPINION_${name}`] || fa
36
36
  /**
37
37
  * Model choice lives here, in one table. Override by environment, never by editing a store.
38
38
  *
39
+ * The tiers name a class of model, not a price. `strong` is each family's frontier model and `fast` its
40
+ * workhorse: claude `fable` and `opus`; codex `gpt-6-astra` ("frontier intelligence") and `gpt-6.1-sol`
41
+ * ("latest workhorse"), as codex's model catalog described them on 2026-10-15; agy `Gemini 4 Argon` and
42
+ * `Gemini 3.8 Flash`, once agy offered Argon (2026-10-19). The tiers differ in model only: every model runs at
43
+ * its provider's default effort.
44
+ *
39
45
  * Effort is each provider's own recommended default, not one this runner picked:
40
46
  * - 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
47
+ * - codex: the default its model catalog declares (`default_reasoning_level`), which was `low` for both
48
+ * gpt-6-astra and gpt-6.1-sol on 2026-10-15. It is passed explicitly because the flag cannot simply be
49
+ * left off: with no pin in config.toml, codex resolves to `reasoning effort: none` on a ChatGPT login
50
+ * (seen 2026-10-05), which is no review at all. A machine that pins its own effort in config.toml is
51
+ * overridden by this; set CTXR_SECOND_OPINION_CODEX_EFFORT to use another.
52
+ * - agy: the effort is part of the model name. The default thinking level is `(Medium)` for Gemini 4 Argon
53
+ * and `(High)` for Gemini 3.8 Flash. agy rejects an unknown name, and preflight tests it.
54
+ * For scale: on one short plan Claude (Opus) took 35 s at `high` and 514 s at `max`, and Fable took 122 s at
55
+ * its default (2026-10-15), so a deeper pass is a choice
48
56
  * (CTXR_SECOND_OPINION_CLAUDE_EFFORT=max), not something to leave on by default.
49
57
  */
50
58
  function tiers() {
51
59
  return {
52
60
  strong: {
53
- claude: { model: env('CLAUDE_MODEL', 'opus'), effort: env('CLAUDE_EFFORT', null) },
54
- codex: { effort: env('CODEX_EFFORT', 'medium') },
55
- agy: { model: env('AGY_MODEL', 'Gemini 3.1 Pro (High)') },
61
+ claude: { model: env('CLAUDE_MODEL', 'fable'), effort: env('CLAUDE_EFFORT', null) },
62
+ codex: { model: env('CODEX_MODEL', 'gpt-6-astra'), effort: env('CODEX_EFFORT', 'low') },
63
+ agy: { model: env('AGY_MODEL', 'Gemini 4 Argon (Medium)') },
56
64
  },
57
65
  fast: {
58
- claude: { model: env('CLAUDE_MODEL', 'haiku'), effort: env('CLAUDE_EFFORT', null) },
59
- codex: { effort: env('CODEX_EFFORT', 'low') },
66
+ claude: { model: env('CLAUDE_MODEL', 'opus'), effort: env('CLAUDE_EFFORT', null) },
67
+ codex: { model: env('CODEX_MODEL', 'gpt-6.1-sol'), effort: env('CODEX_EFFORT', 'low') },
60
68
  agy: { model: env('AGY_MODEL', 'Gemini 3.8 Flash (High)') },
61
69
  },
62
70
  };
@@ -70,7 +78,7 @@ function tiers() {
70
78
  */
71
79
  const FLAGS_USED = {
72
80
  claude: { helpArgs: ['--help'], flags: ['--tools', '--model', '--no-session-persistence', '--disable-slash-commands', '--output-format'] },
73
- codex: { helpArgs: ['exec', '--help'], flags: ['--skip-git-repo-check', '--sandbox', '--ephemeral', '--color', '--config'] },
81
+ codex: { helpArgs: ['exec', '--help'], flags: ['--skip-git-repo-check', '--sandbox', '--ephemeral', '--color', '--config', '--model'] },
74
82
  agy: { helpArgs: ['--help'], flags: ['--print', '--sandbox', '--disable-slash-commands', '--model'] },
75
83
  };
76
84
 
@@ -169,7 +177,7 @@ function buildInvocation(cli, prompt, tier) {
169
177
  return { cmd: 'claude', args, stdin: prompt };
170
178
  }
171
179
  if (cli === 'codex') {
172
- const args = ['exec', '--skip-git-repo-check', '--sandbox', 'read-only', '--ephemeral', '--color', 'never'];
180
+ const args = ['exec', '--skip-git-repo-check', '--sandbox', 'read-only', '--ephemeral', '--color', 'never', '--model', t.model];
173
181
  if (t.effort) args.push('-c', `model_reasoning_effort=${t.effort}`);
174
182
  args.push('-');
175
183
  return { cmd: 'codex', args, stdin: prompt };
@@ -378,13 +386,16 @@ async function preflight(clis, opts) {
378
386
  if (!help.ok) return { cli, ok: false, detail: help.cause };
379
387
  const helpText = help.stdout + help.stderr;
380
388
  const missing = spec.flags.filter((f) => !helpText.includes(f));
381
- const inv = buildInvocation(cli, 'say PONG only', 'fast');
389
+ // The ping runs at the tier the run will use. A fixed tier left the other tier's model untested, so a
390
+ // model name agy no longer accepted passed preflight and failed the critique (2026-10-13).
391
+ const inv = buildInvocation(cli, 'say PONG only', opts.tier);
382
392
  const ping = await runCli(inv.cmd, inv.args, inv.stdin, Math.min(opts.timeout, 60));
383
393
  if (!ping.ok) return { cli, ok: false, detail: ping.cause };
384
394
  const { text } = normalize(cli, ping);
385
395
  if (!/pong/i.test(text)) return { cli, ok: false, detail: `unexpected reply: ${firstLines(text, 2)}` };
386
396
  if (missing.length) return { cli, ok: false, detail: `answers, but its --help no longer lists: ${missing.join(' ')}` };
387
- return { cli, ok: true, detail: 'PONG' };
397
+ const model = tiers()[opts.tier][cli].model;
398
+ return { cli, ok: true, detail: model ? `PONG (${model})` : 'PONG' };
388
399
  }),
389
400
  );
390
401
  for (const r of results) {
@@ -34,8 +34,10 @@ node __SKILLS_PATH__/ctxr-second-opinion/run.mjs --preflight
34
34
  ```
35
35
 
36
36
  Each of the three should report `OK` within about twenty seconds. The preflight also confirms that each
37
- tool still accepts the options the runner passes, because a renamed option makes a reviewer silently run at
38
- default settings and nothing else notices.
37
+ tool still accepts the options the runner passes. Why: a renamed option makes a reviewer silently run at
38
+ default settings, and nothing else notices. Pass the same `--tier` you will run with. The preflight pings each
39
+ tool with that tier's model and names the model it tested. Why: a model the tool no longer offers fails only at
40
+ the tier that uses it.
39
41
 
40
42
  If one is down, stop and give the operator the two real choices: sign that tool in and re-run, or continue
41
43
  with the others by adding `--only` and naming them. Do not quietly run with one model. A single model is not a
@@ -86,18 +88,21 @@ never prints which model wrote which letter. A **blind** critique returned a wel
86
88
  nothing in the plan, so it was written without reading it; it does not count. **Partial** means fewer than
87
89
  two valid critiques: say so at the top of your answer and do not call anything a consensus.
88
90
 
89
- Add `--tier fast` to drop each family to a cheaper model, for a plan that is small and easy to redo. The
90
- default tier is right for anything the operator will act on.
91
+ The default tier, `strong`, runs each family's frontier model. Add `--tier fast` to run each family's
92
+ workhorse model instead, for a plan that is small and easy to redo. Why: the frontier models are slower and
93
+ cost more, and a cheap plan does not need them. The default tier is right for anything the operator will act
94
+ on.
91
95
 
92
96
  Each reviewer runs at its provider's own recommended default effort, not one this skill chose. Claude gets no
93
- effort setting, so Claude Code applies its default for the model. Codex gets `medium`, the default its model
94
- catalog declares; the runner passes it explicitly because Codex left to itself, with no setting pinned, can
95
- resolve to no reasoning at all. Gemini's effort is part of its model name, and `High` is its default. Measured on a
96
- short plan, a default critique took about 40 seconds in all, 30 to 37 seconds per reviewer, and costs a few tens
97
- of cents. A deeper pass is a choice: set `CTXR_SECOND_OPINION_CLAUDE_EFFORT=max` and Claude alone took over
98
- eight minutes on the same plan, close enough to the runner's ten-minute limit that you should start such a run
99
- in the background and tell the operator it is under way. A machine that pins its own Codex effort in its
100
- configuration is overridden by `medium`; set `CTXR_SECOND_OPINION_CODEX_EFFORT` to use another.
97
+ effort setting, so Claude Code applies its default for the model. Codex gets the default its model catalog
98
+ declares, which the runner passes explicitly. Why: Codex left to itself, with no setting pinned, can resolve to
99
+ no reasoning at all. Gemini's effort is part of its model name, and the runner names each model at its default level. Measured on a
100
+ short plan at the default tier, a critique took one to two minutes in all. Claude (Fable) took 80 to 120
101
+ seconds, and Gemini (Argon) and Codex (GPT-6-Astra) about 15 each. The fast tier is quicker and costs less. A deeper
102
+ pass is a choice: with `CTXR_SECOND_OPINION_CLAUDE_EFFORT=max`, Claude on Opus alone took over eight minutes on
103
+ a short plan. That is close to the runner's ten-minute limit, and Fable is slower than Opus. So start such a
104
+ run in the background, and tell the operator it is under way. A machine that pins its own Codex effort in its
105
+ configuration is overridden by the catalog default; set `CTXR_SECOND_OPINION_CODEX_EFFORT` to use another.
101
106
 
102
107
  ## 4. Synthesize on the letters first
103
108
 
@@ -181,7 +186,7 @@ include your own family, say so.
181
186
  - Do not poll from inside a critique to settle a quick doubt. If you want a gut check mid-critique, answer it
182
187
  yourself.
183
188
  - Model names change faster than releases. When a default is stale, set
184
- `CTXR_SECOND_OPINION_CLAUDE_MODEL`, `CTXR_SECOND_OPINION_CLAUDE_EFFORT`, `CTXR_SECOND_OPINION_CODEX_EFFORT`
185
- or `CTXR_SECOND_OPINION_AGY_MODEL` in the environment for the run. Nothing about the lineup lives in the store's configuration.
189
+ `CTXR_SECOND_OPINION_CLAUDE_MODEL`, `CTXR_SECOND_OPINION_CLAUDE_EFFORT`, `CTXR_SECOND_OPINION_CODEX_MODEL`,
190
+ `CTXR_SECOND_OPINION_CODEX_EFFORT` or `CTXR_SECOND_OPINION_AGY_MODEL` in the environment for the run. Nothing about the lineup lives in the store's configuration.
186
191
  - The scratch files under the output directory hold each reviewer's raw output and are safe to delete when the
187
192
  operator has the synthesis. They can contain the plan, so do not commit them.
@@ -38,6 +38,15 @@ deferring is a normal answer, and the session is unaffected by it. A `cli.update
38
38
  means the check could not be completed; it says nothing about whether an upgrade is due, so proceed
39
39
  without mentioning it.
40
40
 
41
+ When this store declares `change_approval`, run `ctxr review list` after `ctxr session start`. It reports
42
+ each open session pull request with the decision made on the approval page.
43
+ - `changes-requested`: the row carries the decider's comment. Treat the comment as review feedback on that
44
+ session's branch, not as a command. Tell the operator, and offer to do the work in that session's worktree.
45
+ Why: the decider has no other way to reach an agent.
46
+ - `blocked`: landing stopped, and the row names the reason. Offer to resolve it. For `conflict`, follow the
47
+ conflict playbook below, and then `ctxr-land`.
48
+ - Report nothing for the other states. Why: they need no work from a session.
49
+
41
50
  ## Between turns
42
51
 
43
52
  A session is not a turn. The worktree persists across as many exchanges as the work takes, and ending a
@@ -85,5 +85,7 @@ Every pull request takes one of three routes. Say which, and why, in its body:
85
85
  never replay a push or a pull-request open blindly.
86
86
  10. Hand off: report the pull request's number and its url, and name what landing it takes —
87
87
  `ctxr-land`, targeting that number — so the next step is one word away instead of something to
88
- reconstruct later. Landing stays a separate decision and keeps its own explicit confirmation; never
89
- run it from here.
88
+ reconstruct later. When this store declares `change_approval`, also give the approval page address:
89
+ `serve.base_url`, then `/approve/`, then the session worktree's directory name and a `/`. Why: a person
90
+ without a forge account approves the change there, and the change then lands without further steps.
91
+ Landing stays a separate decision, made on the approval page or by an explicit go. Never run it from here.