Skip to content
Merged
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
18 changes: 18 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"permissions": {
"allow": [
"Bash(npm run typecheck)",
"Bash(npm run test)",
"Bash(npm run test:coverage)",
"Bash(npm run build)",
"Bash(npm run format:check)",
"Bash(npx vitest run *)",
"Bash(npx tsc *)",
"Bash(git status)",
"Bash(git diff *)",
"Bash(git log *)",
"Bash(git -C *)",
"Bash(git worktree *)"
]
}
}
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,17 @@ notes-local/
# Netlify local link state (per-developer)
.netlify/
.worktrees/

# Claude Code local settings — may contain personal credentials in Bash allow-lists
.claude/settings.local.json
.claude/*.local.json

# Internal dev planning docs (superpowers skill artifacts)
docs/superpowers/

# Internal demo scripts and working notes
docs/demo-script-*.md
docs/tabstack-usage-by-page.md

# Local debug scripts
scripts/debug-health.ts
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ npm install
2. Configure environment:

```bash
cp .env.example .env
cp .env.example .env.local
```

Required env vars:
Expand Down Expand Up @@ -69,7 +69,10 @@ npm run dev

## Config (`rivals.config.json`)

Rival reads competitor definitions from `rivals.config.json`.
Rival reads competitor definitions from `rivals.config.json`. The repo ships with a generic placeholder template — replace it with your own companies and competitors before running `db:seed`.

For a real-world example showing all available fields, see [`rivals.config.tabstack.json`](./rivals.config.tabstack.json) — this is the Tabstack team's reference config, included as documentation only. The app always loads `rivals.config.json`.

Supported `type` values for pages:

- `pricing`
Expand All @@ -82,6 +85,10 @@ Supported `type` values for pages:
- `stack`
- `custom`

Optional top-level competitor fields:

- `matrix: false` — exclude this competitor from the positioning matrix visualization (still tracked and scanned normally)

Minimal example:

```json
Expand Down
35 changes: 1 addition & 34 deletions app/api/deep-dive/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,7 @@ export const maxDuration = 120;

import { buildPromptForTemplate } from "@/lib/deep-dive-templates";
import { parseSseChunk } from "@/lib/utils/sse";

type ResearchCitation = { claim: string; source_url: string; source_text?: string };

function extractResult(data: unknown): unknown {
if (!data || typeof data !== "object" || Array.isArray(data)) return data ?? null;
const d = data as Record<string, unknown>;
if ("result" in d) return d.result;
const { citations: _c, ...rest } = d;
return Object.keys(rest).length > 0 ? rest : null;
}

function extractCitations(data: unknown): ResearchCitation[] {
if (!data || typeof data !== "object" || Array.isArray(data)) return [];
const raw = (data as Record<string, unknown>).citations;
if (!Array.isArray(raw)) return [];
return raw.flatMap((item: unknown): ResearchCitation[] => {
if (!item || typeof item !== "object" || Array.isArray(item)) return [];
const r = item as Record<string, unknown>;
if (typeof r.source_url !== "string") return [];
try {
const u = new URL(r.source_url);
if (u.protocol !== "https:" && u.protocol !== "http:") return [];
} catch {
return [];
}
return [
{
claim: typeof r.claim === "string" ? r.claim : "",
source_url: r.source_url,
source_text: typeof r.source_text === "string" ? r.source_text : undefined
}
];
});
}
import { extractCitations, extractResult } from "@/lib/tabstack/research";

type DeepDiveRequest = {
competitorId?: string;
Expand Down
Loading
Loading