@link-assistant/hive-mind 2.1.11 → 2.2.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 2.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - e86a40e: Add a `--development-log` solve option that prompts agents to collect issue data, then preserves and commits resumable tool sessions under per-session UUID directories.
8
+
9
+ ## 2.2.0
10
+
11
+ ### Minor Changes
12
+
13
+ - d3b7c97: Default `--tool codex` to `gpt-5.6-sol` and make the `--think` levels map predictably to Codex reasoning efforts (`off`→`none`, `low`/`medium`/`high`/`xhigh`/`ultra`/`max` as an identity mapping). GPT-5.6 Sol's multi-agent `ultra` mode is always paired with a `rollout_token_budget` cap (default `500000`, overridable via `--rollout-token-budget`), and budget-derived effort stays capped at `xhigh`. Align `--tool claude` by adding the matching `ultra` level (equivalent to `ultracode`). By default both tools run the model as-is with no thinking level enforced.
14
+
3
15
  ## 2.1.11
4
16
 
5
17
  ### Patch Changes
package/README.hi.md CHANGED
@@ -39,7 +39,7 @@ Hive Mind एक **सामान्यवादी AI** (मिनी-AGI) ह
39
39
  | सदस्यता | `--tool` के साथ | डिफ़ॉल्ट मॉडल | किसके लिए बेहतर है |
40
40
  | ---------------------------------------------------------------- | ------------------- | ------------- | ------------------------------------------------- |
41
41
  | **Anthropic Claude MAX** (~$200/माह, अक्सर 50% छूट = $400 मूल्य) | `claude` (डिफ़ॉल्ट) | Sonnet/Haiku | उच्चतम रचनात्मकता, मजबूत सामान्य कोड रीजनिंग |
42
- | **OpenAI ChatGPT Pro** ($200/माह, Codex शामिल) | `codex` | `gpt-5.5` | भरोसेमंद deterministic refactors और तेज iteration |
42
+ | **OpenAI ChatGPT Pro** ($200/माह, Codex शामिल) | `codex` | `gpt-5.6-sol` | भरोसेमंद deterministic refactors और तेज iteration |
43
43
 
44
44
  दोनों टूल एक ही hive में साथ उपयोग किए जा सकते हैं। Worker अलग-अलग टूल समानांतर चला सकते हैं, और `/codex` या `/solve --tool codex` कार्यों को ChatGPT Pro पर भेजता है जबकि डिफ़ॉल्ट Claude MAX पर जाता है। किसी एक को चुनना आवश्यक नहीं है: किसी भी एक सदस्यता से संचालन संभव है, और दोनों का उपयोग per-tool/model concurrency mode (#1474) खोलता है।
45
45
 
package/README.md CHANGED
@@ -39,7 +39,7 @@ Hive Mind is a **generalist AI** (mini-AGI) capable of working on a wide range o
39
39
  | Subscription | Pairs with `--tool` | Default model | Best for |
40
40
  | ------------------------------------------------------------------ | ------------------- | ------------- | ------------------------------------------------------- |
41
41
  | **Anthropic Claude MAX** (~$200/month, often 50% off = $400 value) | `claude` (default) | Sonnet/Haiku | Highest creativity, strongest general code reasoning |
42
- | **OpenAI ChatGPT Pro** ($200/month, includes Codex) | `codex` | `gpt-5.5` | Strong deterministic refactors and fast iteration loops |
42
+ | **OpenAI ChatGPT Pro** ($200/month, includes Codex) | `codex` | `gpt-5.6-sol` | Strong deterministic refactors and fast iteration loops |
43
43
 
44
44
  Both tools can be combined in the same hive. Workers can run different tools in parallel, and `/codex` or `/solve --tool codex` routes tasks to ChatGPT Pro while the default routes to Claude MAX. There is no requirement to pick one: either single subscription is enough to operate, and using both unlocks per-tool/model concurrency mode (#1474).
45
45
 
@@ -507,15 +507,15 @@ Free Models via Kilo Gateway (with --tool agent):
507
507
 
508
508
  Current tool defaults in Hive Mind:
509
509
 
510
- | Tool | Default model | Default reasoning behavior |
511
- | ---------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
512
- | `claude` | `sonnet` | No extra thinking is requested unless you pass `--think` or `--thinking-budget` |
513
- | `codex` | `gpt-5.5` preferred, with runtime fallback to local catalog | Codex runs with `reasoning_effort=none` unless you pass `--think` or `--thinking-budget` |
514
- | `opencode` | `grok-code-fast-1` | No extra thinking prompt is added for the default model |
515
- | `agent` | `nemotron-3-super-free` | No extra thinking prompt is added for the default model |
516
- | `gemini` | `flash` | No extra thinking prompt is added for the default model |
517
- | `qwen` | `qwen3-coder-plus` | No extra thinking prompt is added for the default model |
518
- | `gemini` | `gemini-2.5-flash` | No extra thinking prompt is added for the default model |
510
+ | Tool | Default model | Default reasoning behavior |
511
+ | ---------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
512
+ | `claude` | `sonnet` | No extra thinking is requested unless you pass `--think` or `--thinking-budget` |
513
+ | `codex` | `gpt-5.6-sol` preferred, with runtime fallback to local catalog (`gpt-5.5`) | Codex runs with `reasoning_effort=none` unless you pass `--think` or `--thinking-budget` |
514
+ | `opencode` | `grok-code-fast-1` | No extra thinking prompt is added for the default model |
515
+ | `agent` | `nemotron-3-super-free` | No extra thinking prompt is added for the default model |
516
+ | `gemini` | `flash` | No extra thinking prompt is added for the default model |
517
+ | `qwen` | `qwen3-coder-plus` | No extra thinking prompt is added for the default model |
518
+ | `gemini` | `gemini-2.5-flash` | No extra thinking prompt is added for the default model |
519
519
 
520
520
  See [docs/CONFIGURATION.md](./docs/CONFIGURATION.md) for the full per-tool defaults and reasoning mappings.
521
521
 
package/README.ru.md CHANGED
@@ -39,7 +39,7 @@ Hive Mind — это **универсальный ИИ** (мини-AGI), спо
39
39
  | Подписка | Используется с `--tool` | Модель по умолчанию | Лучше всего подходит для |
40
40
  | ----------------------------------------------------------------- | ----------------------- | ------------------- | ---------------------------------------------------------- |
41
41
  | **Anthropic Claude MAX** (~$200 в месяц, часто скидка 50% = $400) | `claude` (по умолчанию) | Sonnet/Haiku | Максимальная креативность и сильное общее кодовое мышление |
42
- | **OpenAI ChatGPT Pro** ($200 в месяц, включает Codex) | `codex` | `gpt-5.5` | Надёжные детерминированные рефакторинги и быстрые итерации |
42
+ | **OpenAI ChatGPT Pro** ($200 в месяц, включает Codex) | `codex` | `gpt-5.6-sol` | Надёжные детерминированные рефакторинги и быстрые итерации |
43
43
 
44
44
  Оба инструмента можно сочетать в одном hive. Воркеры могут параллельно запускать разные инструменты, а `/codex` или `/solve --tool codex` направляет задачи в ChatGPT Pro, тогда как маршрут по умолчанию идёт в Claude MAX. Выбирать только один вариант не требуется: любой одной подписки достаточно для работы, а использование обеих открывает режим параллелизма по инструментам/моделям (#1474).
45
45
 
package/README.zh.md CHANGED
@@ -36,10 +36,10 @@ Hive Mind 是一款**通用 AI**(迷你 AGI),能够处理广泛的任务
36
36
 
37
37
  **费用**:Hive Mind 支持两种 $200/月订阅作为功能完整、近乎“无限”的选项:
38
38
 
39
- | 订阅 | 搭配 `--tool` | 默认模型 | 最适合场景 |
40
- | ---------------------------------------------------------------- | ---------------- | ------------ | ---------------------------- |
41
- | **Anthropic Claude MAX**(约 $200/月,常有五折优惠 = 价值 $400) | `claude`(默认) | Sonnet/Haiku | 最高创造力、最强通用代码推理 |
42
- | **OpenAI ChatGPT Pro**($200/月,包含 Codex) | `codex` | `gpt-5.5` | 稳定确定性的重构和快速迭代 |
39
+ | 订阅 | 搭配 `--tool` | 默认模型 | 最适合场景 |
40
+ | ---------------------------------------------------------------- | ---------------- | ------------- | ---------------------------- |
41
+ | **Anthropic Claude MAX**(约 $200/月,常有五折优惠 = 价值 $400) | `claude`(默认) | Sonnet/Haiku | 最高创造力、最强通用代码推理 |
42
+ | **OpenAI ChatGPT Pro**($200/月,包含 Codex) | `codex` | `gpt-5.6-sol` | 稳定确定性的重构和快速迭代 |
43
43
 
44
44
  两个工具可以在同一个 hive 中组合使用。Worker 可以并行运行不同工具,`/codex` 或 `/solve --tool codex` 会将任务路由到 ChatGPT Pro,而默认路由到 Claude MAX。无需二选一:任意单一订阅都足以运行,同时使用两者可解锁按工具/模型划分的并发模式(#1474)。
45
45
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "2.1.11",
3
+ "version": "2.2.1",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -62,8 +62,13 @@ const buildClaudeToolOptions = (argv = {}) => {
62
62
 
63
63
  const buildCodexToolOptions = (argv = {}) => {
64
64
  const options = {};
65
- const { reasoningEffort } = resolveCodexReasoningEffort(argv);
66
- appendExtraArgs(options, ['-c', `model_reasoning_effort=${reasoningEffort}`, '-c', 'model_reasoning_summary=auto']);
65
+ const { reasoningEffort, rolloutTokenBudget } = resolveCodexReasoningEffort(argv);
66
+ const reasoningArgs = ['-c', `model_reasoning_effort=${reasoningEffort}`, '-c', 'model_reasoning_summary=auto'];
67
+ // Issue #2027: pair GPT-5.6 Sol's multi-agent `ultra` effort with a rollout token budget cap.
68
+ if (rolloutTokenBudget) {
69
+ reasoningArgs.push('-c', `rollout_token_budget=${rolloutTokenBudget}`);
70
+ }
71
+ appendExtraArgs(options, reasoningArgs);
67
72
 
68
73
  appendExtraArgs(options, buildCodexDisable1mContextConfigArgs(!!argv.disable1mContext));
69
74
  try {
@@ -8,6 +8,7 @@ import { getExperimentsExamplesSubPrompt } from './experiments-examples.prompts.
8
8
  import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
9
9
  import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
10
10
  import { buildRequestedBaseBranchDirective } from './solve-option-contract.prompts.lib.mjs';
11
+ import { buildDevelopmentLogPrompt } from './development-log.lib.mjs';
11
12
 
12
13
  /**
13
14
  * Build the user prompt for Agent
@@ -66,6 +67,11 @@ export const buildUserPrompt = params => {
66
67
  promptLines.push('');
67
68
  }
68
69
 
70
+ const developmentLogPrompt = buildDevelopmentLogPrompt({ argv, issueNumber, prNumber }).trim();
71
+ if (developmentLogPrompt) {
72
+ promptLines.push(developmentLogPrompt, '');
73
+ }
74
+
69
75
  const thinkingPromptInstruction = getThinkingPromptInstruction({ tool: 'agent', argv });
70
76
  if (thinkingPromptInstruction) {
71
77
  promptLines.push(thinkingPromptInstruction);
@@ -10,6 +10,7 @@ import { primaryModelNames } from './models/index.mjs';
10
10
  import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
11
11
  import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
12
12
  import { buildRequestedBaseBranchDirective } from './solve-option-contract.prompts.lib.mjs';
13
+ import { buildDevelopmentLogPrompt } from './development-log.lib.mjs';
13
14
 
14
15
  /**
15
16
  * Build the user prompt for Claude
@@ -79,6 +80,11 @@ export const buildUserPrompt = params => {
79
80
  promptLines.push('');
80
81
  }
81
82
 
83
+ const developmentLogPrompt = buildDevelopmentLogPrompt({ argv, issueNumber, prNumber }).trim();
84
+ if (developmentLogPrompt) {
85
+ promptLines.push(developmentLogPrompt, '');
86
+ }
87
+
82
88
  const thinkingPromptInstruction = getThinkingPromptInstruction({ tool: 'claude', argv, claudeVersion });
83
89
  if (thinkingPromptInstruction) {
84
90
  promptLines.push(thinkingPromptInstruction);
package/src/codex.lib.mjs CHANGED
@@ -804,7 +804,7 @@ export const executeCodexCommand = async params => {
804
804
 
805
805
  let execCommand;
806
806
  const mappedModel = mapModelToId(argv.model);
807
- const { reasoningEffort, source: reasoningEffortSource } = resolveCodexReasoningEffort(argv);
807
+ const { reasoningEffort, source: reasoningEffortSource, rolloutTokenBudget } = resolveCodexReasoningEffort(argv);
808
808
  const isResumeMode = !!argv.resume;
809
809
  const codexEnv = getCodexExecEnv(argv.verbose);
810
810
 
@@ -839,7 +839,10 @@ export const executeCodexCommand = async params => {
839
839
  for (const arg of codexPlaywrightMcpDisableConfigArgs) {
840
840
  codexArgs += ` ${shellQuote(arg)}`;
841
841
  }
842
- codexArgs += ` --json --skip-git-repo-check -o ${shellQuote(lastMessageFile)} -c ${shellQuote(`model_reasoning_effort=${reasoningEffort}`)} -c ${shellQuote('model_reasoning_summary=auto')} --dangerously-bypass-approvals-and-sandbox`;
842
+ codexArgs += ` --json --skip-git-repo-check -o ${shellQuote(lastMessageFile)} -c ${shellQuote(`model_reasoning_effort=${reasoningEffort}`)} -c ${shellQuote('model_reasoning_summary=auto')}`;
843
+ // Issue #2027: pair GPT-5.6 Sol's multi-agent `ultra` effort with a rollout token budget cap so it stays predictable and does not run away on cost.
844
+ if (rolloutTokenBudget) codexArgs += ` -c ${shellQuote(`rollout_token_budget=${rolloutTokenBudget}`)}`;
845
+ codexArgs += ' --dangerously-bypass-approvals-and-sandbox';
843
846
 
844
847
  // Issue #1706: Append --disable-1m-context and --sub-session-size as Codex -c overrides.
845
848
  let parsedSubSessionSize;
@@ -4,13 +4,34 @@ import { codexModels } from './models/index.mjs';
4
4
 
5
5
  export const mapModelToId = model => codexModels[model] || model;
6
6
 
7
+ // Issue #2027: Map the shared hive-mind --think levels to Codex `model_reasoning_effort`
8
+ // values. GPT-5.6 Sol (the default Codex model) keeps the full ladder inherited from the
9
+ // GPT-5.5/GPT-5.4 generation — low/medium/high/xhigh — and adds `max` *above* xhigh for the
10
+ // deepest single-agent reasoning, plus a multi-agent `ultra` mode. Because every hive level
11
+ // has a same-named Codex reasoning effort, the mapping is a predictable identity: `xhigh`
12
+ // stays `xhigh` (natively supported per `codex debug models`), `ultra` selects GPT-5.6's
13
+ // multi-agent ultra mode (the counterpart of Claude's "ultracode"), and `max` selects the
14
+ // deepest single-agent effort. `off` disables reasoning (`none`). See docs/case-studies/issue-2027.
7
15
  const THINK_LEVEL_TO_CODEX_REASONING = {
8
16
  off: 'none',
9
17
  low: 'low',
10
18
  medium: 'medium',
11
19
  high: 'high',
12
20
  xhigh: 'xhigh',
13
- max: 'xhigh',
21
+ ultra: 'ultra',
22
+ max: 'max',
23
+ };
24
+
25
+ // Issue #2027: GPT-5.6 Sol's multi-agent `ultra` mode spawns subagents and consumes far more
26
+ // tokens per turn than single-agent reasoning. OpenAI's guidance is explicit: never use `ultra`
27
+ // reasoning effort without a `rollout_token_budget` cap, or it can run away on cost. We pair
28
+ // every `ultra` selection with this budget (their recommended default) so `--think ultra` stays
29
+ // predictable. Override with `--rollout-token-budget`.
30
+ export const CODEX_ULTRA_ROLLOUT_TOKEN_BUDGET = 500000;
31
+
32
+ const resolveUltraRolloutTokenBudget = argv => {
33
+ const override = argv?.rolloutTokenBudget;
34
+ return Number.isFinite(override) && override > 0 ? override : CODEX_ULTRA_ROLLOUT_TOKEN_BUDGET;
14
35
  };
15
36
 
16
37
  export const resolveCodexReasoningEffort = argv => {
@@ -26,6 +47,10 @@ export const resolveCodexReasoningEffort = argv => {
26
47
  }
27
48
 
28
49
  const ratio = Math.min(1, thinkingBudget / maxBudget);
50
+ // Issue #2027: the budget-derived effort caps at `xhigh` — the deepest tier every Codex
51
+ // model (including the gpt-5.5 runtime fallback) supports. `max` is GPT-5.6-only and `ultra`
52
+ // needs a paired rollout token budget, so both require an explicit `--think max`/`--think ultra`
53
+ // to stay predictable rather than being reached implicitly through a token budget.
29
54
  const reasoningEffort = ratio <= 0.2 ? 'minimal' : ratio <= 0.4 ? 'low' : ratio <= 0.6 ? 'medium' : ratio <= 0.8 ? 'high' : 'xhigh';
30
55
 
31
56
  return {
@@ -35,10 +60,15 @@ export const resolveCodexReasoningEffort = argv => {
35
60
  }
36
61
 
37
62
  if (argv?.think && THINK_LEVEL_TO_CODEX_REASONING[argv.think]) {
38
- return {
39
- reasoningEffort: THINK_LEVEL_TO_CODEX_REASONING[argv.think],
63
+ const reasoningEffort = THINK_LEVEL_TO_CODEX_REASONING[argv.think];
64
+ const result = {
65
+ reasoningEffort,
40
66
  source: `--think ${argv.think}`,
41
67
  };
68
+ if (reasoningEffort === 'ultra') {
69
+ result.rolloutTokenBudget = resolveUltraRolloutTokenBudget(argv);
70
+ }
71
+ return result;
42
72
  }
43
73
 
44
74
  return {
@@ -50,4 +80,5 @@ export const resolveCodexReasoningEffort = argv => {
50
80
  export default {
51
81
  mapModelToId,
52
82
  resolveCodexReasoningEffort,
83
+ CODEX_ULTRA_ROLLOUT_TOKEN_BUDGET,
53
84
  };
@@ -9,6 +9,7 @@ import { getExperimentsExamplesSubPrompt } from './experiments-examples.prompts.
9
9
  import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
10
10
  import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
11
11
  import { buildRequestedBaseBranchDirective } from './solve-option-contract.prompts.lib.mjs';
12
+ import { buildDevelopmentLogPrompt } from './development-log.lib.mjs';
12
13
 
13
14
  /**
14
15
  * Build the user prompt for Codex
@@ -67,6 +68,11 @@ export const buildUserPrompt = params => {
67
68
  promptLines.push('');
68
69
  }
69
70
 
71
+ const developmentLogPrompt = buildDevelopmentLogPrompt({ argv, issueNumber, prNumber }).trim();
72
+ if (developmentLogPrompt) {
73
+ promptLines.push(developmentLogPrompt, '');
74
+ }
75
+
70
76
  const thinkingPromptInstruction = getThinkingPromptInstruction({ tool: 'codex', argv });
71
77
  if (thinkingPromptInstruction) {
72
78
  promptLines.push(thinkingPromptInstruction);
@@ -397,6 +397,7 @@ export const getThinkingLevelToTokens = (maxBudget = DEFAULT_MAX_THINKING_BUDGET
397
397
  medium: Math.floor(maxBudget / 2), // ~16000 for default 31999
398
398
  high: Math.floor((maxBudget * 3) / 4), // ~24000 for default 31999
399
399
  xhigh: maxBudget, // same as max when represented as MAX_THINKING_TOKENS
400
+ ultra: maxBudget, // Issue #2027: ultra is max-class in token terms for Claude
400
401
  max: maxBudget, // 31999 by default
401
402
  });
402
403
 
@@ -482,10 +483,13 @@ export const OPUS_46_EFFORT_LEVELS = ['low', 'medium', 'high', 'max'];
482
483
  export const OPUS_47_EFFORT_LEVELS = ['low', 'medium', 'high', 'xhigh', 'max'];
483
484
 
484
485
  /**
485
- * Convert thinking level to effort level (Issue #1238, Issue #1620)
486
+ * Convert thinking level to effort level (Issue #1238, Issue #1620, Issue #2027)
486
487
  * Models with max support keep max as max. Opus 4.7 keeps xhigh as xhigh.
487
488
  * Models with effort but without max support use high for max/xhigh.
488
- * @param {string|undefined} thinkLevel - The thinking level (off/low/medium/high/xhigh/max)
489
+ * `ultra` (Issue #2027) requests Claude "ultracode"-class reasoning; Claude Code has no
490
+ * distinct `ultra`/`ultracode` effort value, so it clamps to the highest supported effort
491
+ * (max, else xhigh, else high).
492
+ * @param {string|undefined} thinkLevel - The thinking level (off/low/medium/high/xhigh/ultra/max)
489
493
  * @param {Object} [options] - Options
490
494
  * @param {boolean} [options.isOpus47] - Backward-compatible shorthand for supportsXHigh
491
495
  * @param {boolean} [options.supportsXHigh] - Whether the model supports xhigh effort
@@ -509,6 +513,8 @@ export const thinkLevelToEffortLevel = (thinkLevel, options = {}) => {
509
513
  return 'high';
510
514
  case 'xhigh':
511
515
  return supportsXHigh ? 'xhigh' : supportsMax ? 'max' : 'high';
516
+ case 'ultra':
517
+ return supportsMax ? 'max' : supportsXHigh ? 'xhigh' : 'high';
512
518
  case 'max':
513
519
  return supportsMax ? 'max' : 'high';
514
520
  default:
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Build a once-only finalizer so both the normal and error completion paths can
3
+ * preserve a development log without creating duplicate commits.
4
+ */
5
+ export const createDevelopmentLogFinalizer = ({ collect, getParams }) => {
6
+ let resultPromise = null;
7
+ return () => {
8
+ if (!resultPromise) resultPromise = Promise.resolve().then(() => collect(getParams()));
9
+ return resultPromise;
10
+ };
11
+ };
@@ -0,0 +1,297 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ const sanitizePathSegment = (value, fallback) => {
6
+ const raw = value === null || value === undefined || value === '' ? fallback : String(value);
7
+ const sanitized = raw.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');
8
+ return sanitized || fallback;
9
+ };
10
+
11
+ const stripDotSlash = value => value.replace(/^\.\//, '');
12
+ const toPosixPath = value => value.split(path.sep).join('/');
13
+ const addDotSlash = value => (value.startsWith('./') ? value : `./${value}`);
14
+
15
+ const safeFileName = value => sanitizePathSegment(value, 'session');
16
+
17
+ export const buildDevelopmentLogDirectory = ({ issueNumber, prNumber }) => {
18
+ const issueSegment = sanitizePathSegment(issueNumber, 'unknown');
19
+ const prSegment = sanitizePathSegment(prNumber, 'pending');
20
+ return `./dev/log/issues/${issueSegment}/pulls/${prSegment}`;
21
+ };
22
+
23
+ export const buildCaseStudyDirectory = ({ issueNumber }) => {
24
+ const issueSegment = sanitizePathSegment(issueNumber, 'unknown');
25
+ return `./docs/case-studies/issue-${issueSegment}`;
26
+ };
27
+
28
+ // Normalize a GitHub issue type (or label) into one of the buckets the
29
+ // development-log prompt distinguishes. Bug issues get the stronger
30
+ // "download all logs" wording; everything else (feature, task, or an
31
+ // unspecified/unknown type) gets the universal data-collection wording.
32
+ export const isBugIssueType = issueType => {
33
+ if (issueType === null || issueType === undefined) return false;
34
+ const normalized = String(issueType).trim().toLowerCase();
35
+ if (!normalized) return false;
36
+ return normalized === 'bug' || normalized === 'bugs' || normalized.includes('bug') || normalized === 'defect';
37
+ };
38
+
39
+ // True when the run requested the development log via --development-log
40
+ // (yargs exposes both the camelCase and kebab-case keys).
41
+ export const isDevelopmentLogEnabled = argv => argv?.developmentLog === true || argv?.['development-log'] === true;
42
+
43
+ export const buildDevelopmentLogPrompt = ({ argv, issueNumber, prNumber, issueType }) => {
44
+ if (!(argv?.developmentLog || argv?.['development-log'])) return '';
45
+
46
+ const developmentLogDirectory = buildDevelopmentLogDirectory({ issueNumber, prNumber });
47
+ // Automatic support for issue types: when the issue type is "bug" the
48
+ // instruction asks to download all logs as well; for feature/task issues, or
49
+ // when no issue type is selected, the universal data-collection wording is used.
50
+ const resolvedIssueType = issueType ?? argv?.issueType ?? null;
51
+ const collectionInstruction = isBugIssueType(resolvedIssueType) ? `Download all logs and collect data related about the issue to this repository, make sure we compile that data into the ${developmentLogDirectory} folder.` : `Collect data related about the issue to this repository, make sure we compile that data into the ${developmentLogDirectory} folder.`;
52
+
53
+ return `\n${collectionInstruction}\n`;
54
+ };
55
+
56
+ // Fetch the GitHub issue type (e.g. "Bug", "Feature", "Task") for an issue.
57
+ // Returns null when the type cannot be determined (no type selected, command
58
+ // failure, or non-issue targets). Accepts an injectable command runner so the
59
+ // behavior can be unit tested without hitting the network.
60
+ export const fetchIssueType = async ({ owner, repo, issueNumber, $, log }) => {
61
+ if (!owner || !repo || !issueNumber || typeof $ !== 'function') return null;
62
+ try {
63
+ // eslint-disable-next-line gh-rate-limit/no-direct-gh-exec -- $ is the injected, rate-limit-safe runner (wrapDollarWithGhRetry) passed in by the caller.
64
+ const result = await $`gh issue view ${issueNumber} --repo ${owner}/${repo} --json issueType`;
65
+ if (result?.code && result.code !== 0) return null;
66
+ const stdout = result?.stdout?.toString?.() ?? String(result?.stdout ?? '');
67
+ if (!stdout.trim()) return null;
68
+ const parsed = JSON.parse(stdout);
69
+ const name = parsed?.issueType?.name;
70
+ return name ? String(name) : null;
71
+ } catch (error) {
72
+ await log?.(`ℹ️ Could not determine issue type: ${error.message}`, { verbose: true });
73
+ return null;
74
+ }
75
+ };
76
+
77
+ const fileExists = async filePath => {
78
+ try {
79
+ const stat = await fs.stat(filePath);
80
+ return stat.isFile();
81
+ } catch {
82
+ return false;
83
+ }
84
+ };
85
+
86
+ const copyIfExists = async ({ sourcePath, destinationPath }) => {
87
+ if (!(await fileExists(sourcePath))) return false;
88
+ await fs.copyFile(sourcePath, destinationPath);
89
+ return true;
90
+ };
91
+
92
+ const getClaudeSessionFile = ({ repositoryPath, sessionId, homeDir }) => {
93
+ if (!repositoryPath || !sessionId || !homeDir) return null;
94
+ const projectDirName = repositoryPath.replace(/\//g, '-');
95
+ return path.join(homeDir, '.claude', 'projects', projectDirName, `${sessionId}.jsonl`);
96
+ };
97
+
98
+ // Codex CLI stores its transcript ("rollout") under
99
+ // ~/.codex/sessions/YYYY/MM/DD/rollout-<timestamp>-<sessionId>.jsonl. The date
100
+ // path and timestamp are not derivable from the sessionId, so locate the file
101
+ // by recursively matching the sessionId suffix instead.
102
+ const findCodexSessionFile = async ({ sessionId, homeDir }) => {
103
+ if (!sessionId || !homeDir) return null;
104
+ const sessionsRoot = path.join(homeDir, '.codex', 'sessions');
105
+ try {
106
+ const entries = await fs.readdir(sessionsRoot, { recursive: true });
107
+ const match = entries.find(entry => typeof entry === 'string' && entry.includes('rollout-') && entry.endsWith(`-${sessionId}.jsonl`));
108
+ return match ? path.join(sessionsRoot, match) : null;
109
+ } catch {
110
+ return null;
111
+ }
112
+ };
113
+
114
+ const copyKnownSessionFiles = async ({ repositoryPath, sessionRelativeDirectory, logFile, sessionId, tool, homeDir }) => {
115
+ if (!sessionId) return [];
116
+
117
+ const sessionDirectory = path.join(repositoryPath, sessionRelativeDirectory);
118
+ const candidates = [];
119
+ const logDirectory = logFile ? path.dirname(logFile) : null;
120
+
121
+ if (logDirectory) {
122
+ candidates.push({
123
+ sourcePath: path.join(logDirectory, `${sessionId}.log`),
124
+ destinationName: `${tool || 'tool'}-${sessionId}.log`,
125
+ });
126
+ }
127
+
128
+ if (tool === 'claude') {
129
+ const claudeSessionFile = getClaudeSessionFile({ repositoryPath, sessionId, homeDir });
130
+ if (claudeSessionFile) {
131
+ candidates.push({
132
+ sourcePath: claudeSessionFile,
133
+ destinationName: `claude-${sessionId}.jsonl`,
134
+ });
135
+ }
136
+ }
137
+
138
+ if (tool === 'codex') {
139
+ const codexSessionFile = await findCodexSessionFile({ sessionId, homeDir });
140
+ if (codexSessionFile) {
141
+ candidates.push({
142
+ sourcePath: codexSessionFile,
143
+ destinationName: `codex-${sessionId}.jsonl`,
144
+ });
145
+ }
146
+ }
147
+
148
+ const copied = [];
149
+ const seenSources = new Set();
150
+ for (const candidate of candidates) {
151
+ if (!candidate.sourcePath || seenSources.has(candidate.sourcePath)) continue;
152
+ seenSources.add(candidate.sourcePath);
153
+
154
+ const relativePath = `${sessionRelativeDirectory}/${safeFileName(candidate.destinationName)}`;
155
+ const copiedPath = path.join(sessionDirectory, safeFileName(candidate.destinationName));
156
+ if (await copyIfExists({ sourcePath: candidate.sourcePath, destinationPath: copiedPath })) {
157
+ copied.push(addDotSlash(toPosixPath(relativePath)));
158
+ }
159
+ }
160
+
161
+ return copied;
162
+ };
163
+
164
+ export const writeDevelopmentLogArtifacts = async ({ repositoryPath, logFile, issueNumber, prNumber, tool, sessionId, branchName, rawCommand, now = new Date(), homeDir = os.homedir() }) => {
165
+ if (!repositoryPath) {
166
+ throw new Error('repositoryPath is required to write development-log artifacts');
167
+ }
168
+
169
+ const developmentLogDirectory = buildDevelopmentLogDirectory({ issueNumber, prNumber });
170
+ const caseStudyDirectory = buildCaseStudyDirectory({ issueNumber });
171
+ const relativeDirectory = stripDotSlash(developmentLogDirectory);
172
+ const timestamp = now.toISOString().replace(/[:.]/g, '-');
173
+ const sessionDirectoryName = safeFileName(sessionId || `run-${timestamp}`);
174
+ const sessionRelativeDirectory = `${relativeDirectory}/sessions/${sessionDirectoryName}`;
175
+ const sessionDirectory = path.join(repositoryPath, sessionRelativeDirectory);
176
+
177
+ await fs.mkdir(sessionDirectory, { recursive: true });
178
+
179
+ let copiedLogRelativePath = null;
180
+ if (logFile) {
181
+ copiedLogRelativePath = `${sessionRelativeDirectory}/solve.log`;
182
+ await fs.copyFile(logFile, path.join(repositoryPath, copiedLogRelativePath));
183
+ }
184
+
185
+ const sessionFiles = await copyKnownSessionFiles({
186
+ repositoryPath,
187
+ sessionRelativeDirectory,
188
+ logFile,
189
+ sessionId,
190
+ tool,
191
+ homeDir,
192
+ });
193
+
194
+ const metadataRelativePath = `${sessionRelativeDirectory}/metadata.json`;
195
+ const metadata = {
196
+ schemaVersion: 2,
197
+ collectedAt: now.toISOString(),
198
+ issueNumber: issueNumber ?? null,
199
+ prNumber: prNumber ?? null,
200
+ branchName: branchName || null,
201
+ tool: tool || null,
202
+ sessionId: sessionId || null,
203
+ rawCommand: rawCommand || null,
204
+ developmentLogDirectory,
205
+ caseStudyDirectory,
206
+ artifacts: {
207
+ solveLog: copiedLogRelativePath ? addDotSlash(toPosixPath(copiedLogRelativePath)) : null,
208
+ sessionFiles,
209
+ },
210
+ };
211
+
212
+ await fs.writeFile(path.join(repositoryPath, metadataRelativePath), `${JSON.stringify(metadata, null, 2)}\n`, 'utf8');
213
+
214
+ return {
215
+ developmentLogDirectory,
216
+ caseStudyDirectory,
217
+ relativeDirectory,
218
+ sessionRelativeDirectory,
219
+ copiedLogRelativePath: copiedLogRelativePath ? toPosixPath(copiedLogRelativePath) : null,
220
+ metadataRelativePath: toPosixPath(metadataRelativePath),
221
+ sessionFiles,
222
+ };
223
+ };
224
+
225
+ const getCommandOutput = result => (result?.stderr?.toString?.() || result?.stdout?.toString?.() || '').trim();
226
+
227
+ export const collectAndCommitDevelopmentLogArtifacts = async ({ enabled, repositoryPath, logFile, issueNumber, prNumber, tool, sessionId, branchName, rawCommand, $, log }) => {
228
+ if (!enabled) {
229
+ return { skipped: 'disabled' };
230
+ }
231
+
232
+ if (!repositoryPath) {
233
+ await log?.('⚠️ Development log requested but no repository path is available', { level: 'warning' });
234
+ return { skipped: 'missing-repository-path' };
235
+ }
236
+
237
+ try {
238
+ const artifacts = await writeDevelopmentLogArtifacts({
239
+ repositoryPath,
240
+ logFile,
241
+ issueNumber,
242
+ prNumber,
243
+ tool,
244
+ sessionId,
245
+ branchName,
246
+ rawCommand,
247
+ });
248
+
249
+ await log?.(`🧾 Development log artifacts written to ${artifacts.developmentLogDirectory}`);
250
+
251
+ if (!$) {
252
+ return { ...artifacts, committed: false, pushed: false };
253
+ }
254
+
255
+ const addResult = await $({ cwd: repositoryPath })`git add -f -- ${artifacts.relativeDirectory}`;
256
+ if (addResult.code !== 0) {
257
+ await log?.(`⚠️ Could not stage development log: ${getCommandOutput(addResult)}`, { level: 'warning' });
258
+ return { ...artifacts, committed: false, pushed: false };
259
+ }
260
+
261
+ const diffResult = await $({ cwd: repositoryPath })`git diff --cached --quiet -- ${artifacts.relativeDirectory}`;
262
+ if (diffResult.code === 0) {
263
+ await log?.('ℹ️ Development log artifacts already committed');
264
+ return { ...artifacts, committed: false, pushed: false };
265
+ }
266
+ if (diffResult.code !== 1) {
267
+ await log?.(`⚠️ Could not inspect staged development log changes: ${getCommandOutput(diffResult)}`, { level: 'warning' });
268
+ return { ...artifacts, committed: false, pushed: false };
269
+ }
270
+
271
+ const commitMessage = prNumber ? `Add development log for issue #${issueNumber} PR #${prNumber}` : `Add development log for issue #${issueNumber}`;
272
+ const commitResult = await $({ cwd: repositoryPath })`git commit -m ${commitMessage} -- ${artifacts.relativeDirectory}`;
273
+ if (commitResult.code !== 0) {
274
+ await log?.(`⚠️ Could not commit development log: ${getCommandOutput(commitResult)}`, { level: 'warning' });
275
+ return { ...artifacts, committed: false, pushed: false };
276
+ }
277
+
278
+ await log?.('✅ Development log committed');
279
+
280
+ if (!branchName) {
281
+ await log?.('ℹ️ Development log committed locally; no branch name available for push');
282
+ return { ...artifacts, committed: true, pushed: false };
283
+ }
284
+
285
+ const pushResult = await $({ cwd: repositoryPath })`git push origin ${branchName}`;
286
+ if (pushResult.code !== 0) {
287
+ await log?.(`⚠️ Could not push development log commit: ${getCommandOutput(pushResult)}`, { level: 'warning' });
288
+ return { ...artifacts, committed: true, pushed: false };
289
+ }
290
+
291
+ await log?.('✅ Development log pushed');
292
+ return { ...artifacts, committed: true, pushed: true };
293
+ } catch (error) {
294
+ await log?.(`⚠️ Development log collection failed: ${error.message}`, { level: 'warning' });
295
+ return { skipped: 'error', error };
296
+ }
297
+ };
@@ -8,6 +8,7 @@ import { getExperimentsExamplesSubPrompt } from './experiments-examples.prompts.
8
8
  import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
9
9
  import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
10
10
  import { buildRequestedBaseBranchDirective } from './solve-option-contract.prompts.lib.mjs';
11
+ import { buildDevelopmentLogPrompt } from './development-log.lib.mjs';
11
12
 
12
13
  /**
13
14
  * Build the user prompt for Gemini
@@ -57,6 +58,11 @@ export const buildUserPrompt = params => {
57
58
  promptLines.push('');
58
59
  }
59
60
 
61
+ const developmentLogPrompt = buildDevelopmentLogPrompt({ argv, issueNumber, prNumber }).trim();
62
+ if (developmentLogPrompt) {
63
+ promptLines.push(developmentLogPrompt, '');
64
+ }
65
+
60
66
  const thinkingPromptInstruction = getThinkingPromptInstruction({ tool: 'gemini', argv });
61
67
  if (thinkingPromptInstruction) {
62
68
  promptLines.push(thinkingPromptInstruction);
package/src/lib.mjs CHANGED
@@ -937,6 +937,19 @@ export default {
937
937
  setupStdioLogInterceptor,
938
938
  };
939
939
 
940
+ // Issue #1596: log the solve startup banner (version + raw command) and return
941
+ // the raw command string for reuse. Extracted from solve.mjs to keep that file
942
+ // under the 1500-line limit enforced by scripts/check-file-line-limits.sh.
943
+ export const logSolveStartup = async versionInfo => {
944
+ const rawCommand = process.argv.join(' ');
945
+ await log('');
946
+ await log(`🚀 solve v${versionInfo}`);
947
+ await log('🔧 Raw command executed:');
948
+ await log(` ${rawCommand}`);
949
+ await log('');
950
+ return rawCommand;
951
+ };
952
+
940
953
  /**
941
954
  * Get version information for logging
942
955
  * @returns {Promise<string>} Version string
@@ -578,7 +578,7 @@ en
578
578
  branch
579
579
  option "• `--base-branch <branch>` or `-b` - Target branch for PR (default: repo default branch)"
580
580
  think
581
- option "• `--think <level>` - Thinking level (off/low/medium/high/xhigh/max) | `--thinking-budget <num>` - Token budget (0-63999)"
581
+ option "• `--think <level>` - Thinking level (off/low/medium/high/xhigh/ultra/max) | `--thinking-budget <num>` - Token budget (0-63999)"
582
582
  verbose
583
583
  option "• `--verbose` or `-v` - Verbose output | `--attach-logs` - Attach logs to PR"
584
584
  show
@@ -578,7 +578,7 @@ hi
578
578
  branch
579
579
  option "• `--base-branch <branch>` या `-b` - PR के लिए target branch (default: repo default branch)"
580
580
  think
581
- option "• `--think <level>` - thinking level (off/low/medium/high/xhigh/max) | `--thinking-budget <num>` - token budget (0-63999)"
581
+ option "• `--think <level>` - thinking level (off/low/medium/high/xhigh/ultra/max) | `--thinking-budget <num>` - token budget (0-63999)"
582
582
  verbose
583
583
  option "• `--verbose` या `-v` - verbose output | `--attach-logs` - logs को PR से attach करें"
584
584
  show
@@ -578,7 +578,7 @@ ru
578
578
  branch
579
579
  option "• `--base-branch <branch>` или `-b` - Целевая ветка для PR (по умолчанию ветка репозитория)"
580
580
  think
581
- option "• `--think <level>` - уровень размышления (off/low/medium/high/xhigh/max) | `--thinking-budget <num>` - бюджет токенов (0-63999)"
581
+ option "• `--think <level>` - уровень размышления (off/low/medium/high/xhigh/ultra/max) | `--thinking-budget <num>` - бюджет токенов (0-63999)"
582
582
  verbose
583
583
  option "• `--verbose` или `-v` - подробный вывод | `--attach-logs` - прикрепить логи к PR"
584
584
  show
@@ -578,7 +578,7 @@ zh
578
578
  branch
579
579
  option "• `--base-branch <branch>` 或 `-b` - PR 目标分支(默认:仓库默认分支)"
580
580
  think
581
- option "• `--think <level>` - 思考级别(off/low/medium/high/xhigh/max)| `--thinking-budget <num>` - token 预算(0-63999)"
581
+ option "• `--think <level>` - 思考级别(off/low/medium/high/xhigh/ultra/max)| `--thinking-budget <num>` - token 预算(0-63999)"
582
582
  verbose
583
583
  option "• `--verbose` 或 `-v` - 详细输出 | `--attach-logs` - 将日志附加到 PR"
584
584
  show
@@ -192,7 +192,7 @@ export const defaultModels = {
192
192
  claude: 'sonnet',
193
193
  agent: 'nemotron-3-super-free', // Issue #1563: changed from qwen3.6-plus-free (free promotion ended) per agent PR #243
194
194
  opencode: 'grok-code-fast-1',
195
- codex: 'gpt-5.5',
195
+ codex: 'gpt-5.6-sol', // Issue #2027: GPT-5.6 Sol is the released Codex flagship; runtime falls back to gpt-5.5 when Sol is not in the local catalog
196
196
  qwen: 'qwen3-coder-plus',
197
197
  gemini: 'flash',
198
198
  };
@@ -366,7 +366,10 @@ export const getDefaultModelForTool = tool => {
366
366
  };
367
367
 
368
368
  let cachedInstalledCodexModelsPromise = null;
369
- const CODEX_DEFAULT_FALLBACK_CHAIN = ['openai.gpt-5.5', 'gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna', 'openai.gpt-5.6-sol', 'openai.gpt-5.6-terra', 'openai.gpt-5.6-luna', 'gpt-5.4', 'openai.gpt-5.4', 'gpt-5.5-mini', 'gpt-5.4-mini', 'gpt-5.3-codex', 'gpt-5.3-codex-spark', 'gpt-5.2', 'gpt-5.2-codex', 'gpt-5.5-nano', 'gpt-5.4-nano'];
369
+ // Issue #2027: With gpt-5.6-sol as the preferred default, the fallback chain is only
370
+ // consulted when Sol is absent from the local catalog. Prefer the previous stable
371
+ // default (gpt-5.5) first, then the remaining GPT-5.6 preview tiers, then older models.
372
+ const CODEX_DEFAULT_FALLBACK_CHAIN = ['gpt-5.5', 'openai.gpt-5.5', 'gpt-5.6-terra', 'gpt-5.6-luna', 'openai.gpt-5.6-sol', 'openai.gpt-5.6-terra', 'openai.gpt-5.6-luna', 'gpt-5.4', 'openai.gpt-5.4', 'gpt-5.5-mini', 'gpt-5.4-mini', 'gpt-5.3-codex', 'gpt-5.3-codex-spark', 'gpt-5.2', 'gpt-5.2-codex', 'gpt-5.5-nano', 'gpt-5.4-nano'];
370
373
 
371
374
  export const getInstalledCodexModels = async () => {
372
375
  if (!cachedInstalledCodexModelsPromise) {
@@ -490,7 +493,7 @@ export const getValidModelsForTool = tool => {
490
493
  export const primaryModelNames = {
491
494
  claude: ['opus', 'sonnet', 'haiku', 'opusplan', 'fable'],
492
495
  opencode: ['grok', 'gpt4o'],
493
- codex: ['gpt-5.5', 'gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-codex-spark'],
496
+ codex: ['gpt-5.6-sol', 'gpt-5.5', 'gpt-5.6-terra', 'gpt-5.6-luna', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-codex-spark'],
494
497
  agent: ['nemotron-3-super-free', 'minimax-m2.5-free', 'big-pickle', 'gpt-5-nano', 'glm-5-free', 'deepseek-r1-free'],
495
498
  qwen: ['qwen3-coder-plus', 'qwen3-coder', 'qwen3-coder-flash'],
496
499
  gemini: ['flash', 'pro', 'flash-lite', 'auto'],
@@ -8,6 +8,7 @@ import { getExperimentsExamplesSubPrompt } from './experiments-examples.prompts.
8
8
  import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
9
9
  import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
10
10
  import { buildRequestedBaseBranchDirective } from './solve-option-contract.prompts.lib.mjs';
11
+ import { buildDevelopmentLogPrompt } from './development-log.lib.mjs';
11
12
 
12
13
  /**
13
14
  * Build the user prompt for OpenCode
@@ -66,6 +67,11 @@ export const buildUserPrompt = params => {
66
67
  promptLines.push('');
67
68
  }
68
69
 
70
+ const developmentLogPrompt = buildDevelopmentLogPrompt({ argv, issueNumber, prNumber }).trim();
71
+ if (developmentLogPrompt) {
72
+ promptLines.push(developmentLogPrompt, '');
73
+ }
74
+
69
75
  const thinkingPromptInstruction = getThinkingPromptInstruction({ tool: 'opencode', argv });
70
76
  if (thinkingPromptInstruction) {
71
77
  promptLines.push(thinkingPromptInstruction);
@@ -207,6 +207,7 @@ const KNOWN_OPTION_NAMES = [
207
207
  'prompt-issue-reporting',
208
208
  'prompt-architecture-care',
209
209
  'prompt-case-studies',
210
+ 'development-log',
210
211
  'use-handoff',
211
212
  'prompt-playwright-mcp',
212
213
  'prompt-check-sibling-pull-requests',
@@ -8,6 +8,7 @@ import { getExperimentsExamplesSubPrompt } from './experiments-examples.prompts.
8
8
  import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
9
9
  import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
10
10
  import { buildRequestedBaseBranchDirective } from './solve-option-contract.prompts.lib.mjs';
11
+ import { buildDevelopmentLogPrompt } from './development-log.lib.mjs';
11
12
 
12
13
  /**
13
14
  * Build the user prompt for Qwen Code
@@ -57,6 +58,11 @@ export const buildUserPrompt = params => {
57
58
  promptLines.push('');
58
59
  }
59
60
 
61
+ const developmentLogPrompt = buildDevelopmentLogPrompt({ argv, issueNumber, prNumber }).trim();
62
+ if (developmentLogPrompt) {
63
+ promptLines.push(developmentLogPrompt, '');
64
+ }
65
+
60
66
  const thinkingPromptInstruction = getThinkingPromptInstruction({ tool, argv });
61
67
  if (thinkingPromptInstruction) {
62
68
  promptLines.push(thinkingPromptInstruction);
@@ -299,13 +299,18 @@ export const SOLVE_OPTION_DEFINITIONS = {
299
299
  },
300
300
  think: {
301
301
  type: 'string',
302
- description: 'Thinking level hint. For Claude, translated to --thinking-budget for Claude Code >= 2.1.12 (off=0, low=~8000, medium=~16000, high=~24000, xhigh/max=31999) and to CLAUDE_CODE_EFFORT_LEVEL when supported. Fable 5/Mythos 5/Sonnet 5/Opus 4.8/4.7 support xhigh and max; Opus 4.6/Sonnet 4.6/Mythos Preview support max; Opus 4.5 uses high for xhigh/max. For Codex, mapped to reasoning effort (off=none, low=low, medium=medium, high=high, xhigh/max=xhigh).',
303
- choices: ['off', 'low', 'medium', 'high', 'xhigh', 'max'],
302
+ description: 'Thinking level hint. For Claude, translated to --thinking-budget for Claude Code >= 2.1.12 (off=0, low=~8000, medium=~16000, high=~24000, xhigh/ultra/max=31999) and to CLAUDE_CODE_EFFORT_LEVEL when supported. Fable 5/Mythos 5/Sonnet 5/Opus 4.8/4.7 support xhigh and max; Opus 4.6/Sonnet 4.6/Mythos Preview support max; Opus 4.5 uses high for xhigh/max. `ultra` maps to the highest supported Claude effort (Claude "ultracode"-class reasoning). For Codex (GPT-5.6 Sol), mapped 1:1 to reasoning effort (off=none, low=low, medium=medium, high=high, xhigh=xhigh, ultra=ultra, max=max); GPT-5.6 keeps xhigh and adds max above it, and ultra runs the multi-agent mode paired with a rollout token budget cap. Default: no thinking level is enforced (models run as-is).',
303
+ choices: ['off', 'low', 'medium', 'high', 'xhigh', 'ultra', 'max'],
304
304
  default: undefined,
305
305
  },
306
306
  'thinking-budget': {
307
307
  type: 'number',
308
- description: 'Thinking token budget. For Claude Code, controls MAX_THINKING_TOKENS (0-31999 by default). For Codex, enables finer reasoning-effort mapping including minimal/low/medium/high/xhigh.',
308
+ description: 'Thinking token budget. For Claude Code, controls MAX_THINKING_TOKENS (0-31999 by default). For Codex, enables finer reasoning-effort mapping (minimal/low/medium/high) capped at xhigh; the deepest single-agent `max` and the multi-agent `ultra` effort must be requested explicitly via --think max / --think ultra.',
309
+ default: undefined,
310
+ },
311
+ 'rollout-token-budget': {
312
+ type: 'number',
313
+ description: "Codex rollout token budget (turn-level cap) paired with the multi-agent `--think ultra` effort so GPT-5.6 Sol's ultra mode stays predictable and does not run away on cost. Default: 500000. Only applied when --think ultra selects the ultra reasoning effort.",
309
314
  default: undefined,
310
315
  },
311
316
  'thinking-budget-claude-minimum-version': {
@@ -499,6 +504,11 @@ export const SOLVE_OPTION_DEFINITIONS = {
499
504
  description: 'Create comprehensive case study documentation for the issue including logs, analysis, timeline, root cause investigation, and proposed solutions. Organizes findings into ./docs/case-studies/issue-{id}/ directory. Supported for --tool claude and --tool codex.',
500
505
  default: false,
501
506
  },
507
+ 'development-log': {
508
+ type: 'boolean',
509
+ description: 'Prompt for issue-data collection under ./dev/log/issues/{issue-id}/pulls/{pull-id}, preserve native tool state under sessions/{UUID}, and commit the artifacts when solve finishes. Supported for --tool claude, --tool codex, --tool opencode, --tool agent, --tool qwen, and --tool gemini.',
510
+ default: false,
511
+ },
502
512
  'use-handoff': {
503
513
  type: 'boolean',
504
514
  description: '[EXPERIMENTAL] Enable the HANDOFF.md continuity Agent Skill so a session can continue the work of a previous session — even when a different AI tool is used (e.g. Claude and Codex continuing each other in the same pull request). A real SKILL.md (the open Agent Skills standard) is deployed into the working directory so each tool loads it natively (.claude/skills/handoff/ for Claude, .agents/skills/handoff/ for Codex). The AI reads HANDOFF.md (repository root) first when present and keeps it updated with task, current state, decisions, next steps, gotchas, and critical files. HANDOFF.md is committed to the PR branch so it persists across the ephemeral per-session working directories; the SKILL.md itself is re-deployed each session and git-excluded so it never pollutes the PR. The same skill file is used identically for --tool claude and --tool codex. Disabled by default (issue #1877).',
package/src/solve.mjs CHANGED
@@ -21,7 +21,7 @@ const fs = (await use('fs')).promises;
21
21
  const crypto = (await use('crypto')).default;
22
22
  const memoryCheck = await import('./memory-check.mjs');
23
23
  const lib = await import('./lib.mjs');
24
- const { log, setLogFile, getLogFile, getAbsoluteLogPath, cleanErrorMessage, formatAligned, formatToolExecutionFailure, getVersionInfo, setupVerboseLogInterceptor, setupStdioLogInterceptor } = lib;
24
+ const { log, setLogFile, getLogFile, getAbsoluteLogPath, cleanErrorMessage, formatAligned, formatToolExecutionFailure, getVersionInfo, logSolveStartup, setupVerboseLogInterceptor, setupStdioLogInterceptor } = lib;
25
25
  const githubLib = await import('./github.lib.mjs');
26
26
  const { sanitizeLogContent, attachLogToGitHub, getToolDisplayName } = githubLib;
27
27
  const validation = await import('./solve.validation.lib.mjs');
@@ -62,6 +62,8 @@ const { recordAfterCloneSize, recordAfterAgentSize } = await import('./solve.dis
62
62
  const { createOrCheckoutBranch } = await import('./solve.branch.lib.mjs');
63
63
  const { startWorkSession, endWorkSession, SESSION_TYPES } = await import('./solve.session.lib.mjs');
64
64
  const { attachFinalLogIfMissing } = await import('./attach-logs-guarantee.lib.mjs'); // Issue #1952
65
+ const { collectAndCommitDevelopmentLogArtifacts, fetchIssueType, isDevelopmentLogEnabled } = await import('./development-log.lib.mjs');
66
+ const { createDevelopmentLogFinalizer } = await import('./development-log.finalize.lib.mjs');
65
67
  // Issue #1625: centralized markers + tracked comment posting for solve.mjs's
66
68
  // own usage-limit notifications (so they're excluded from the
67
69
  // "did the AI post anything?" check in --auto-attach-solution-summary).
@@ -72,12 +74,7 @@ const { autoAcceptInviteForRepo } = await import('./solve.accept-invite.lib.mjs'
72
74
  const { handleAutoForkOption, handleMaintainerForkAccess } = await import('./solve.fork-detection.lib.mjs');
73
75
  const logFile = await initializeLogFile(null);
74
76
  const versionInfo = await getVersionInfo();
75
- await log('');
76
- await log(`🚀 solve v${versionInfo}`);
77
- const rawCommand = process.argv.join(' ');
78
- await log('🔧 Raw command executed:');
79
- await log(` ${rawCommand}`);
80
- await log('');
77
+ const rawCommand = await logSolveStartup(versionInfo);
81
78
 
82
79
  let finalResourceSnapshotRecorded = false;
83
80
  const safeExit = async (code = 0, reason = 'Process completed', options = {}) => {
@@ -489,6 +486,8 @@ if (isPrUrl) {
489
486
  }
490
487
  // Issues #1212, #1462: Store issueNumber globally for error handlers (attach failure logs to issue when no PR exists)
491
488
  global.issueNumber = issueNumber;
489
+ // Issue #1596: detect the issue type so the development-log prompt automatically uses bug vs feature/task wording.
490
+ if (isDevelopmentLogEnabled(argv) && issueNumber) argv.issueType = await fetchIssueType({ owner, repo, issueNumber, $, log });
492
491
  const workspaceInfo = argv.enableWorkspaces ? { owner, repo, issueNumber } : null;
493
492
  const { tempDir, workspaceTmpDir, needsClone } = await setupTempDirectory(argv, workspaceInfo);
494
493
  cleanupContext.tempDir = tempDir;
@@ -497,6 +496,12 @@ cleanupContext.owner = owner;
497
496
  cleanupContext.repo = repo;
498
497
  if (prNumber) cleanupContext.prNumber = prNumber;
499
498
  let limitReached = false;
499
+ let sessionId = null;
500
+ let branchName = null;
501
+ const finalizeDevelopmentLog = createDevelopmentLogFinalizer({
502
+ collect: collectAndCommitDevelopmentLogArtifacts,
503
+ getParams: () => ({ enabled: isDevelopmentLogEnabled(argv), repositoryPath: tempDir, logFile: getLogFile(), issueNumber, prNumber, tool: argv.tool || 'claude', sessionId, branchName, rawCommand, $, log }), // prettier-ignore
504
+ });
500
505
  try {
501
506
  // Set up repository and clone using the new module
502
507
  // If --working-directory points to existing repo, needsClone is false and we skip cloning
@@ -531,7 +536,7 @@ try {
531
536
  issueUrl,
532
537
  });
533
538
  // Create or checkout branch using the new module
534
- const branchName = await createOrCheckoutBranch({
539
+ branchName = await createOrCheckoutBranch({
535
540
  isContinueMode,
536
541
  prBranch,
537
542
  issueNumber,
@@ -861,7 +866,7 @@ try {
861
866
  }
862
867
 
863
868
  const { success } = toolResult;
864
- let sessionId = toolResult.sessionId;
869
+ sessionId = toolResult.sessionId;
865
870
  let anthropicTotalCostUSD = toolResult.anthropicTotalCostUSD;
866
871
  let publicPricingEstimate = toolResult.publicPricingEstimate; // Used by agent tool
867
872
  let pricingInfo = toolResult.pricingInfo; // Used by agent tool for detailed pricing
@@ -1461,17 +1466,10 @@ try {
1461
1466
  // Issue #1516: Cleanup after all signals (was before verifyResults, caused premature commits)
1462
1467
  await cleanupClaudeFile(tempDir, branchName, claudeCommitHash, argv);
1463
1468
 
1464
- // End work session using the new module
1465
- await endWorkSession({
1466
- isContinueMode,
1467
- prNumber,
1468
- argv,
1469
- log,
1470
- formatAligned,
1471
- $,
1472
- logsAttached,
1473
- });
1469
+ await finalizeDevelopmentLog(); // Issue #1596: preserve session before ending work.
1470
+ await endWorkSession({ isContinueMode, prNumber, argv, log, formatAligned, $, logsAttached });
1474
1471
  } catch (error) {
1472
+ await finalizeDevelopmentLog(); // Preserve failed/interrupted sessions too.
1475
1473
  // Don't report authentication errors to Sentry as they are user configuration issues
1476
1474
  if (!error.isAuthError) {
1477
1475
  reportError(error, {
@@ -8,6 +8,7 @@ export const THINK_PROMPT_MESSAGES = Object.freeze({
8
8
  medium: 'Think hard.',
9
9
  high: 'Think harder.',
10
10
  xhigh: 'Ultrathink.',
11
+ ultra: 'Ultrathink.',
11
12
  max: 'Ultrathink.',
12
13
  });
13
14