diff --git a/background.js b/background.js index d102992..2a44902 100644 --- a/background.js +++ b/background.js @@ -596,7 +596,7 @@ async function pushToGitHub(session, settings) { const filename = `consensus-${session.id.slice(0, 8)}-${formatDateForFilename(session.createdAt)}.md`; const filePath = githubPath ? `${githubPath.replace(/\/$/, '')}/${filename}` : filename; - const content = generateMarkdownExport(session); + const content = generateMarkdownExport(session, settings); const contentBase64 = btoa(unescape(encodeURIComponent(content))); let sha = null; @@ -647,7 +647,7 @@ async function pushToGitHub(session, settings) { // ===== Export Helpers ===== // NOTE: Duplicated across sidepanel and background for MV3 service worker stability. Keep in sync. -function generateMarkdownExport(session) { +function generateMarkdownExport(session, settings) { const lines = []; lines.push(`# AIDIALECTIC Transcript`); lines.push(''); @@ -667,6 +667,7 @@ function generateMarkdownExport(session) { lines.push('## Conversation'); lines.push(''); + const isBrainstorm = settings && settings.brainstormMode; let lastRound = 0; for (const msg of session.messages) { if (msg.round && msg.round !== lastRound) { @@ -683,7 +684,25 @@ function generateMarkdownExport(session) { lines.push(`#### ${label} — ${time}${consensusTag}`); lines.push(''); - lines.push(msg.content); + + if (isBrainstorm && msg.source !== 'user') { + const round = msg.round || 0; + let ideaNum = 0; + lines.push( + msg.content + .split('\n') + .map((line) => { + if (/^\*{0,2}\[The\s/.test(line.trimStart())) { + ideaNum++; + return `[R${round}-${ideaNum}] ${line}`; + } + return line; + }) + .join('\n'), + ); + } else { + lines.push(msg.content); + } lines.push(''); } diff --git a/manifest.json b/manifest.json index f3a556e..58e3832 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "AIDIALECTIC", - "version": "1.0.0", + "version": "1.1.0", "description": "Produce rigorously vetted, exportable reasoning sessions using Claude and Gemini. No copy-paste needed.", "permissions": [ "sidePanel", diff --git a/sidepanel/prompts.js b/sidepanel/prompts.js index 4412c7d..1a22abf 100644 --- a/sidepanel/prompts.js +++ b/sidepanel/prompts.js @@ -18,7 +18,56 @@ const TONE_INJECTIONS = { 'Be sharp, opinionated, and unapologetically direct. Show genuine intellectual impatience when the opposing position has obvious flaws -- express frustration, disbelief, or dry sarcasm. Call out lazy reasoning bluntly. However, your irritation must always be grounded in actual analytical substance -- never substitute attitude for argument. If the opposing model makes a genuinely strong point, grudgingly acknowledge it.', }; +const EXPANSION_INJECTIONS = { + sparks: + 'Limit output to tagged one-liners. Aim for 10+ ideas. No explanation, no justification. Raw sparks only.', + seeds: + 'Develop each idea with a 2-3 sentence expansion. Include specific variations, edge cases, or unexpected applications. Breadth and depth — but no evaluation.', + branches: + 'Provide a full paragraph per idea with variations and applications. Thoroughness over brevity — but no evaluation or ranking.', +}; + +const PERSPECTIVE_INJECTIONS = { + 'rapid-fire': + 'Adopt a radically different analytical lens for your ideation. You must generate exactly 9 ideas per round — one for each of the following specific archetypes: [The Hacker], [The Aesthete], [The Futurist], [The Naturalist], [The Capitalist], [The Beginner], [The Psychologist], [The Historian], and [The Athlete]. Tag the lens in brackets at the start of each idea. State each idea as a direct, impersonal assertion derived purely from what that specific archetype values most.', + contrarian: + 'Systematically invert the core assumptions of the topic. Generate ideas that do the exact opposite of conventional wisdom, standard industry practice, or logical next steps. Do not simply negate existing ideas — build viable alternatives from the inversion.', + 'first-principles': + 'Strip away all industry assumptions, jargon, and standard practices. Ask "why do we do it this way?" for each concept and build your ideas upward from fundamental physical, behavioral, or logical truths. Name the fundamental truth you\'re building from.', +}; + +const VECTOR_INJECTIONS = { + who: 'Constrain your ideation entirely to the audience, shifting target users, creating bizarre partnerships, or applying the concept to entirely different demographics.', + what: 'Constrain your ideation strictly to tangible outputs — what is actually being built, made, or produced. Focus on concrete deliverables.', + when: 'Constrain your ideation entirely to time — time of day, macroeconomic cycles, historical eras, or future timelines.', + where: 'Constrain your ideation entirely to location — physical space, geographic constraints, digital environments, or spatial computing.', + why: 'Constrain your ideation entirely to motivations, human incentives, emotional resonance, market dynamics, and underlying philosophy.', + how: 'Constrain your ideation entirely to novel implementations, bizarre technical workarounds, and physical/digital mechanics.', + 'what-if': + 'Constrain your ideation by systematically breaking the fundamental laws of the topic (physics, economics, social norms) and building ideas in that void.', +}; + function buildModifierBlock(settings) { + if (settings.brainstormMode) { + const expansionVal = settings.modExpansion || 'sparks'; + const perspectiveVal = settings.modPerspective || 'rapid-fire'; + const vectorVal = settings.modVector || 'all'; + + const parts = []; + if (EXPANSION_INJECTIONS[expansionVal]) { + parts.push(EXPANSION_INJECTIONS[expansionVal]); + } + if (PERSPECTIVE_INJECTIONS[perspectiveVal]) { + parts.push(PERSPECTIVE_INJECTIONS[perspectiveVal]); + } + if (vectorVal !== 'all' && VECTOR_INJECTIONS[vectorVal]) { + parts.push(VECTOR_INJECTIONS[vectorVal]); + } + + if (parts.length === 0) return ''; + return '\n\n### RUNTIME MODIFIERS ###\n' + parts.join('\n'); + } + const lengthVal = settings.modLength || 'normal'; const toneVal = settings.modTone || 'default'; @@ -43,22 +92,47 @@ function generateInitialPrompt(context, settings) { parts.push(''); } - parts.push('=== DIALECTIC — ROUND 1 ==='); - parts.push( - 'Structured dialectic between independent AI analysts. Challenge errors, defend sound positions, update only when genuinely persuaded. Do not soften disagreements.', - ); - parts.push( - 'Do NOT produce finished documents, specs, summaries, or deliverables unless the user explicitly asks. Focus on dialectic reasoning and decision-making.', - ); - parts.push(''); - parts.push('--- INITIAL CONTEXT (msgid#1) ---'); - parts.push(context); - parts.push(''); - parts.push('--- YOUR TASK ---'); - parts.push('1. Analyze the initial context thoroughly'); - parts.push('2. State your position clearly with supporting reasoning'); - parts.push('3. Identify key assumptions and potential weaknesses in your own analysis'); - parts.push('4. Provide your complete, honest assessment'); + if (settings.brainstormMode) { + parts.push('=== BRAINSTORM — ROUND 1 ==='); + parts.push( + 'Generative ideation between independent AI contributors. Build on every idea presented. Add novel directions the other contributor hasn\'t explored.', + ); + parts.push( + 'Do not evaluate, critique, rank, or filter ideas. Volume and diversity over quality.', + ); + parts.push( + 'Do NOT converge on recommendations, narrow to "top picks," or produce polished deliverables unless the user explicitly asks. Focus on expanding the possibility space.', + ); + parts.push( + 'Focus on conceptual novelty. No abstract jargon as a substitute.', + ); + parts.push(''); + parts.push('--- INITIAL CONTEXT (msgid#1) ---'); + parts.push(context); + parts.push(''); + parts.push('--- YOUR TASK ---'); + parts.push('1. Generate as many distinct ideas as possible related to the initial context'); + parts.push('2. Explore unexpected angles, tangents, and creative leaps'); + parts.push('3. Tag each idea with a bracketed label (e.g., [Distribution], [UX], [Pricing])'); + parts.push('4. Do NOT evaluate or rank — quantity and variety over quality'); + } else { + parts.push('=== DIALECTIC — ROUND 1 ==='); + parts.push( + 'Structured dialectic between independent AI analysts. Challenge errors, defend sound positions, update only when genuinely persuaded. Do not soften disagreements.', + ); + parts.push( + 'Do NOT produce finished documents, specs, summaries, or deliverables unless the user explicitly asks. Focus on dialectic reasoning and decision-making.', + ); + parts.push(''); + parts.push('--- INITIAL CONTEXT (msgid#1) ---'); + parts.push(context); + parts.push(''); + parts.push('--- YOUR TASK ---'); + parts.push('1. Analyze the initial context thoroughly'); + parts.push('2. State your position clearly with supporting reasoning'); + parts.push('3. Identify key assumptions and potential weaknesses in your own analysis'); + parts.push('4. Provide your complete, honest assessment'); + } const modifiers = buildModifierBlock(settings); return parts.join('\n') + modifiers; @@ -87,13 +161,29 @@ function generateHandoffPrompt(session, target, settings) { upcomingRound = session.round; } - parts.push(`=== DIALECTIC — ROUND ${upcomingRound} ===`); - parts.push( - 'Structured dialectic between independent AI analysts. Challenge errors, defend sound positions, update only when genuinely persuaded. Do not soften disagreements.', - ); - parts.push( - 'Do NOT produce finished documents, specs, summaries, or deliverables unless the user explicitly asks. Focus on dialectic reasoning and decision-making.', - ); + if (settings.brainstormMode) { + parts.push(`=== BRAINSTORM — ROUND ${upcomingRound} ===`); + parts.push( + 'Generative ideation between independent AI contributors. Build on every idea presented. Add novel directions the other contributor hasn\'t explored.', + ); + parts.push( + 'Do not evaluate, critique, rank, or filter ideas. Volume and diversity over quality.', + ); + parts.push( + 'Do NOT converge on recommendations, narrow to "top picks," or produce polished deliverables unless the user explicitly asks. Focus on expanding the possibility space.', + ); + parts.push( + 'Focus on conceptual novelty. No abstract jargon as a substitute.', + ); + } else { + parts.push(`=== DIALECTIC — ROUND ${upcomingRound} ===`); + parts.push( + 'Structured dialectic between independent AI analysts. Challenge errors, defend sound positions, update only when genuinely persuaded. Do not soften disagreements.', + ); + parts.push( + 'Do NOT produce finished documents, specs, summaries, or deliverables unless the user explicitly asks. Focus on dialectic reasoning and decision-making.', + ); + } parts.push(''); const targetHasResponded = session.messages.some((m) => m.source === target); @@ -167,12 +257,26 @@ function generateHandoffPrompt(session, target, settings) { } parts.push('--- YOUR TASK ---'); - parts.push('1. Identify any errors, unsupported claims, or logical gaps in the above analysis'); - parts.push('2. Challenge assumptions you disagree with — explain why'); - parts.push('3. Defend your own prior positions where you believe they are correct'); - parts.push('4. Update your position only where genuinely persuaded by evidence or logic'); - parts.push('5. Provide your complete, honest assessment'); + if (settings.brainstormMode) { + parts.push('1. Build on, remix, and extend the ideas already generated — do not repeat them verbatim'); + parts.push('2. Add entirely new ideas the prior round missed'); + parts.push('3. Explore unexpected tangents, combinations, and creative leaps'); + parts.push('4. Tag each idea with a bracketed label (e.g., [Distribution], [UX], [Pricing])'); + parts.push('5. Do NOT evaluate, rank, or filter — quantity and variety over quality'); + } else { + parts.push('1. Identify any errors, unsupported claims, or logical gaps in the above analysis'); + parts.push('2. Challenge assumptions you disagree with — explain why'); + parts.push('3. Defend your own prior positions where you believe they are correct'); + parts.push('4. Update your position only where genuinely persuaded by evidence or logic'); + parts.push('5. Provide your complete, honest assessment'); + } const modifiers = buildModifierBlock(settings); - return parts.join('\n') + modifiers; + let prompt = parts.join('\n') + modifiers; + + if (settings.brainstormMode && target === 'gemini') { + prompt += '\n\nOUTPUT ONLY THE REQUESTED LIST. NO PREAMBLE. NO CLOSING REMARKS. DO NOT ASK FOLLOW-UP QUESTIONS.'; + } + + return prompt; } diff --git a/sidepanel/sidepanel.css b/sidepanel/sidepanel.css index 4b1f9c6..a1ffe49 100644 --- a/sidepanel/sidepanel.css +++ b/sidepanel/sidepanel.css @@ -699,6 +699,15 @@ body { font-variant-numeric: tabular-nums; } +/* Brainstorm idea coordinate tag */ +.idea-coord { + color: var(--text-muted); + font-size: 0.85em; + font-weight: 700; + font-variant-numeric: tabular-nums; + margin-right: 2px; +} + /* Clickable source label */ .source-label { cursor: pointer; diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index f0d7de0..fc3b94b 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -427,6 +427,17 @@

Settings

+
+
Mode
+
+
+ Brainstorm Mode + Switch from convergent dialectic to divergent ideation. +
+
+
+
+
Response Modifiers
@@ -450,6 +461,19 @@

Settings

+
diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index 218698b..65433bf 100644 --- a/sidepanel/sidepanel.js +++ b/sidepanel/sidepanel.js @@ -68,6 +68,12 @@ btnFontDown: $('#btn-font-down'), modLength: $('#mod-length'), modTone: $('#mod-tone'), + modVector: $('#mod-vector'), + modifierGroupVector: $('#modifier-group-vector'), + toggleBrainstorm: $('#settings-toggle-brainstorm'), + modLengthLabel: document.querySelector('label[for="mod-length"]'), + modToneLabel: document.querySelector('label[for="mod-tone"]'), + brandSubtitle: $('.brand-text .subtitle'), }; // ===== Init ===== @@ -76,6 +82,9 @@ await initTheme(); await initUIZoom(); await loadSettings(); + if (els.brandSubtitle) { + els.brandSubtitle.textContent = settings.brainstormMode ? 'BRAINSTORM SESSION' : 'CONSENSUS WORKFLOW'; + } await loadCurrentSession(); bindEvents(); bindSettingsEvents(); @@ -169,6 +178,66 @@ }); } + // ===== Brainstorm UI ===== + + function applyBrainstormUI(active) { + // Update header subtitle + if (els.brandSubtitle) { + els.brandSubtitle.textContent = active ? 'BRAINSTORM SESSION' : 'CONSENSUS WORKFLOW'; + } + + // Show/hide vector dropdown + els.modifierGroupVector.style.display = active ? '' : 'none'; + + // Swap Length <-> Expansion + if (els.modLengthLabel) { + els.modLengthLabel.textContent = active ? 'Expansion' : 'Length'; + } + const lengthSelect = els.modLength; + lengthSelect.innerHTML = ''; + if (active) { + [['sparks', 'Sparks'], ['seeds', 'Seeds'], ['branches', 'Branches']].forEach(([val, label]) => { + const opt = document.createElement('option'); + opt.value = val; + opt.textContent = label; + lengthSelect.appendChild(opt); + }); + lengthSelect.value = settings.modExpansion || 'sparks'; + } else { + [['normal', 'Normal'], ['short', 'Short'], ['deep', 'Deep Dive']].forEach(([val, label]) => { + const opt = document.createElement('option'); + opt.value = val; + opt.textContent = label; + lengthSelect.appendChild(opt); + }); + lengthSelect.value = settings.modLength || 'normal'; + } + + // Swap Tone <-> Perspective + if (els.modToneLabel) { + els.modToneLabel.textContent = active ? 'Perspective' : 'Tone'; + } + const toneSelect = els.modTone; + toneSelect.innerHTML = ''; + if (active) { + [['rapid-fire', 'Rapid-Fire'], ['contrarian', 'Contrarian'], ['first-principles', 'First Principles']].forEach(([val, label]) => { + const opt = document.createElement('option'); + opt.value = val; + opt.textContent = label; + toneSelect.appendChild(opt); + }); + toneSelect.value = settings.modPerspective || 'rapid-fire'; + } else { + [['default', 'Default'], ['clinical', 'Clinical'], ['socratic', 'Socratic'], ['sassy', 'Sassy']].forEach(([val, label]) => { + const opt = document.createElement('option'); + opt.value = val; + opt.textContent = label; + toneSelect.appendChild(opt); + }); + toneSelect.value = settings.modTone || 'default'; + } + } + // ===== Settings Modal ===== function openSettingsModal() { @@ -177,10 +246,12 @@ $('#settings-github-path').value = settings.githubPath || ''; $('#settings-github-token').value = settings.githubToken || ''; $('#settings-system-context').value = settings.systemContext || ''; - els.modLength.value = settings.modLength || 'normal'; - els.modTone.value = settings.modTone || 'default'; const toggle = $('#settings-toggle-autocapture'); toggle.classList.toggle('active', !!settings.autoCapture); + els.toggleBrainstorm.classList.toggle('active', !!settings.brainstormMode); + const brainstormActive = !!settings.brainstormMode; + applyBrainstormUI(brainstormActive); + els.modVector.value = settings.modVector || 'all'; modal.classList.remove('hidden'); } @@ -189,17 +260,26 @@ } function saveSettingsModal() { + const brainstormActive = els.toggleBrainstorm.classList.contains('active'); // Merge with existing settings so we never lose keys not shown in the modal - settings = { + const updated = { ...settings, githubRepo: $('#settings-github-repo').value.trim(), githubPath: $('#settings-github-path').value.trim(), githubToken: $('#settings-github-token').value.trim(), systemContext: $('#settings-system-context').value.trim(), - modLength: els.modLength.value, - modTone: els.modTone.value, autoCapture: $('#settings-toggle-autocapture').classList.contains('active'), + brainstormMode: brainstormActive, + modVector: els.modVector.value, }; + if (brainstormActive) { + updated.modExpansion = els.modLength.value; + updated.modPerspective = els.modTone.value; + } else { + updated.modLength = els.modLength.value; + updated.modTone = els.modTone.value; + } + settings = updated; chrome.storage.local.set({ ce_settings: settings }, () => { showSettingsStatus('Settings saved!', 'success'); }); @@ -278,6 +358,10 @@ $('#settings-toggle-autocapture').addEventListener('click', () => { $('#settings-toggle-autocapture').classList.toggle('active'); }); + els.toggleBrainstorm.addEventListener('click', () => { + els.toggleBrainstorm.classList.toggle('active'); + applyBrainstormUI(els.toggleBrainstorm.classList.contains('active')); + }); } // ===== Session Management ===== @@ -457,10 +541,10 @@ // Consensus bar: show toggle (mark / unmark) els.consensusBar.classList.remove('hidden'); if (session.consensusReached) { - els.btnConsensus.textContent = 'Reopen Session'; + els.btnConsensus.textContent = settings.brainstormMode ? 'Reopen Brainstorm' : 'Reopen Session'; els.btnConsensus.classList.add('btn-consensus-reopen'); } else { - els.btnConsensus.textContent = 'Mark Consensus'; + els.btnConsensus.textContent = settings.brainstormMode ? 'End Brainstorm' : 'Mark Consensus'; els.btnConsensus.classList.remove('btn-consensus-reopen'); } } else { @@ -573,6 +657,21 @@ body.textContent = msg.content; } + // Brainstorm idea coordinates: prepend [R#-#] to idea lines + // Ideas may be in separate

tags OR within one

separated by
. + // Use innerHTML replacement to catch both cases. + if (settings.brainstormMode && msg.source !== 'user') { + const round = msg.round || 0; + let ideaNum = 0; + body.innerHTML = body.innerHTML.replace( + /(]*>|]*>|)\s*((?:<(?:strong|em|b)>)*)(\[The\s)/gi, + (match, tag, formatting, bracket) => { + ideaNum++; + return `${tag}${formatting}[R${round}-${ideaNum}] ${bracket}`; + }, + ); + } + div.querySelector('.btn-copy').addEventListener('click', (e) => { e.stopPropagation(); navigator.clipboard @@ -807,7 +906,10 @@ } }); } else { - if (!confirm('Mark the last response as the consensus result?')) return; + const confirmMsg = settings.brainstormMode + ? 'End this brainstorm session?' + : 'Mark the last response as the consensus result?'; + if (!confirm(confirmMsg)) return; chrome.runtime.sendMessage({ type: 'MARK_CONSENSUS' }, (response) => { if (response && response.session) { session = response.session; @@ -857,7 +959,26 @@ function exportJSON() { if (!session) return; - const json = JSON.stringify(session, null, 2); + let exportData = session; + if (settings.brainstormMode) { + exportData = { + ...session, + messages: session.messages.map((msg) => { + if (msg.source === 'user') return msg; + const round = msg.round || 0; + const ideas = []; + let ideaNum = 0; + msg.content.split('\n').forEach((line) => { + if (/^\*{0,2}\[The\s/.test(line.trimStart())) { + ideaNum++; + ideas.push({ ideaIndex: `R${round}-${ideaNum}`, text: line }); + } + }); + return ideas.length > 0 ? { ...msg, ideas } : msg; + }), + }; + } + const json = JSON.stringify(exportData, null, 2); downloadFile(json, exportFilename('json'), 'application/json'); showExportStatus('JSON downloaded!', 'success'); } @@ -959,7 +1080,25 @@ lines.push(`#### ${label} — ${time}${consensusTag}`); lines.push(''); - lines.push(msg.content); + + if (settings.brainstormMode && msg.source !== 'user') { + const round = msg.round || 0; + let ideaNum = 0; + lines.push( + msg.content + .split('\n') + .map((line) => { + if (/^\*{0,2}\[The\s/.test(line.trimStart())) { + ideaNum++; + return `[R${round}-${ideaNum}] ${line}`; + } + return line; + }) + .join('\n'), + ); + } else { + lines.push(msg.content); + } lines.push(''); }