Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"lint:fix": "eslint src/ electron/ --fix",
"fmt": "prettier --write 'src/**/*.{ts,tsx}' 'electron/**/*.{js,cjs}' 'vite.config.ts'",
"fix": "npm run lint:fix && npm run fmt",
"test:ui": "vitest run --environment jsdom",
"test:ui": "vitest run --environment jsdom src",
"preview": "node scripts/assert-root-install.cjs && vite preview --host 127.0.0.1 --port 4174"
},
"dependencies": {
Expand Down Expand Up @@ -84,7 +84,7 @@
"react": "^19.2.5",
"react-arborist": "^3.5.0",
"react-dom": "^19.2.5",
"react-router-dom": "^7.14.2",
"react-router-dom": "7.17.0",
"react-shiki": "^0.9.3",
"remark-math": "^6.0.0",
"shiki": "^4.0.2",
Expand Down Expand Up @@ -125,7 +125,7 @@
"rcedit": "^5.0.2",
"typescript": "^6.0.3",
"vite": "^8.0.10",
"vitest": "^4.1.5",
"vitest": "^4.1.6",
"wait-on": "^9.0.5"
},
"build": {
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/components/pane-shell/pane-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ function trackForPane(pane: CollectedPane, states: Record<string, { open: boolea
return { open: false, track: '0px' }
}

const override = pane.resizable ? states[pane.id]?.widthOverride : undefined
const override = states[pane.id]?.widthOverride

return { open: true, track: override !== undefined ? `${override}px` : pane.width }
}
Expand Down
52 changes: 52 additions & 0 deletions apps/desktop/src/lib/company-brain-trace.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { describe, expect, it } from 'vitest'

import {
answerHasCompleteCompanyBrainTrace,
formatCompanyBrainTrace,
missingCompanyBrainTraceFields,
parseCompanyBrainTrace
} from './company-brain-trace'

describe('Company Brain traceability primitive', () => {
it('formats the minimum source/conflict/correction footer for an important answer', () => {
const markdown = formatCompanyBrainTrace({
changedAfterCorrection: [{ after: 'Qigawa spelling is canonical', before: 'Kigawa spelling' }],
conflictWinner: 'Obsidian canon note beats stale model memory',
sources: ['Obsidian: BBBB/Canon.md', 'session correction from Dave']
})

expect(markdown).toContain('### Company Brain trace')
expect(markdown).toContain('- Source: Obsidian: BBBB/Canon.md')
expect(markdown).toContain('- Conflict winner: Obsidian canon note beats stale model memory')
expect(markdown).toContain('- Changed after correction: Kigawa spelling → Qigawa spelling is canonical')
expect(answerHasCompleteCompanyBrainTrace(markdown)).toBe(true)
})

it('parses trace footers and de-duplicates semicolon-separated sources', () => {
const trace = parseCompanyBrainTrace(`Answer body.

### Company Brain trace
- Sources: memory:BBBB; memory:BBBB; Obsidian:Canon
- Conflict winner: Dave correction wins over stale memory
- Changed after correction: old → new
`)

expect(trace).toEqual({
changedAfterCorrection: [{ after: 'new', before: 'old' }],
conflictWinner: 'Dave correction wins over stale memory',
sources: ['memory:BBBB', 'Obsidian:Canon']
})
})

it('reports missing trace fields so the UI/gateway can gate important answers later', () => {
expect(missingCompanyBrainTraceFields(null)).toEqual(['source', 'conflictWinner', 'changedAfterCorrection'])
expect(
missingCompanyBrainTraceFields(
parseCompanyBrainTrace(`### Company Brain trace
- Source: session_search:abc
`)
)
).toEqual(['conflictWinner', 'changedAfterCorrection'])
expect(answerHasCompleteCompanyBrainTrace('plain answer')).toBe(false)
})
})
108 changes: 108 additions & 0 deletions apps/desktop/src/lib/company-brain-trace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
export interface CompanyBrainCorrection {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this parser from this PR. The diff adds no production import or consumer—only its own test—and the stated use is future gating; AGENTS.md:98-101 rejects speculative infrastructure without a concrete consumer.

after: string
before?: string
}

export interface CompanyBrainTrace {
changedAfterCorrection?: CompanyBrainCorrection[]
conflictWinner?: string
sources: string[]
}

export type CompanyBrainTraceField = 'source' | 'conflictWinner' | 'changedAfterCorrection'

const TRACE_HEADING_RE = /^#{2,6}\s*Company Brain trace\s*$/im
const TRACE_BLOCK_RE = /(?:^|\n)#{2,6}\s*Company Brain trace\s*\n(?<body>[\s\S]*?)(?=\n#{1,6}\s|\n?$)/i
const BULLET_RE = /^\s*[-*]\s*(?<label>Source|Sources|Conflict winner|Changed after correction)\s*:\s*(?<value>.+?)\s*$/i

function cleanLine(value: string): string {
return value.replace(/\s+/g, ' ').trim()
}

function unique(values: string[]): string[] {
return [...new Set(values.map(cleanLine).filter(Boolean))]
}

function parseCorrection(value: string): CompanyBrainCorrection | null {
const cleaned = cleanLine(value)
if (!cleaned) return null

const arrow = cleaned.match(/^(?<before>.+?)\s*(?:->|→)\s*(?<after>.+)$/)
if (arrow?.groups?.after) {
return {
after: cleanLine(arrow.groups.after),
before: cleanLine(arrow.groups.before)
}
}

return { after: cleaned }
}

export function parseCompanyBrainTrace(markdown: string): CompanyBrainTrace | null {
if (!TRACE_HEADING_RE.test(markdown)) {
return null
}

const body = markdown.match(TRACE_BLOCK_RE)?.groups?.body ?? ''
const sources: string[] = []
const corrections: CompanyBrainCorrection[] = []
let conflictWinner = ''

for (const line of body.split('\n')) {
const match = line.match(BULLET_RE)
if (!match?.groups) continue

const label = match.groups.label.toLowerCase()
const value = cleanLine(match.groups.value)

if (!value) continue

if (label === 'source' || label === 'sources') {
sources.push(...value.split(/\s*;\s*/))
} else if (label === 'conflict winner') {
conflictWinner = value
} else if (label === 'changed after correction') {
const correction = parseCorrection(value)
if (correction) corrections.push(correction)
}
}

return {
changedAfterCorrection: corrections,
conflictWinner: conflictWinner || undefined,
sources: unique(sources)
}
}

export function missingCompanyBrainTraceFields(trace: CompanyBrainTrace | null): CompanyBrainTraceField[] {
if (!trace) return ['source', 'conflictWinner', 'changedAfterCorrection']

const missing: CompanyBrainTraceField[] = []
if (trace.sources.length === 0) missing.push('source')
if (!trace.conflictWinner) missing.push('conflictWinner')
if (!trace.changedAfterCorrection || trace.changedAfterCorrection.length === 0) missing.push('changedAfterCorrection')

return missing
}

export function formatCompanyBrainTrace(trace: CompanyBrainTrace): string {
const lines = ['### Company Brain trace']

for (const source of unique(trace.sources)) {
lines.push(`- Source: ${source}`)
}

lines.push(`- Conflict winner: ${cleanLine(trace.conflictWinner || 'none')}`)

const corrections = trace.changedAfterCorrection?.length ? trace.changedAfterCorrection : [{ after: 'none' }]
for (const correction of corrections) {
const value = correction.before ? `${correction.before} → ${correction.after}` : correction.after
lines.push(`- Changed after correction: ${cleanLine(value)}`)
}

return `${lines.join('\n')}\n`
}

export function answerHasCompleteCompanyBrainTrace(markdown: string): boolean {
return missingCompanyBrainTraceFields(parseCompanyBrainTrace(markdown)).length === 0
}
12 changes: 12 additions & 0 deletions hermes_cli/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -3083,6 +3083,18 @@ def generate_launchd_plist() -> str:

<key>KeepAlive</key>
<true/>

<key>SoftResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>4096</integer>
</dict>

<key>HardResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>8192</integer>
</dict>

<key>StandardOutPath</key>
<string>{log_dir}/gateway.log</string>
Expand Down
Loading