ctxr-cli 0.20.0 → 0.21.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 (110) hide show
  1. package/README.md +55 -16
  2. package/dist/adapters/builtin/index.d.ts.map +1 -1
  3. package/dist/adapters/builtin/index.js +8 -1
  4. package/dist/adapters/builtin/index.js.map +1 -1
  5. package/dist/adapters/harness/antigravity.d.ts +18 -0
  6. package/dist/adapters/harness/antigravity.d.ts.map +1 -0
  7. package/dist/adapters/harness/antigravity.js +24 -0
  8. package/dist/adapters/harness/antigravity.js.map +1 -0
  9. package/dist/adapters/harness/codex.d.ts +22 -0
  10. package/dist/adapters/harness/codex.d.ts.map +1 -0
  11. package/dist/adapters/harness/codex.js +28 -0
  12. package/dist/adapters/harness/codex.js.map +1 -0
  13. package/dist/adapters/types.d.ts +9 -0
  14. package/dist/adapters/types.d.ts.map +1 -1
  15. package/dist/commands/adapters-generate.d.ts +2 -14
  16. package/dist/commands/adapters-generate.d.ts.map +1 -1
  17. package/dist/commands/adapters-generate.js +2 -50
  18. package/dist/commands/adapters-generate.js.map +1 -1
  19. package/dist/commands/ingest.d.ts.map +1 -1
  20. package/dist/commands/ingest.js +13 -1
  21. package/dist/commands/ingest.js.map +1 -1
  22. package/dist/commands/init.d.ts +2 -0
  23. package/dist/commands/init.d.ts.map +1 -1
  24. package/dist/commands/init.js +32 -3
  25. package/dist/commands/init.js.map +1 -1
  26. package/dist/commands/publish-urls.d.ts +32 -0
  27. package/dist/commands/publish-urls.d.ts.map +1 -0
  28. package/dist/commands/publish-urls.js +125 -0
  29. package/dist/commands/publish-urls.js.map +1 -0
  30. package/dist/commands/update.d.ts.map +1 -1
  31. package/dist/commands/update.js +0 -3
  32. package/dist/commands/update.js.map +1 -1
  33. package/dist/config/defaults.d.ts +26 -0
  34. package/dist/config/defaults.d.ts.map +1 -1
  35. package/dist/config/defaults.js +25 -1
  36. package/dist/config/defaults.js.map +1 -1
  37. package/dist/config/schema.d.ts +19 -0
  38. package/dist/config/schema.d.ts.map +1 -1
  39. package/dist/config/schema.js +31 -0
  40. package/dist/config/schema.js.map +1 -1
  41. package/dist/core/adapter-outputs.d.ts +19 -0
  42. package/dist/core/adapter-outputs.d.ts.map +1 -0
  43. package/dist/core/adapter-outputs.js +54 -0
  44. package/dist/core/adapter-outputs.js.map +1 -0
  45. package/dist/core/agents-doc.d.ts +26 -0
  46. package/dist/core/agents-doc.d.ts.map +1 -1
  47. package/dist/core/agents-doc.js +95 -39
  48. package/dist/core/agents-doc.js.map +1 -1
  49. package/dist/core/checks/harness-portability-checks.d.ts +32 -0
  50. package/dist/core/checks/harness-portability-checks.d.ts.map +1 -1
  51. package/dist/core/checks/harness-portability-checks.js +177 -2
  52. package/dist/core/checks/harness-portability-checks.js.map +1 -1
  53. package/dist/core/checks/integrity-checks.d.ts.map +1 -1
  54. package/dist/core/checks/integrity-checks.js +34 -14
  55. package/dist/core/checks/integrity-checks.js.map +1 -1
  56. package/dist/core/checks/organize-checks.d.ts +5 -0
  57. package/dist/core/checks/organize-checks.d.ts.map +1 -1
  58. package/dist/core/checks/organize-checks.js +57 -4
  59. package/dist/core/checks/organize-checks.js.map +1 -1
  60. package/dist/core/convention-doc.d.ts.map +1 -1
  61. package/dist/core/convention-doc.js +5 -2
  62. package/dist/core/convention-doc.js.map +1 -1
  63. package/dist/core/conventions.d.ts +7 -0
  64. package/dist/core/conventions.d.ts.map +1 -1
  65. package/dist/core/conventions.js +3 -0
  66. package/dist/core/conventions.js.map +1 -1
  67. package/dist/core/errors.d.ts +16 -0
  68. package/dist/core/errors.d.ts.map +1 -1
  69. package/dist/core/errors.js +32 -0
  70. package/dist/core/errors.js.map +1 -1
  71. package/dist/core/ingest/required-sections.d.ts +3 -3
  72. package/dist/core/ingest/required-sections.d.ts.map +1 -1
  73. package/dist/core/ingest/required-sections.js +79 -16
  74. package/dist/core/ingest/required-sections.js.map +1 -1
  75. package/dist/core/notes/list.d.ts.map +1 -1
  76. package/dist/core/notes/list.js +3 -0
  77. package/dist/core/notes/list.js.map +1 -1
  78. package/dist/core/reconcile.d.ts +2 -1
  79. package/dist/core/reconcile.d.ts.map +1 -1
  80. package/dist/core/reconcile.js +10 -2
  81. package/dist/core/reconcile.js.map +1 -1
  82. package/dist/core/skills.d.ts.map +1 -1
  83. package/dist/core/skills.js +16 -5
  84. package/dist/core/skills.js.map +1 -1
  85. package/dist/run.d.ts.map +1 -1
  86. package/dist/run.js +14 -1
  87. package/dist/run.js.map +1 -1
  88. package/dist/version.d.ts +1 -1
  89. package/dist/version.js +1 -1
  90. package/package.json +3 -3
  91. package/templates/agents/canonical.md +3 -6
  92. package/templates/agents/capture-and-ingest.md +1 -36
  93. package/templates/agents/conventions.md +3 -1
  94. package/templates/agents/guidance-index.md +5 -0
  95. package/templates/agents/mission.md +2 -0
  96. package/templates/agents/retrieval-leg-routing.md +7 -36
  97. package/templates/conventions/baseline-conventions.md +20 -9
  98. package/templates/conventions/house-conventions-seed.md +10 -0
  99. package/templates/notes/Concept.md +2 -1
  100. package/templates/skills/ctxr-capture.md +30 -4
  101. package/templates/skills/ctxr-derived-artifacts.md +6 -0
  102. package/templates/skills/ctxr-ingest-orchestration.md +5 -1
  103. package/templates/skills/ctxr-mission.md +40 -31
  104. package/templates/skills/ctxr-organize-audit.md +6 -0
  105. package/templates/skills/ctxr-placement.md +5 -0
  106. package/templates/skills/ctxr-publish.md +18 -4
  107. package/templates/skills/ctxr-second-opinion/lenses.md +3 -1
  108. package/templates/skills/ctxr-second-opinion/run.mjs +39 -12
  109. package/templates/skills/ctxr-second-opinion.md +19 -6
  110. package/templates/skills/ctxr-submit.md +28 -4
@@ -31,6 +31,11 @@ clutter — worse than a slightly crowded existing one. Starting small and promo
31
31
  starting big and hollow is not. Make the promotion trigger a rule, not a vibe, and write it INTO the
32
32
  note: "Promote to its own top-level location the moment this holds 3+ distinct notes or topics."
33
33
 
34
+ A unit that will carry supporting notes (a project with its research, an exploration with its evidence)
35
+ gets a folder of its own. The graph clusters notes by their first __CLUSTER_DEPTH__ path segments, so
36
+ grouping such units under one common parent at that depth merges their clusters, and the graph then asserts
37
+ a relationship between them that does not exist.
38
+
34
39
  ## 4. What must never enter the store?
35
40
 
36
41
  Credentials, full account numbers, and secrets never enter the store — last-4 only. This is a placement
@@ -51,7 +51,8 @@ says something.
51
51
  When `gather` reports that an existing page belongs at the reported path too, it is because this
52
52
  subject is acquiring a second page, and a subject's pages collect under one folder naming it. Move
53
53
  that page there before you scaffold the new one, and tell the operator its URL changed — somebody
54
- may already be holding that link. `gather` moves nothing itself, and it names which signal counted
54
+ may already be holding that link. `ctxr publish urls` reports the old address beside the new one, and
55
+ `ctxr-submit` carries both into the pull request. `gather` moves nothing itself, and it names which signal counted
55
56
  the page as this subject's; read that before you move anything, because the signal is a reading of
56
57
  the page's README and can be wrong. Lead a page's source notes with the note it is a representation
57
58
  of — that lead link is what says whose page this is, and everything after it is a citation.
@@ -99,9 +100,17 @@ before reporting the page as ready. It answers only what's derivable from the fi
99
100
  in step 1, the form and reader choices in step 5, and factual accuracy stay judgment calls, not checker output.
100
101
 
101
102
  `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.
103
+ this page. Look at the page before you hand it on. Whether the form works, whether the prose reads,
104
+ whether it renders at all is exactly what the checks above cannot answer, and it is cheapest to fix
105
+ while the page is still yours.
106
+
107
+ That address may be served from another host, or behind an access proxy, where you cannot reach it.
108
+ You do not need it: from this machine, start `ctxr serve` in the background, open the address it
109
+ prints followed by the page's route in your browser tool, and stop the server when you are done. A
110
+ page still in a session worktree is served there under its previewable route, the same as anywhere
111
+ else. If you have no way to see a rendered page, say in your hand-off that it has not been checked
112
+ visually, so whoever reviews it knows they are the first to look. Never report a page as ready on the
113
+ strength of `ctxr publish check` alone.
105
114
 
106
115
  A page written in a session worktree is previewable rather than published, because the
107
116
  published-pages address reads the store's own checkout and the page has not reached it yet. That
@@ -110,6 +119,11 @@ previewable address stops answering the moment the worktree is reclaimed — the
110
119
  Report the address you actually opened, and say which of the two it is, so nobody is handed a link
111
120
  that is about to change.
112
121
 
122
+ Both addresses are full URLs when the store declares `serve.base_url` in `contexture.yaml`, and bare
123
+ routes such as `/preview/…` when it does not. A bare route is not a link anyone else can open. Do not
124
+ guess a host to put in front of it: hand on the route as it is, and tell the operator that declaring
125
+ `serve.base_url` would make every address a command reports a link.
126
+
113
127
  ## 7. It's a page, not a note
114
128
 
115
129
  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,19 +33,30 @@ 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
- // `high`, not `max`: the same two-and-a-half-thousand-character plan took Claude 35 s at `high` and 514 s at
41
- // `max` (and once timed out at 280 s), with a valid critique both times. `max` stays one variable away.
42
- claude: { model: env('CLAUDE_MODEL', 'opus'), effort: env('CLAUDE_EFFORT', 'high') },
43
- 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') },
44
55
  agy: { model: env('AGY_MODEL', 'Gemini 3.1 Pro (High)') },
45
56
  },
46
57
  fast: {
47
- claude: { model: env('CLAUDE_MODEL', 'haiku'), effort: null },
48
- 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') },
49
60
  agy: { model: env('AGY_MODEL', 'Gemini 3.8 Flash (High)') },
50
61
  },
51
62
  };
@@ -54,10 +65,11 @@ function tiers() {
54
65
  /**
55
66
  * The flags this runner passes, so preflight can confirm each CLI's --help still lists them. Only flags
56
67
  * the help text documents belong here: `claude` still accepts a hidden --max-turns, but with `--tools ""`
57
- * there is no tool turn to bound, so it is not passed at all.
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.
58
70
  */
59
71
  const FLAGS_USED = {
60
- claude: { helpArgs: ['--help'], flags: ['--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'] },
61
73
  codex: { helpArgs: ['exec', '--help'], flags: ['--skip-git-repo-check', '--sandbox', '--ephemeral', '--color', '--config'] },
62
74
  agy: { helpArgs: ['--help'], flags: ['--print', '--sandbox', '--disable-slash-commands', '--model'] },
63
75
  };
@@ -267,13 +279,28 @@ function loadLensRoster() {
267
279
  return roster;
268
280
  }
269
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
+
270
298
  function lensBlock(spec) {
271
299
  const roster = loadLensRoster();
272
- const specs = spec.split(',').map((s) => s.trim()).filter(Boolean);
273
300
  const described = [];
274
- for (const s of specs) {
301
+ for (const s of splitLenses(spec, roster)) {
275
302
  if (roster.has(s.toLowerCase())) described.push(`- ${s.toLowerCase()} — ${roster.get(s.toLowerCase())}`);
276
- else if (s.includes(':')) {
303
+ else if (/^[^:,]{1,40}:/.test(s)) {
277
304
  const [name, ...rest] = s.split(':');
278
305
  described.push(`- ${name.trim()} — ${rest.join(':').trim()}`);
279
306
  } else throw new UsageError(`unknown lens "${s}" (known: ${[...roster.keys()].join(', ')}; or pass "Name:description")`);
@@ -13,6 +13,11 @@ what it reviews. Weighing the answers is your job, and steps 4 and 5 are how.
13
13
  - **Poll** when the question is *which of these is better?* and being wrong costs mild regret: a name, a
14
14
  framing, a piece of copy, which of several tools fits. If all three agreeing would settle it, it is a poll.
15
15
  If you would still want each reviewer's top objections and a suggested edit, it is a critique.
16
+ A critique is required, not optional, before implementing a change that is architectural, irreversible, or touches
17
+ what may be disclosed and to whom — run it on the written plan before the work, never as a justification after it.
18
+ Its verdict, and the reasoning behind the option chosen, land in the pull request's body: for a structural change
19
+ the pull request and its commit are the decision record, and there is no separate governance step.
20
+
16
21
  - **Answer directly** when there is one right answer, when the operator has already decided and wants
17
22
  confirmation, or when the work is read-only investigation.
18
23
 
@@ -82,11 +87,17 @@ nothing in the plan, so it was written without reading it; it does not count. **
82
87
  two valid critiques: say so at the top of your answer and do not call anything a consensus.
83
88
 
84
89
  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. A default critique usually takes under a minute
86
- and costs a few tens of cents. Claude's highest effort setting (`CTXR_SECOND_OPINION_CLAUDE_EFFORT=max`) is
87
- a deliberate deep pass, not a default: on the same short plan it took over eight minutes where the default
88
- took half a minute, and it is the one setting that can reach the runner's ten-minute limit. Start such a run
89
- in the background and tell the operator it is under way.
90
+ default tier is right for anything the operator will act on.
91
+
92
+ 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.
90
101
 
91
102
  ## 4. Synthesize on the letters first
92
103
 
@@ -150,7 +161,9 @@ This mode names its files by model, because what you are reading is whether the
150
161
  To get breadth as well as model diversity, add `--lens` with two or three taste lenses. Each model then
151
162
  answers as every lens in one response, giving a lens-by-model grid. The built-in lenses are minimalist,
152
163
  marketer, contrarian, end-user and brand-strategist. A store's own lens goes in as `Name:description`, for
153
- example `--lens "minimalist,Plainspoken:blunt and substance first"`. Choose lenses that pull in different
164
+ example `--lens "minimalist,Plainspoken:blunt, substance first, distrusts slogans"`. A description may contain
165
+ commas: a comma starts the next lens only when what follows is a built-in lens name or begins `Name:`. So a
166
+ description must not contain a colon. Choose lenses that pull in different
154
167
  directions; if two would obviously agree, drop one.
155
168
 
156
169
  Read a lens grid on both axes. Down a column (one lens across the three models) agreement means that
@@ -23,13 +23,25 @@ pull request?" — needing to ask is itself the answer: leave, and let the work
23
23
  Ending a turn without that signal is normal and is not this skill. Say in one line what is uncommitted
24
24
  and which worktree holds it, then stop. The work is safe on the branch and the next message picks it up.
25
25
 
26
+ ## Routes
27
+
28
+ Every pull request takes one of three routes. Say which, and why, in its body:
29
+
30
+ - **Session** (the default): the session's work plus the capture it approved.
31
+ - **Own**: a new skill, a structural change (folders, the entry document, conventions), or a refactor touching five
32
+ or more files — reviewed on its own so it is not buried in content.
33
+ - **Housekeeping**: small appends nothing else depends on (a log line, a stray link fix), batched rather than folded
34
+ into unrelated work.
35
+
26
36
  ## Steps
27
37
 
28
38
  1. Re-scan (mandatory — never replay a plan from an earlier snapshot): `git fetch origin`,
29
39
  `git status --short`, `git diff --stat`, `git diff --cached --stat`,
30
40
  `git ls-files --others --exclude-standard`, `git log --oneline origin/__DEFAULT_BRANCH__..HEAD`. State moves
31
41
  under you while you work; name the delta from the previous scan rather than silently folding new work
32
- into old buckets.
42
+ into old buckets. If the session touched or linked to an entity note, run `ctxr rollup stale --for <entity>`
43
+ (or a plain `ctxr rollup stale` when unsure which), and offer `ctxr-rollup` on anything flagged — never run a
44
+ rollup unasked; the operator decides.
33
45
  2. Capture pass: run `ctxr-session-capture` exactly once here — closing a session is itself a capture
34
46
  trigger; do not fire it again after submitting.
35
47
  3. Stage surgically: `git add <paths>`, never `git add -A`; confirm with `git status --short` that the
@@ -39,11 +51,23 @@ and which worktree holds it, then stop. The work is safe on the branch and the n
39
51
  5. Validate: run `ctxr doctor` (store scope, not `--staged` — a session's job is to leave the whole store
40
52
  healthy, not merely pass one commit's gate). Fix a failure; never bypass it, and never proceed past it.
41
53
  6. Commit: `git commit -m "<message>"`, describing the unit staged in step 3.
42
- 7. Name the branch: if it still carries a generated name, `git branch -m "<name>"` before pushing — never
54
+ 7. Name the branch: `<prefix>/<short-slug>`, with a prefix from `skill/` `feat/` `project/` `notes/` `chore/`
55
+ `refactor/` `fix/` unless this store's conventions say otherwise. If it still carries a generated name,
56
+ `git branch -m "<name>"` before pushing — never
43
57
  let a generated name reach the forge. A name given at `ctxr session start` does not settle this: it
44
58
  named a worktree when the work began, and what ships is decided here.
45
- 8. Run: `git push -u origin "<branch>"`, then `gh pr create --base __DEFAULT_BRANCH__ --title "<title>" --body
46
- "<why / what changed / verification / follow-ups>"`. Do not stop to confirm first — the request to
59
+ 8. Before opening the pull request, run `ctxr publish urls --since origin/__DEFAULT_BRANCH__ --json`. When it
60
+ lists any page, the body gets a **Pages** section: a table with one row per page giving its status, its
61
+ preview address, and its published address — copied from the report, never rebuilt by hand. Mark a moved
62
+ page's old address as one that stops working, and a removed page's the same. Under the table state the
63
+ condition once: a preview resolves only while this pull request is open and the server can see this
64
+ session's worktree, and the published address resolves once the served checkout takes the landed commit.
65
+ When the report carries no preview, leave that column out; when it says no base URL is declared, say the
66
+ addresses are bare routes. When it lists no page, add no section.
67
+ Then run: `git push -u origin "<branch>"`, then `gh pr create --base __DEFAULT_BRANCH__ --title "<title>" --body
68
+ "<why / what changed / route / verification / follow-ups>"`. `gh` authenticates from its own credential
69
+ file: never export or interpolate a token, and never rewrite the origin's URL — it is a credential. The body answers those five questions; write the
70
+ why so a reviewer need not re-derive it. Do not stop to confirm first — the request to
47
71
  submit is the consent for both, and `ctxr doctor` in step 5 is the gate on this path. If `gh` has no
48
72
  reachable GitHub remote for this repository, `git push` still succeeds on its own — report the pushed
49
73
  branch and give the operator the manual pull-request instructions instead of retrying `gh`.