Skip to content

Commit 69a7ef7

Browse files
khaliqgantclaude
andcommitted
test: add comprehensive tests and CI workflows
Add tests for new features: - TRAJECTORIES_DATA_DIR env var behavior - TRAJECTORIES_SEARCH_PATHS multi-path search - --agent, --project, --quiet CLI flags - CLI flag override of env vars Add GitHub Actions CI workflows: - ci.yml: lint, typecheck, test (Node 20/22), build - release.yml: automated npm publishing on tags Fix type errors in web generator to align with core types: - Update TrajectoryEvent to use `ts` timestamp - Update Retrospective to use correct fields (learnings, suggestions) - Update Decision rendering to use question/chosen fields 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 113e6b9 commit 69a7ef7

File tree

6 files changed

+447
-30
lines changed

6 files changed

+447
-30
lines changed

.github/workflows/ci.yml

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
lint:
11+
name: Lint
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
17+
- name: Setup Node.js
18+
uses: actions/setup-node@v4
19+
with:
20+
node-version: "20"
21+
cache: "npm"
22+
23+
- name: Install dependencies
24+
run: npm ci
25+
26+
- name: Run linter
27+
run: npm run lint
28+
29+
typecheck:
30+
name: Type Check
31+
runs-on: ubuntu-latest
32+
steps:
33+
- name: Checkout code
34+
uses: actions/checkout@v4
35+
36+
- name: Setup Node.js
37+
uses: actions/setup-node@v4
38+
with:
39+
node-version: "20"
40+
cache: "npm"
41+
42+
- name: Install dependencies
43+
run: npm ci
44+
45+
- name: Run type check
46+
run: npm run typecheck
47+
48+
test:
49+
name: Test (Node ${{ matrix.node-version }})
50+
runs-on: ubuntu-latest
51+
strategy:
52+
matrix:
53+
node-version: ["20", "22"]
54+
fail-fast: false
55+
steps:
56+
- name: Checkout code
57+
uses: actions/checkout@v4
58+
59+
- name: Setup Node.js ${{ matrix.node-version }}
60+
uses: actions/setup-node@v4
61+
with:
62+
node-version: ${{ matrix.node-version }}
63+
cache: "npm"
64+
65+
- name: Install dependencies
66+
run: npm ci
67+
68+
- name: Run tests
69+
run: npm run test:run
70+
71+
build:
72+
name: Build
73+
runs-on: ubuntu-latest
74+
steps:
75+
- name: Checkout code
76+
uses: actions/checkout@v4
77+
78+
- name: Setup Node.js
79+
uses: actions/setup-node@v4
80+
with:
81+
node-version: "20"
82+
cache: "npm"
83+
84+
- name: Install dependencies
85+
run: npm ci
86+
87+
- name: Build
88+
run: npm run build
89+
90+
- name: Verify CLI is executable
91+
run: |
92+
node dist/cli/index.js --version
93+
node dist/cli/index.js --help
94+
95+
# Summary job that depends on all others - useful for branch protection
96+
ci-success:
97+
name: CI Success
98+
needs: [lint, typecheck, test, build]
99+
runs-on: ubuntu-latest
100+
if: always()
101+
steps:
102+
- name: Check all jobs passed
103+
run: |
104+
if [[ "${{ needs.lint.result }}" != "success" ]] || \
105+
[[ "${{ needs.typecheck.result }}" != "success" ]] || \
106+
[[ "${{ needs.test.result }}" != "success" ]] || \
107+
[[ "${{ needs.build.result }}" != "success" ]]; then
108+
echo "One or more jobs failed"
109+
exit 1
110+
fi
111+
echo "All CI jobs passed!"

.github/workflows/release.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
jobs:
9+
release:
10+
name: Release
11+
runs-on: ubuntu-latest
12+
permissions:
13+
contents: write
14+
id-token: write
15+
steps:
16+
- name: Checkout code
17+
uses: actions/checkout@v4
18+
19+
- name: Setup Node.js
20+
uses: actions/setup-node@v4
21+
with:
22+
node-version: "20"
23+
cache: "npm"
24+
registry-url: "https://registry.npmjs.org"
25+
26+
- name: Install dependencies
27+
run: npm ci
28+
29+
- name: Run tests
30+
run: npm run test:run
31+
32+
- name: Build
33+
run: npm run build
34+
35+
- name: Publish to npm
36+
run: npm publish --provenance --access public
37+
env:
38+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
39+
40+
- name: Create GitHub Release
41+
uses: softprops/action-gh-release@v1
42+
with:
43+
generate_release_notes: true
44+
env:
45+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

src/web/generator.ts

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -64,35 +64,45 @@ function renderDecision(decision: Decision): string {
6464

6565
return `
6666
<div class="decision">
67-
<div class="decision-title">${escapeHtml(decision.title)}</div>
67+
<div class="decision-title">${escapeHtml(decision.question)}: ${escapeHtml(decision.chosen)}</div>
6868
<div class="decision-reasoning">${escapeHtml(decision.reasoning)}</div>
6969
${alternatives}
7070
</div>
7171
`;
7272
}
7373

7474
function renderEvent(event: TrajectoryEvent): string {
75-
const time = formatDate(event.timestamp);
75+
const time = formatDate(new Date(event.ts).toISOString());
7676
let content = "";
7777
let typeClass = "";
78+
const rawData = event.raw as Record<string, unknown> | undefined;
7879

7980
switch (event.type) {
8081
case "decision":
8182
typeClass = "decision";
8283
content = `
8384
<strong>Decision:</strong> ${escapeHtml(event.content)}
84-
${event.metadata?.reasoning ? `<div class="decision-reasoning">${escapeHtml(event.metadata.reasoning)}</div>` : ""}
85+
${rawData?.reasoning ? `<div class="decision-reasoning">${escapeHtml(String(rawData.reasoning))}</div>` : ""}
8586
`;
8687
break;
87-
case "observation":
88-
content = `<strong>Observed:</strong> ${escapeHtml(event.content)}`;
88+
case "thinking":
89+
content = `<strong>Thinking:</strong> ${escapeHtml(event.content)}`;
8990
break;
90-
case "action":
91-
content = `<strong>Action:</strong> ${escapeHtml(event.content)}`;
91+
case "prompt":
92+
content = `<strong>Prompt:</strong> ${escapeHtml(event.content)}`;
9293
break;
9394
case "tool_call":
9495
content = `<strong>Tool:</strong> <code>${escapeHtml(event.content)}</code>`;
9596
break;
97+
case "tool_result":
98+
content = `<strong>Result:</strong> ${escapeHtml(event.content)}`;
99+
break;
100+
case "message_sent":
101+
content = `<strong>Sent:</strong> ${escapeHtml(event.content)}`;
102+
break;
103+
case "message_received":
104+
content = `<strong>Received:</strong> ${escapeHtml(event.content)}`;
105+
break;
96106
case "error":
97107
content = `<strong style="color: var(--error)">Error:</strong> ${escapeHtml(event.content)}`;
98108
break;
@@ -120,7 +130,6 @@ function renderChapter(chapter: Chapter, index: number): string {
120130
Chapter ${index + 1}: ${escapeHtml(chapter.title)}
121131
</div>
122132
<div class="chapter-agent">Agent: ${escapeHtml(chapter.agentName)}</div>
123-
${chapter.summary ? `<p>${escapeHtml(chapter.summary)}</p>` : ""}
124133
${
125134
chapter.events.length > 0
126135
? `
@@ -143,16 +152,20 @@ function renderRetrospective(trajectory: Trajectory): string {
143152
const retro = trajectory.retrospective;
144153
const confidencePercent = Math.round(retro.confidence * 100);
145154

146-
const wentWell = retro.wentWell?.length
147-
? `<div><strong>What went well:</strong><ul class="list">${retro.wentWell.map((w) => `<li>${escapeHtml(w)}</li>`).join("")}</ul></div>`
155+
const approach = retro.approach
156+
? `<div><strong>Approach:</strong><p>${escapeHtml(retro.approach)}</p></div>`
157+
: "";
158+
159+
const learnings = retro.learnings?.length
160+
? `<div><strong>Learnings:</strong><ul class="list">${retro.learnings.map((l) => `<li>${escapeHtml(l)}</li>`).join("")}</ul></div>`
148161
: "";
149162

150163
const challenges = retro.challenges?.length
151164
? `<div><strong>Challenges:</strong><ul class="list">${retro.challenges.map((c) => `<li>${escapeHtml(c)}</li>`).join("")}</ul></div>`
152165
: "";
153166

154-
const wouldDoDifferently = retro.wouldDoDifferently?.length
155-
? `<div><strong>Would do differently:</strong><ul class="list">${retro.wouldDoDifferently.map((w) => `<li>${escapeHtml(w)}</li>`).join("")}</ul></div>`
167+
const suggestions = retro.suggestions?.length
168+
? `<div><strong>Suggestions:</strong><ul class="list">${retro.suggestions.map((s) => `<li>${escapeHtml(s)}</li>`).join("")}</ul></div>`
156169
: "";
157170

158171
return `
@@ -168,9 +181,10 @@ function renderRetrospective(trajectory: Trajectory): string {
168181
<span>${confidencePercent}%</span>
169182
</div>
170183
171-
${wentWell}
184+
${approach}
185+
${learnings}
172186
${challenges}
173-
${wouldDoDifferently}
187+
${suggestions}
174188
</div>
175189
`;
176190
}
@@ -180,14 +194,11 @@ export function generateTrajectoryHtml(trajectory: Trajectory): string {
180194
const duration = formatDuration(trajectory.startedAt, trajectory.completedAt);
181195

182196
// Extract all decisions from chapters
183-
const decisions = trajectory.chapters.flatMap((ch) =>
197+
const decisions: Decision[] = trajectory.chapters.flatMap((ch) =>
184198
ch.events
185-
.filter((e) => e.type === "decision")
186-
.map((e) => ({
187-
title: e.content,
188-
reasoning: e.metadata?.reasoning || "",
189-
alternatives: e.metadata?.alternatives as string[] | undefined,
190-
}))
199+
.filter((e) => e.type === "decision" && e.raw)
200+
.map((e) => e.raw as Decision)
201+
.filter((d): d is Decision => d !== undefined && typeof d.question === "string")
191202
);
192203

193204
const decisionsHtml = decisions.length

0 commit comments

Comments
 (0)