commandmate 0.2.2 → 0.2.4

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 (138) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +46 -50
  3. package/.next/app-path-routes-manifest.json +1 -1
  4. package/.next/build-manifest.json +9 -9
  5. package/.next/cache/.tsbuildinfo +1 -1
  6. package/.next/cache/config.json +3 -3
  7. package/.next/cache/fetch-cache/799a63cbfa61e2ab38626c05fe43500464c7bbd38341bdde69f5ec4b25acff68 +1 -0
  8. package/.next/cache/webpack/client-production/0.pack +0 -0
  9. package/.next/cache/webpack/client-production/1.pack +0 -0
  10. package/.next/cache/webpack/client-production/2.pack +0 -0
  11. package/.next/cache/webpack/client-production/index.pack +0 -0
  12. package/.next/cache/webpack/client-production/index.pack.old +0 -0
  13. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  14. package/.next/cache/webpack/server-production/0.pack +0 -0
  15. package/.next/cache/webpack/server-production/index.pack +0 -0
  16. package/.next/next-minimal-server.js.nft.json +1 -1
  17. package/.next/next-server.js.nft.json +1 -1
  18. package/.next/prerender-manifest.json +1 -1
  19. package/.next/required-server-files.json +1 -1
  20. package/.next/routes-manifest.json +1 -1
  21. package/.next/server/app/_not-found/page.js +1 -1
  22. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  23. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  24. package/.next/server/app/api/app/update-check/route.js +1 -0
  25. package/.next/server/app/{worktrees/[id]/simple-terminal/page.js.nft.json → api/app/update-check/route.js.nft.json} +1 -1
  26. package/.next/server/app/api/app/update-check.body +1 -0
  27. package/.next/server/app/api/app/update-check.meta +1 -0
  28. package/.next/server/app/api/repositories/restore/route.js +1 -1
  29. package/.next/server/app/api/repositories/route.js +1 -1
  30. package/.next/server/app/api/repositories/scan/route.js +1 -1
  31. package/.next/server/app/api/repositories/sync/route.js +1 -1
  32. package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
  33. package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js +1 -1
  34. package/.next/server/app/api/worktrees/[id]/logs/route.js +4 -4
  35. package/.next/server/app/api/worktrees/[id]/route.js +1 -1
  36. package/.next/server/app/api/worktrees/[id]/search/route.js +1 -1
  37. package/.next/server/app/api/worktrees/route.js +1 -1
  38. package/.next/server/app/page.js +2 -4
  39. package/.next/server/app/page.js.nft.json +1 -1
  40. package/.next/server/app/page_client-reference-manifest.js +1 -1
  41. package/.next/server/app/proxy/[...path]/route.js +1 -1
  42. package/.next/server/app/worktrees/[id]/files/[...path]/page.js +1 -1
  43. package/.next/server/app/worktrees/[id]/files/[...path]/page.js.nft.json +1 -1
  44. package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
  45. package/.next/server/app/worktrees/[id]/page.js +3 -15
  46. package/.next/server/app/worktrees/[id]/page.js.nft.json +1 -1
  47. package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
  48. package/.next/server/app/worktrees/[id]/terminal/page.js +3 -3
  49. package/.next/server/app/worktrees/[id]/terminal/page.js.nft.json +1 -1
  50. package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
  51. package/.next/server/app-paths-manifest.json +13 -13
  52. package/.next/server/chunks/1287.js +4 -0
  53. package/.next/server/chunks/2683.js +1 -0
  54. package/.next/server/chunks/3348.js +1 -0
  55. package/.next/server/chunks/369.js +1 -0
  56. package/.next/server/chunks/3860.js +1 -0
  57. package/.next/server/chunks/4559.js +1 -0
  58. package/.next/server/chunks/4704.js +35 -0
  59. package/.next/server/chunks/5488.js +1 -1
  60. package/.next/server/chunks/5781.js +1 -0
  61. package/.next/server/chunks/5823.js +1 -0
  62. package/.next/server/chunks/5853.js +1 -0
  63. package/.next/server/chunks/6837.js +1 -0
  64. package/.next/server/chunks/7266.js +1 -0
  65. package/.next/server/chunks/7425.js +3 -3
  66. package/.next/server/chunks/7458.js +1 -0
  67. package/.next/server/chunks/7536.js +1 -1
  68. package/.next/server/chunks/8705.js +1 -0
  69. package/.next/server/chunks/8744.js +1 -0
  70. package/.next/server/chunks/9367.js +2 -2
  71. package/.next/server/chunks/9582.js +1 -0
  72. package/.next/server/functions-config-manifest.json +1 -1
  73. package/.next/server/middleware-build-manifest.js +1 -1
  74. package/.next/server/pages/500.html +1 -1
  75. package/.next/server/pages-manifest.json +1 -1
  76. package/.next/server/server-reference-manifest.json +1 -1
  77. package/.next/static/chunks/1038-3509435b68c0967e.js +1 -0
  78. package/.next/static/chunks/216-f18f4a9d8b04a91e.js +1 -0
  79. package/.next/static/chunks/2330-0299b9879f4977d2.js +1 -0
  80. package/.next/static/chunks/4733-50bdfc169adb4881.js +1 -0
  81. package/.next/static/chunks/6140-389f1951062dcf4f.js +1 -0
  82. package/.next/static/chunks/816-af44cb865b0c980e.js +1 -0
  83. package/.next/static/chunks/8216-00e20326f32abd12.js +1 -0
  84. package/.next/static/chunks/9234-b0304101384ca079.js +3 -0
  85. package/.next/static/chunks/app/layout-07755491d5d57242.js +1 -0
  86. package/.next/static/chunks/app/page-43b5de1a0a788b1f.js +1 -0
  87. package/.next/static/chunks/app/worktrees/[id]/files/[...path]/page-ce9ac3658f2b7d91.js +1 -0
  88. package/.next/static/chunks/app/worktrees/[id]/page-9632761937a4d1ad.js +1 -0
  89. package/.next/static/chunks/app/worktrees/[id]/terminal/page-5d85a7e508ce36d3.js +1 -0
  90. package/.next/static/chunks/{main-b6d727aa9248d4f2.js → main-f00f82f1cf18dd99.js} +1 -1
  91. package/.next/static/chunks/{webpack-4f85dcef6279c6ee.js → webpack-af8567a485ade35a.js} +1 -1
  92. package/.next/static/css/6a92c8ad3c94d15a.css +3 -0
  93. package/.next/trace +5 -5
  94. package/.next/types/app/api/app/update-check/route.ts +343 -0
  95. package/README.md +84 -82
  96. package/dist/server/src/config/auto-yes-config.js +4 -4
  97. package/dist/server/src/config/log-config.js +41 -0
  98. package/dist/server/src/lib/cli-tools/manager.js +0 -6
  99. package/dist/server/src/lib/log-manager.js +2 -6
  100. package/dist/server/src/lib/prompt-detector.js +272 -75
  101. package/dist/server/src/lib/response-poller.js +29 -43
  102. package/package.json +2 -1
  103. package/.next/server/app/_not-found.html +0 -1
  104. package/.next/server/app/_not-found.meta +0 -6
  105. package/.next/server/app/_not-found.rsc +0 -10
  106. package/.next/server/app/index.html +0 -9
  107. package/.next/server/app/index.meta +0 -5
  108. package/.next/server/app/index.rsc +0 -8
  109. package/.next/server/app/worktrees/[id]/simple-terminal/page.js +0 -4
  110. package/.next/server/app/worktrees/[id]/simple-terminal/page_client-reference-manifest.js +0 -1
  111. package/.next/server/chunks/3053.js +0 -1
  112. package/.next/server/chunks/434.js +0 -1
  113. package/.next/server/chunks/4471.js +0 -2
  114. package/.next/server/chunks/6550.js +0 -1
  115. package/.next/server/chunks/8174.js +0 -23
  116. package/.next/server/chunks/8887.js +0 -1
  117. package/.next/server/pages/404.html +0 -1
  118. package/.next/static/chunks/4343-ebe884a2a80eb033.js +0 -1
  119. package/.next/static/chunks/4851-45df4d388db5623f.js +0 -1
  120. package/.next/static/chunks/6568-c65d7e4d7db7655b.js +0 -1
  121. package/.next/static/chunks/6725-f7607851b7d57eb1.js +0 -1
  122. package/.next/static/chunks/7648-325564a6e12a3257.js +0 -1
  123. package/.next/static/chunks/816-c254f4e2406e696a.js +0 -1
  124. package/.next/static/chunks/9325-9e98829c1e75f42f.js +0 -1
  125. package/.next/static/chunks/app/layout-4804cfba519283cf.js +0 -1
  126. package/.next/static/chunks/app/page-3926224c4cdf315b.js +0 -1
  127. package/.next/static/chunks/app/worktrees/[id]/files/[...path]/page-7eb14f8043796805.js +0 -1
  128. package/.next/static/chunks/app/worktrees/[id]/page-912c3c4c66821d99.js +0 -1
  129. package/.next/static/chunks/app/worktrees/[id]/simple-terminal/page-16feb3e86e42f4d1.js +0 -1
  130. package/.next/static/chunks/app/worktrees/[id]/terminal/page-be802baffc84dbd2.js +0 -1
  131. package/.next/static/css/d4b58a1129eff6af.css +0 -3
  132. package/.next/types/app/worktrees/[id]/simple-terminal/page.ts +0 -79
  133. package/dist/server/src/lib/claude-poller.js +0 -341
  134. /package/.next/static/{HhG0EHeG9E4wTJ4sqnLdv → b3UR0y5mw3Ubf_vI5JjIN}/_buildManifest.js +0 -0
  135. /package/.next/static/{HhG0EHeG9E4wTJ4sqnLdv → b3UR0y5mw3Ubf_vI5JjIN}/_ssgManifest.js +0 -0
  136. /package/.next/static/chunks/{2117-d845c2cd62e344a6.js → 2117-e31fa477cb500950.js} +0 -0
  137. /package/.next/static/chunks/app/_not-found/{page-a9d04e58c81115ec.js → page-ac4e4463b39d0cf7.js} +0 -0
  138. /package/.next/static/chunks/{fd9d1056-bbe86e4ae099d5cd.js → fd9d1056-cfdf4f91f13d3485.js} +0 -0
@@ -16,11 +16,7 @@ exports.cleanupOldLogs = cleanupOldLogs;
16
16
  const promises_1 = __importDefault(require("fs/promises"));
17
17
  const path_1 = __importDefault(require("path"));
18
18
  const date_fns_1 = require("date-fns");
19
- const env_1 = require("./env");
20
- /**
21
- * Log directory configuration (with fallback support - Issue #76)
22
- */
23
- const LOG_DIR = (0, env_1.getEnvByKey)('CM_LOG_DIR') || path_1.default.join(process.cwd(), 'data', 'logs');
19
+ const log_config_1 = require("../config/log-config");
24
20
  /**
25
21
  * Get log directory for a CLI tool
26
22
  *
@@ -28,7 +24,7 @@ const LOG_DIR = (0, env_1.getEnvByKey)('CM_LOG_DIR') || path_1.default.join(proc
28
24
  * @returns Log directory path
29
25
  */
30
26
  function getCliToolLogDir(cliToolId = 'claude') {
31
- return path_1.default.join(LOG_DIR, cliToolId);
27
+ return path_1.default.join((0, log_config_1.getLogDir)(), cliToolId);
32
28
  }
33
29
  /**
34
30
  * Ensure log directory exists
@@ -8,6 +8,40 @@ exports.detectPrompt = detectPrompt;
8
8
  exports.getAnswerInput = getAnswerInput;
9
9
  const logger_1 = require("./logger");
10
10
  const logger = (0, logger_1.createLogger)('prompt-detector');
11
+ /**
12
+ * Maximum number of lines to retain in rawContent.
13
+ * Tail lines are preserved (instruction text typically appears just before the prompt).
14
+ * @see truncateRawContent
15
+ */
16
+ const RAW_CONTENT_MAX_LINES = 200;
17
+ /**
18
+ * Maximum number of characters to retain in rawContent.
19
+ * Tail characters are preserved.
20
+ * @see truncateRawContent
21
+ */
22
+ const RAW_CONTENT_MAX_CHARS = 5000;
23
+ /**
24
+ * Truncate raw content to fit within size limits.
25
+ * Preserves the tail (end) of the content since instruction text
26
+ * typically appears just before the prompt at the end of output.
27
+ *
28
+ * Security: No regular expressions used -- no ReDoS risk. [SF-S4-002]
29
+ * String.split('\n') and String.slice() are literal string operations only.
30
+ *
31
+ * @param content - The content to truncate
32
+ * @returns Truncated content (last RAW_CONTENT_MAX_LINES lines, max RAW_CONTENT_MAX_CHARS characters)
33
+ */
34
+ function truncateRawContent(content) {
35
+ const lines = content.split('\n');
36
+ const truncatedLines = lines.length > RAW_CONTENT_MAX_LINES
37
+ ? lines.slice(-RAW_CONTENT_MAX_LINES)
38
+ : lines;
39
+ let result = truncatedLines.join('\n');
40
+ if (result.length > RAW_CONTENT_MAX_CHARS) {
41
+ result = result.slice(-RAW_CONTENT_MAX_CHARS);
42
+ }
43
+ return result;
44
+ }
11
45
  /**
12
46
  * Yes/no pattern definitions for data-driven matching.
13
47
  * Each entry defines a regex pattern and its associated default option.
@@ -27,6 +61,32 @@ const YES_NO_PATTERNS = [
27
61
  // (yes/no) - no default
28
62
  { regex: /^(.+)\s+\(yes\/no\)\s*$/m },
29
63
  ];
64
+ /**
65
+ * Creates a yes/no prompt detection result.
66
+ * Centralizes the repeated construction of yes_no PromptDetectionResult objects
67
+ * used by both YES_NO_PATTERNS matching and Approve pattern matching.
68
+ *
69
+ * @param question - The question text
70
+ * @param cleanContent - The clean content string
71
+ * @param rawContent - The raw content string (last 20 lines, trimmed)
72
+ * @param defaultOption - Optional default option ('yes' or 'no')
73
+ * @returns PromptDetectionResult with isPrompt: true and yes_no prompt data
74
+ */
75
+ function yesNoPromptResult(question, cleanContent, rawContent, defaultOption) {
76
+ return {
77
+ isPrompt: true,
78
+ promptData: {
79
+ type: 'yes_no',
80
+ question,
81
+ options: ['yes', 'no'],
82
+ status: 'pending',
83
+ ...(defaultOption !== undefined && { defaultOption }),
84
+ instructionText: rawContent,
85
+ },
86
+ cleanContent,
87
+ rawContent,
88
+ };
89
+ }
30
90
  /**
31
91
  * Detect if output contains an interactive prompt
32
92
  *
@@ -51,7 +111,8 @@ const YES_NO_PATTERNS = [
51
111
  function detectPrompt(output, options) {
52
112
  logger.debug('detectPrompt:start', { outputLength: output.length });
53
113
  const lines = output.split('\n');
54
- const lastLines = lines.slice(-10).join('\n');
114
+ // [SF-003] [MF-S2-001] Expanded from 10 to 20 lines for rawContent coverage
115
+ const lastLines = lines.slice(-20).join('\n');
55
116
  // Pattern 0: Multiple choice (numbered options with ❯ indicator)
56
117
  // Example:
57
118
  // Do you want to proceed?
@@ -68,21 +129,12 @@ function detectPrompt(output, options) {
68
129
  return multipleChoiceResult;
69
130
  }
70
131
  // Patterns 1-4: Yes/no patterns (data-driven matching)
132
+ const trimmedLastLines = lastLines.trim();
71
133
  for (const pattern of YES_NO_PATTERNS) {
72
134
  const match = lastLines.match(pattern.regex);
73
135
  if (match) {
74
136
  const question = match[1].trim();
75
- return {
76
- isPrompt: true,
77
- promptData: {
78
- type: 'yes_no',
79
- question,
80
- options: ['yes', 'no'],
81
- status: 'pending',
82
- ...(pattern.defaultOption !== undefined && { defaultOption: pattern.defaultOption }),
83
- },
84
- cleanContent: question,
85
- };
137
+ return yesNoPromptResult(question, question, trimmedLastLines, pattern.defaultOption);
86
138
  }
87
139
  }
88
140
  // Pattern 5: Approve?
@@ -93,16 +145,7 @@ function detectPrompt(output, options) {
93
145
  const content = approveMatch[1].trim();
94
146
  // If there's content before "Approve?", include it in the question
95
147
  const question = content ? `${content} Approve?` : 'Approve?';
96
- return {
97
- isPrompt: true,
98
- promptData: {
99
- type: 'yes_no',
100
- question: question,
101
- options: ['yes', 'no'],
102
- status: 'pending',
103
- },
104
- cleanContent: content || 'Approve?',
105
- };
148
+ return yesNoPromptResult(question, content || 'Approve?', trimmedLastLines);
106
149
  }
107
150
  // No prompt detected
108
151
  logger.debug('detectPrompt:complete', { isPrompt: false });
@@ -141,6 +184,28 @@ const NORMAL_OPTION_PATTERN = /^\s*(\d+)\.\s*(.+)$/;
141
184
  * Anchored at both ends -- ReDoS safe (S4-001).
142
185
  */
143
186
  const SEPARATOR_LINE_PATTERN = /^[-─]+$/;
187
+ /**
188
+ * Maximum number of lines to scan upward from questionEndIndex
189
+ * when the questionEndIndex line itself is not a question-like line.
190
+ *
191
+ * Design rationale (IC-256-001):
192
+ * - model selection prompts have 1-2 lines between "Select model" and first option
193
+ * - multi-line question wrapping typically produces 2-3 continuation lines
194
+ * - value of 3 covers these cases while minimizing False Positive surface
195
+ *
196
+ * [SF-002] Change guidelines:
197
+ * - Increase this value ONLY if real-world prompts are discovered where
198
+ * the question line is more than 3 lines above questionEndIndex
199
+ * - Before increasing, verify that the new value does not cause
200
+ * T11h-T11m False Positive tests to fail
201
+ * - Consider that larger values increase the False Positive surface area
202
+ * - If increasing beyond 5, consider whether the detection approach
203
+ * itself needs to be redesigned (e.g., pattern-based instead of scan-based)
204
+ * - Document the specific prompt pattern that necessitated the change
205
+ *
206
+ * @see Issue #256: multiple_choice prompt detection improvement
207
+ */
208
+ const QUESTION_SCAN_RANGE = 3;
144
209
  /**
145
210
  * Creates a "no prompt detected" result.
146
211
  * Centralizes the repeated pattern of returning isPrompt: false with trimmed content.
@@ -201,17 +266,75 @@ function isQuestionLikeLine(line) {
201
266
  // Empty lines are not questions
202
267
  if (line.length === 0)
203
268
  return false;
204
- // Pattern 1: Lines ending with question mark (English or full-width Japanese)
269
+ // Pattern 1: Lines containing question mark anywhere (English '?' or full-width U+FF1F).
270
+ // This covers both:
271
+ // - Lines ending with '?' (standard question format)
272
+ // - Lines with '?' mid-line (Issue #256: multi-line question wrapping where '?'
273
+ // appears mid-line due to terminal width causing the question text to wrap)
274
+ //
205
275
  // Full-width question mark (U+FF1F) support is a defensive measure: Claude Code/CLI
206
276
  // displays questions in English, but this covers future multi-language support
207
277
  // and third-party tool integration.
208
- if (line.endsWith('?') || line.endsWith('\uff1f'))
278
+ //
279
+ // [SF-001] Scope constraints:
280
+ // - The mid-line '?' detection is effective without False Positive risk only within
281
+ // SEC-001b guard context (questionEndIndex vicinity and upward scan range).
282
+ // - isQuestionLikeLine() is currently module-private (no export).
283
+ // - If this function is exported for external use in the future, consider:
284
+ // (a) Providing a stricter variant (e.g., isStrictQuestionLikeLine()) without mid-line match
285
+ // (b) Separating mid-line match into a SEC-001b-specific helper function
286
+ // (c) Adding URL exclusion logic (/[?&]\w+=/.test(line) to exclude)
287
+ if (line.includes('?') || line.includes('\uff1f'))
209
288
  return true;
210
- // Pattern 2: Lines ending with colon that contain a selection/input keyword
211
- // Examples: "Select an option:", "Choose a mode:", "Pick one:"
212
- if (line.endsWith(':')) {
213
- if (QUESTION_KEYWORD_PATTERN.test(line))
289
+ // Pattern 2: Lines containing a selection/input keyword.
290
+ // Detects both colon-terminated (e.g., "Select an option:", "Choose a mode:") and
291
+ // non-colon forms (e.g., "Select model") used by CLI prompts (Issue #256).
292
+ //
293
+ // [SF-001] Scope constraints apply:
294
+ // - Effective without False Positive risk only within SEC-001b guard context.
295
+ // - T11h-T11m False Positive lines do not contain QUESTION_KEYWORD_PATTERN keywords.
296
+ // - If this function is exported, consider restricting this pattern to SEC-001b context.
297
+ if (QUESTION_KEYWORD_PATTERN.test(line))
298
+ return true;
299
+ return false;
300
+ }
301
+ /**
302
+ * Search upward from a given line index to find a question-like line.
303
+ * Skips empty lines and separator lines (horizontal rules).
304
+ *
305
+ * This function is used by SEC-001b guard to find a question line above
306
+ * questionEndIndex when the questionEndIndex line itself is not a question-like line.
307
+ * This handles cases where the question text wraps across multiple lines or
308
+ * where description lines appear between the question and the numbered options.
309
+ *
310
+ * @param lines - Array of output lines
311
+ * @param startIndex - Starting line index (exclusive, searches startIndex-1 and above)
312
+ * @param scanRange - Maximum number of lines to scan upward (must be >= 0, clamped to MAX_SCAN_RANGE=10)
313
+ * @param lowerBound - Minimum line index (inclusive, scan will not go below this)
314
+ * @returns true if a question-like line is found within the scan range
315
+ *
316
+ * @see IC-256-002: SEC-001b upward scan implementation
317
+ * @see SF-003: Function extraction for readability
318
+ * @see SF-S4-001: scanRange input validation (defensive clamping)
319
+ *
320
+ * ReDoS safe: Uses SEPARATOR_LINE_PATTERN (existing ReDoS safe pattern) and
321
+ * isQuestionLikeLine() (literal character checks + simple alternation pattern).
322
+ * No new regex patterns introduced. (C-S4-001)
323
+ */
324
+ function findQuestionLineInRange(lines, startIndex, scanRange, lowerBound) {
325
+ // [SF-S4-001] Defensive input validation: clamp scanRange to safe bounds.
326
+ // Currently only called with QUESTION_SCAN_RANGE=3, but guards against
327
+ // future misuse if the function is refactored or exported.
328
+ const safeScanRange = Math.min(Math.max(scanRange, 0), 10);
329
+ const scanLimit = Math.max(lowerBound, startIndex - safeScanRange);
330
+ for (let i = startIndex - 1; i >= scanLimit; i--) {
331
+ const candidateLine = lines[i]?.trim() ?? '';
332
+ // Skip empty lines and separator lines (horizontal rules)
333
+ if (!candidateLine || SEPARATOR_LINE_PATTERN.test(candidateLine))
334
+ continue;
335
+ if (isQuestionLikeLine(candidateLine)) {
214
336
  return true;
337
+ }
215
338
  }
216
339
  return false;
217
340
  }
@@ -265,19 +388,103 @@ function isConsecutiveFromOne(numbers) {
265
388
  * @returns true if the line should be treated as a continuation of a previous option
266
389
  */
267
390
  function isContinuationLine(rawLine, line) {
268
- // Indented non-option line.
269
- // Excludes lines ending with '?' or '?' (U+FF1F) because those are typically question lines
391
+ // Lines ending with '?' or full-width '?' (U+FF1F) are typically question lines
270
392
  // (e.g., " Do you want to proceed?", " コピーしたい対象はどれですか?") from CLI tool output
271
- // where both the question and options are 2-space indented. Without this exclusion,
272
- // the question line would be misclassified as a continuation line, causing
273
- // questionEndIndex to remain -1 and Layer 5 SEC-001 to block detection.
393
+ // where both the question and options are 2-space indented. These must NOT be
394
+ // treated as continuation lines, otherwise questionEndIndex remains -1 and
395
+ // Layer 5 SEC-001 blocks detection.
274
396
  const endsWithQuestion = line.endsWith('?') || line.endsWith('\uff1f');
275
- const hasLeadingSpaces = rawLine.match(/^\s{2,}[^\d]/) && !rawLine.match(/^\s*\d+\./) && !endsWithQuestion;
276
- // Short fragment (< 5 chars, excluding question-ending lines)
277
- const isShortFragment = line.length < 5 && !endsWithQuestion;
278
- // Path string continuation: lines starting with / or ~, or alphanumeric-only fragments (2+ chars)
279
- const isPathContinuation = /^[\/~]/.test(line) || (line.length >= 2 && /^[a-zA-Z0-9_-]+$/.test(line));
280
- return !!hasLeadingSpaces || isShortFragment || isPathContinuation;
397
+ // Check 1: Indented non-option line (label text wrapping with indentation).
398
+ // Must have 2+ leading spaces, not start with a number (option line), and not end with '?'.
399
+ if (!endsWithQuestion && /^\s{2,}[^\d]/.test(rawLine) && !/^\s*\d+\./.test(rawLine)) {
400
+ return true;
401
+ }
402
+ // Check 2: Short fragment (< 5 chars, e.g., filename tail).
403
+ // Excludes question-ending lines to prevent misclassifying short questions.
404
+ if (line.length < 5 && !endsWithQuestion) {
405
+ return true;
406
+ }
407
+ // Check 3: Path string continuation (Issue #181).
408
+ // Lines starting with / or ~, or alphanumeric-only fragments (2+ chars).
409
+ if (/^[\/~]/.test(line) || (line.length >= 2 && /^[a-zA-Z0-9_-]+$/.test(line))) {
410
+ return true;
411
+ }
412
+ return false;
413
+ }
414
+ /**
415
+ * Extract question text from the lines around questionEndIndex.
416
+ * Collects non-empty, non-separator lines from up to 5 lines before questionEndIndex
417
+ * through questionEndIndex itself, joining them with spaces.
418
+ *
419
+ * @param lines - Array of output lines
420
+ * @param questionEndIndex - Index of the last line before options, or -1 if not found
421
+ * @returns Extracted question text, or generic fallback if questionEndIndex is -1
422
+ */
423
+ function extractQuestionText(lines, questionEndIndex) {
424
+ if (questionEndIndex < 0) {
425
+ return 'Please select an option:';
426
+ }
427
+ const questionLines = [];
428
+ for (let i = Math.max(0, questionEndIndex - 5); i <= questionEndIndex; i++) {
429
+ const line = lines[i].trim();
430
+ if (line && !SEPARATOR_LINE_PATTERN.test(line)) {
431
+ questionLines.push(line);
432
+ }
433
+ }
434
+ return questionLines.join(' ');
435
+ }
436
+ /**
437
+ * Extract instruction text for the prompt block.
438
+ * Captures the complete AskUserQuestion block including context before the question,
439
+ * option descriptions, and navigation hints.
440
+ *
441
+ * @param lines - Array of output lines
442
+ * @param questionEndIndex - Index of the last line before options, or -1 if not found
443
+ * @param effectiveEnd - End index of non-trailing-empty lines
444
+ * @returns Instruction text string, or undefined if no question line found
445
+ */
446
+ function extractInstructionText(lines, questionEndIndex, effectiveEnd) {
447
+ if (questionEndIndex < 0) {
448
+ return undefined;
449
+ }
450
+ const contextStart = Math.max(0, questionEndIndex - 19);
451
+ const blockLines = lines.slice(contextStart, effectiveEnd)
452
+ .map(l => l.trimEnd());
453
+ const joined = blockLines.join('\n').trim();
454
+ return joined.length > 0 ? joined : undefined;
455
+ }
456
+ /**
457
+ * Build the final PromptDetectionResult for a multiple choice prompt.
458
+ * Maps collected options to the output format, checking each option for
459
+ * text input requirements using TEXT_INPUT_PATTERNS.
460
+ *
461
+ * @param question - Extracted question text
462
+ * @param collectedOptions - Options collected during Pass 2 scanning
463
+ * @param instructionText - Instruction text for the prompt block
464
+ * @param output - Original output text (used for rawContent truncation)
465
+ * @returns PromptDetectionResult with isPrompt: true and multiple_choice data
466
+ */
467
+ function buildMultipleChoiceResult(question, collectedOptions, instructionText, output) {
468
+ return {
469
+ isPrompt: true,
470
+ promptData: {
471
+ type: 'multiple_choice',
472
+ question: question.trim(),
473
+ options: collectedOptions.map(opt => {
474
+ const requiresTextInput = TEXT_INPUT_PATTERNS.some(pattern => pattern.test(opt.label));
475
+ return {
476
+ number: opt.number,
477
+ label: opt.label,
478
+ isDefault: opt.isDefault,
479
+ requiresTextInput,
480
+ };
481
+ }),
482
+ status: 'pending',
483
+ instructionText,
484
+ },
485
+ cleanContent: question.trim(),
486
+ rawContent: truncateRawContent(output.trim()), // Issue #235: complete prompt output (truncated) [MF-001]
487
+ };
281
488
  }
282
489
  /**
283
490
  * Detect multiple choice prompts (numbered list with ❯ indicator)
@@ -356,6 +563,20 @@ function detectMultipleChoicePrompt(output, options) {
356
563
  }
357
564
  // Non-option line handling
358
565
  if (collectedOptions.length > 0 && line && !SEPARATOR_LINE_PATTERN.test(line)) {
566
+ // [MF-001 / Issue #256] Check if line is a question-like line BEFORE
567
+ // continuation check. This preserves isContinuationLine()'s SRP by not
568
+ // mixing question detection into it. Without this pre-check, indented
569
+ // question lines (e.g., " Select model") could be misclassified as
570
+ // continuation lines by isContinuationLine()'s hasLeadingSpaces check.
571
+ //
572
+ // [SF-S4-003] Both this pre-check and SEC-001b upward scan use the same
573
+ // isQuestionLikeLine() function intentionally (DRY). If a question line is
574
+ // caught here, SEC-001b upward scan is not needed (questionEndIndex line
575
+ // itself passes isQuestionLikeLine()).
576
+ if (isQuestionLikeLine(line)) {
577
+ questionEndIndex = i;
578
+ break;
579
+ }
359
580
  // Check if this is a continuation line (indented line between options,
360
581
  // or path/filename fragments from terminal width wrapping - Issue #181)
361
582
  const rawLine = lines[i]; // Original line with indentation preserved
@@ -392,47 +613,23 @@ function detectMultipleChoicePrompt(output, options) {
392
613
  // SEC-001b: Question line exists but is not actually a question/selection request.
393
614
  // Validates that the question line contains a question mark or a selection keyword
394
615
  // with colon, distinguishing "Select an option:" from "Recommendations:".
616
+ //
617
+ // [Issue #256] Enhanced with upward scan via findQuestionLineInRange() (SF-003).
618
+ // When questionEndIndex line itself is not a question-like line, scan upward
619
+ // within QUESTION_SCAN_RANGE to find a question line above it. This handles:
620
+ // - Multi-line question wrapping where ? is on a line above questionEndIndex
621
+ // - Model selection prompts where "Select model" is above description lines
395
622
  const questionLine = lines[questionEndIndex]?.trim() ?? '';
396
623
  if (!isQuestionLikeLine(questionLine)) {
397
- return noPromptResult(output);
398
- }
399
- }
400
- // Extract question text
401
- let question = '';
402
- if (questionEndIndex >= 0) {
403
- // Get all non-empty lines from questionEndIndex up to (but not including) first option
404
- const questionLines = [];
405
- for (let i = Math.max(0, questionEndIndex - 5); i <= questionEndIndex; i++) {
406
- const line = lines[i].trim();
407
- if (line && !SEPARATOR_LINE_PATTERN.test(line)) {
408
- questionLines.push(line);
624
+ // Upward scan: look for a question-like line above questionEndIndex
625
+ if (!findQuestionLineInRange(lines, questionEndIndex, QUESTION_SCAN_RANGE, scanStart)) {
626
+ return noPromptResult(output);
409
627
  }
410
628
  }
411
- question = questionLines.join(' ');
412
629
  }
413
- else {
414
- // No clear question found - use a generic one
415
- question = 'Please select an option:';
416
- }
417
- return {
418
- isPrompt: true,
419
- promptData: {
420
- type: 'multiple_choice',
421
- question: question.trim(),
422
- options: collectedOptions.map(opt => {
423
- // Check if this option requires text input using module-level patterns
424
- const requiresTextInput = TEXT_INPUT_PATTERNS.some(pattern => pattern.test(opt.label));
425
- return {
426
- number: opt.number,
427
- label: opt.label,
428
- isDefault: opt.isDefault,
429
- requiresTextInput,
430
- };
431
- }),
432
- status: 'pending',
433
- },
434
- cleanContent: question.trim(),
435
- };
630
+ const question = extractQuestionText(lines, questionEndIndex);
631
+ const instructionText = extractInstructionText(lines, questionEndIndex, effectiveEnd);
632
+ return buildMultipleChoiceResult(question, collectedOptions, instructionText, output);
436
633
  }
437
634
  /**
438
635
  * Get tmux input string for an answer
@@ -53,6 +53,25 @@ const MAX_POLLING_DURATION = 5 * 60 * 1000;
53
53
  * @constant
54
54
  */
55
55
  const RESPONSE_THINKING_TAIL_LINE_COUNT = 5;
56
+ /**
57
+ * Gemini auth/loading state indicators that should not be treated as complete responses.
58
+ * Braille spinner characters are shared with CLAUDE_SPINNER_CHARS in cli-patterns.ts.
59
+ * Extracted to module level for clarity and to avoid re-creation on each call.
60
+ */
61
+ const GEMINI_LOADING_INDICATORS = [
62
+ 'Waiting for auth',
63
+ '\u280b', '\u2819', '\u2839', '\u2838', '\u283c', '\u2834', '\u2826', '\u2827', '\u2807', '\u280f',
64
+ ];
65
+ /**
66
+ * Creates an incomplete extraction result with empty response.
67
+ * Centralizes the repeated pattern of returning an in-progress/incomplete state.
68
+ *
69
+ * @param lineCount - Current line count for state tracking
70
+ * @returns ExtractionResult with empty response and isComplete: false
71
+ */
72
+ function incompleteResult(lineCount) {
73
+ return { response: '', isComplete: false, lineCount };
74
+ }
56
75
  /**
57
76
  * Active pollers map: "worktreeId:cliToolId" -> NodeJS.Timeout
58
77
  */
@@ -335,11 +354,7 @@ function extractResponse(output, lastCapturedLine, cliToolId) {
335
354
  // Prevents false blocking when completed thinking summaries appear in the response body.
336
355
  const responseTailLines = response.split('\n').slice(-RESPONSE_THINKING_TAIL_LINE_COUNT).join('\n');
337
356
  if (thinkingPattern.test(responseTailLines)) {
338
- return {
339
- response: '',
340
- isComplete: false,
341
- lineCount: totalLines,
342
- };
357
+ return incompleteResult(totalLines);
343
358
  }
344
359
  // CRITICAL FIX: Detect and skip Claude Code startup banner/screen
345
360
  // The startup screen contains: ASCII art logo, version info, prompt, separator
@@ -366,20 +381,12 @@ function extractResponse(output, lastCapturedLine, cliToolId) {
366
381
  !/^─+$/.test(trimmed);
367
382
  });
368
383
  if (contentLines.length === 0) {
369
- return {
370
- response: '',
371
- isComplete: false,
372
- lineCount: totalLines,
373
- };
384
+ return incompleteResult(totalLines);
374
385
  }
375
386
  }
376
387
  else if ((hasBannerArt || hasVersionInfo || hasStartupTips || hasProjectInit) && response.length < 2000) {
377
388
  // No user prompt found, but has banner characteristics - likely initial startup
378
- return {
379
- response: '',
380
- isComplete: false,
381
- lineCount: totalLines,
382
- };
389
+ return incompleteResult(totalLines);
383
390
  }
384
391
  }
385
392
  // Gemini-specific check: ensure response contains actual content (✦ marker)
@@ -388,31 +395,13 @@ function extractResponse(output, lastCapturedLine, cliToolId) {
388
395
  const bannerCharCount = (response.match(/[░███]/g) || []).length;
389
396
  const totalChars = response.length;
390
397
  if (bannerCharCount > totalChars * 0.3) {
391
- return {
392
- response: '',
393
- isComplete: false,
394
- lineCount: totalLines,
395
- };
398
+ return incompleteResult(totalLines);
396
399
  }
397
- // Check for auth/loading states that should not be treated as complete responses.
398
- // Braille spinner characters are shared with CLAUDE_SPINNER_CHARS in cli-patterns.ts.
399
- const LOADING_INDICATORS = [
400
- 'Waiting for auth',
401
- '⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏',
402
- ];
403
- if (LOADING_INDICATORS.some(indicator => response.includes(indicator))) {
404
- return {
405
- response: '',
406
- isComplete: false,
407
- lineCount: totalLines,
408
- };
400
+ if (GEMINI_LOADING_INDICATORS.some(indicator => response.includes(indicator))) {
401
+ return incompleteResult(totalLines);
409
402
  }
410
403
  if (!response.includes('✦') && response.length < 10) {
411
- return {
412
- response: '',
413
- isComplete: false,
414
- lineCount: totalLines,
415
- };
404
+ return incompleteResult(totalLines);
416
405
  }
417
406
  }
418
407
  return {
@@ -460,11 +449,7 @@ function extractResponse(output, lastCapturedLine, cliToolId) {
460
449
  };
461
450
  }
462
451
  // Response not yet complete (or is in thinking state)
463
- return {
464
- response: '',
465
- isComplete: false,
466
- lineCount: totalLines,
467
- };
452
+ return incompleteResult(totalLines);
468
453
  }
469
454
  /**
470
455
  * Check for CLI tool response once
@@ -533,7 +518,8 @@ async function checkForResponse(worktreeId, cliToolId) {
533
518
  const message = (0, db_1.createMessage)(db, {
534
519
  worktreeId,
535
520
  role: 'assistant',
536
- content: promptDetection.cleanContent,
521
+ // Issue #235: rawContent優先でDB保存 (rawContent contains complete prompt output)
522
+ content: promptDetection.rawContent || promptDetection.cleanContent,
537
523
  messageType: 'prompt',
538
524
  promptData: promptDetection.promptData,
539
525
  timestamp: new Date(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commandmate",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Git worktree management with Claude CLI and tmux sessions",
5
5
  "repository": {
6
6
  "type": "git",
@@ -48,6 +48,7 @@
48
48
  "lucide-react": "^0.554.0",
49
49
  "mermaid": "^11.12.2",
50
50
  "next": "^14.2.35",
51
+ "next-intl": "^4.8.2",
51
52
  "postcss": "^8.5.6",
52
53
  "react": "^18.3.0",
53
54
  "react-dom": "^18.3.0",
@@ -1 +0,0 @@
1
- <!DOCTYPE html><html lang="ja"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/css/d4b58a1129eff6af.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-4f85dcef6279c6ee.js"/><script src="/_next/static/chunks/fd9d1056-bbe86e4ae099d5cd.js" async=""></script><script src="/_next/static/chunks/2117-d845c2cd62e344a6.js" async=""></script><script src="/_next/static/chunks/main-app-420d93e43682fee5.js" async=""></script><script src="/_next/static/chunks/816-c254f4e2406e696a.js" async=""></script><script src="/_next/static/chunks/app/layout-4804cfba519283cf.js" async=""></script><meta name="robots" content="noindex"/><title>404: This page could not be found.</title><title>CommandMate</title><meta name="description" content="Git worktree management with Claude CLI and tmux sessions"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body class="min-h-screen bg-gray-50"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div><script src="/_next/static/chunks/webpack-4f85dcef6279c6ee.js" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/_next/static/css/d4b58a1129eff6af.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"2:I[12846,[],\"\"]\n4:I[4707,[],\"\"]\n5:I[36423,[],\"\"]\n6:I[91795,[\"816\",\"static/chunks/816-c254f4e2406e696a.js\",\"3185\",\"static/chunks/app/layout-4804cfba519283cf.js\"],\"AppProviders\"]\nc:I[61060,[],\"\"]\n7:{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"}\n8:{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"}\n9:{\"display\":\"inline-block\"}\na:{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0}\nd:[]\n"])</script><script>self.__next_f.push([1,"0:[\"$\",\"$L2\",null,{\"buildId\":\"HhG0EHeG9E4wTJ4sqnLdv\",\"assetPrefix\":\"\",\"urlParts\":[\"\",\"_not-found\"],\"initialTree\":[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{},[[\"$L3\",[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],null],null],null]},[null,[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"/_not-found\",\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"notFoundStyles\":\"$undefined\"}]],null]},[[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/d4b58a1129eff6af.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"ja\",\"children\":[\"$\",\"body\",null,{\"className\":\"min-h-screen bg-gray-50\",\"children\":[\"$\",\"$L6\",null,{\"children\":[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":\"$7\",\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":\"$8\",\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":\"$9\",\"children\":[\"$\",\"h2\",null,{\"style\":\"$a\",\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[]}]}]}]}]],null],null],\"couldBeIntercepted\":false,\"initialHead\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],\"$Lb\"],\"globalErrorComponent\":\"$c\",\"missingSlots\":\"$Wd\"}]\n"])</script><script>self.__next_f.push([1,"b:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"CommandMate\"}],[\"$\",\"meta\",\"3\",{\"name\":\"description\",\"content\":\"Git worktree management with Claude CLI and tmux sessions\"}]]\n3:null\n"])</script></body></html>
@@ -1,6 +0,0 @@
1
- {
2
- "status": 404,
3
- "headers": {
4
- "x-next-cache-tags": "_N_T_/layout,_N_T_/_not-found/layout,_N_T_/_not-found/page,_N_T_/_not-found"
5
- }
6
- }
@@ -1,10 +0,0 @@
1
- 2:I[4707,[],""]
2
- 3:I[36423,[],""]
3
- 4:I[91795,["816","static/chunks/816-c254f4e2406e696a.js","3185","static/chunks/app/layout-4804cfba519283cf.js"],"AppProviders"]
4
- 5:{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"}
5
- 6:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"}
6
- 7:{"display":"inline-block"}
7
- 8:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0}
8
- 0:["HhG0EHeG9E4wTJ4sqnLdv",[[["",{"children":["/_not-found",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["/_not-found",{"children":["__PAGE__",{},[["$L1",[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],null],null],null]},[null,["$","$L2",null,{"parallelRouterKey":"children","segmentPath":["children","/_not-found","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/d4b58a1129eff6af.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"ja","children":["$","body",null,{"className":"min-h-screen bg-gray-50","children":["$","$L4",null,{"children":["$","$L2",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$5","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$6","children":"404"}],["$","div",null,{"style":"$7","children":["$","h2",null,{"style":"$8","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]}]],null],null],["$L9",["$","meta",null,{"name":"robots","content":"noindex"}]]]]]
9
- 9:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"CommandMate"}],["$","meta","3",{"name":"description","content":"Git worktree management with Claude CLI and tmux sessions"}]]
10
- 1:null