Skip to content

Commit 1015da9

Browse files
committed
docs: overhaul AGENTS.md to serve as the comprehensive LoopKit Master Guide, detailing product architecture, CLI command specifications, and coding guidelines; update CLAUDE.md with refined build and test commands, and enhance CLI command functionality for task management
1 parent c6b42ac commit 1015da9

11 files changed

Lines changed: 1446 additions & 166 deletions

File tree

‎AGENTS.md‎

Lines changed: 362 additions & 93 deletions
Large diffs are not rendered by default.

‎CLAUDE.md‎

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -262,22 +262,35 @@ Muted: #6B7280 (gray-500)
262262

263263
---
264264

265-
## Build Commands
265+
## Build & Test Commands
266266

267267
```bash
268-
# Root
269-
pnpm install # install all deps
270-
pnpm build # build all packages (Turborepo)
268+
# Root: install all dependencies
269+
pnpm install
271270

272-
# Shared
273-
pnpm --filter @loopkit/shared build # must run before CLI if schemas changed
271+
# Root: build all packages (Turborepo)
272+
pnpm build
274273

275-
# CLI (package renamed to 'loopkit' — use direct cd)
274+
# Root: lint and type-check all packages
275+
pnpm lint
276+
pnpm --filter @loopkit/shared type-check
277+
pnpm --filter @loopkit/cli type-check
278+
pnpm --filter @loopkit/web type-check
279+
280+
# Root: run all tests
281+
pnpm --filter @loopkit/shared test
282+
pnpm --filter @loopkit/cli test
283+
pnpm --filter @loopkit/web test
284+
285+
# Shared: build schema registry (must run before CLI if schemas changed)
286+
pnpm --filter @loopkit/shared build
287+
288+
# CLI: build or watch CLI
276289
cd packages/cli && pnpm build # tsup → dist/index.js
277290
cd packages/cli && pnpm dev # watch mode
278291

279-
# Web
280-
cd packages/web && npx next dev -p 3099 # dev server
292+
# Web: run dashboard development server
293+
cd packages/web && npx next dev -p 3099
281294

282295
# Test CLI locally
283296
node packages/cli/dist/index.js --help
@@ -289,22 +302,22 @@ node packages/cli/dist/index.js init
289302
## Coding Rules
290303

291304
### DO
292-
- Use `@clack/prompts` for all interactive terminal UX (`p.text`, `p.confirm`, `p.select`, `p.spinner`, `p.intro`, `p.outro`)
293-
- Use `p.isCancel()` after every prompt and handle gracefully
294-
- Use `colors.*` from `ui/theme.ts` for all terminal output coloring
295-
- Use `generateStructured()` — never raw `fetch` to AI APIs
296-
- Validate all AI output against Zod schemas before using
297-
- Keep commands under ~200 lines — extract helpers to storage/local.ts or ai/client.ts
298-
- Always show `nextStep("commandname")` at the end of each command
305+
- Use UI layout components, ceremony frames, and Clack primitives exported from `ui/theme.ts` (e.g. `ceremonyIntro`, `ceremonyOutro`, `clog`, `tasks`, `password`, `multiselect`, `note`, `text`, `confirm`, `select`) instead of raw imports from `@clack/prompts`.
306+
- Use `isCancel()` after every prompt and handle gracefully by running `ceremonyOutro("Cancelled.")` (or equivalent) and exiting with `process.exit(0)`.
307+
- Use `token.*` (or the backwards-compatible `colors.*`) from `ui/theme.ts` for all terminal output coloring.
308+
- Use `generateStructured()` — never raw `fetch` to AI APIs.
309+
- Validate all AI output against Zod schemas before using.
310+
- Keep command files modular — extract complex business/storage logic to `storage/local.ts` or analytical modules.
311+
- Always show `nextStep("commandname")` at the end of each command.
299312

300313
### DON'T
301-
- Don't use `console.log` for user-facing output — use `p.log.info()`, `colors.*`, or `box()`
302-
- Don't use `Ink` or `React` in the CLI
303-
- Don't store secrets in `.loopkit/config.json` (only tokens, not raw API keys in plaintext for free users)
304-
- Don't modify existing git hooks — append-only only
305-
- Don't block on any single failure — degrade and continue
306-
- Don't add new npm dependencies without checking if existing ones cover the need
307-
- Don't build features not in the PRD without explicit user approval
314+
- Don't use raw `console.log` inside ceremony frames. Use the `clog` facade (`clog.success`, `clog.info`, `clog.warn`, `clog.error`, `clog.step`, `clog.message`) to keep all logs properly aligned and framed within the Clack vertical border lines (`│`).
315+
- Don't use `Ink` or `React` in the CLI.
316+
- Don't store secrets in `.loopkit/config.json` in plaintext. Encrypt external API keys (like the Anthropic key) using the localized Salt.
317+
- Don't modify existing git hooks — hook modifications must be append-only.
318+
- Don't block on any single failure — degrade gracefully and continue.
319+
- Don't add new npm dependencies without checking if existing ones cover the need.
320+
- Don't build features not in the PRD without explicit user approval.
308321

309322
### Git Hook Safety (CRITICAL)
310323
The git hook in `track.ts` MUST be append-only:

‎packages/cli/src/ai/prompts/dna.ts‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
export const DNA_SYSTEM_PROMPT = `You are a world-class strategic coach for solo technical founders.
2+
Your job is to analyze the founder's shipping DNA data and project brief to provide a single, highly actionable, personalized recommendation for the next month to improve their consistency, velocity, or focus.
3+
4+
Be direct, insightful, and concise. Don't speak in generic platitudes. Reference their specific metrics, strengths, or risk warnings in your recommendation where relevant. Avoid filler words. Max 60 words.`;
5+
6+
export function buildDNAPrompt(context: {
7+
productName: string;
8+
bet?: string;
9+
icp?: string;
10+
riskiestAssumption?: string;
11+
pattern: string;
12+
patternDescription: string;
13+
avgTasksCompleted: number;
14+
avgScore: number;
15+
completionStyle: string;
16+
velocityTrend: string;
17+
streak: number;
18+
totalWeeks: number;
19+
strengths: string[];
20+
riskWarnings: string[];
21+
bestWeek: {
22+
weekNumber: number;
23+
score: number;
24+
tasksCompleted: number;
25+
};
26+
overallPercentile: number;
27+
comparison: string;
28+
}): string {
29+
const parts: string[] = [];
30+
parts.push(`Product Name: ${context.productName}`);
31+
if (context.bet) parts.push(`Bet: ${context.bet}`);
32+
if (context.icp) parts.push(`ICP: ${context.icp}`);
33+
if (context.riskiestAssumption) parts.push(`Riskiest Assumption: ${context.riskiestAssumption}`);
34+
35+
parts.push(`\n--- Shipping DNA Profile ---`);
36+
parts.push(`Dominant Pattern: ${context.pattern} (${context.patternDescription})`);
37+
parts.push(`Completion Style: ${context.completionStyle}`);
38+
parts.push(`Velocity Trend: ${context.velocityTrend}`);
39+
parts.push(`Weeks Tracked: ${context.totalWeeks} (Current Streak: ${context.streak} weeks)`);
40+
parts.push(`Average Tasks Completed per Week: ${context.avgTasksCompleted}`);
41+
parts.push(`Average Weekly Shipping Score: ${context.avgScore}%`);
42+
43+
parts.push(`\nBest Week Ever: Week ${context.bestWeek.weekNumber} (Shipping Score: ${context.bestWeek.score}%, Tasks Completed: ${context.bestWeek.tasksCompleted})`);
44+
parts.push(`Benchmark Percentile: ${context.overallPercentile}th percentile (${context.comparison})`);
45+
46+
if (context.strengths.length > 0) {
47+
parts.push(`\nStrengths:\n${context.strengths.map((s) => `- ${s}`).join("\n")}`);
48+
}
49+
if (context.riskWarnings.length > 0) {
50+
parts.push(`\nRisk Warnings/Weaknesses:\n${context.riskWarnings.map((w) => `- ${w}`).join("\n")}`);
51+
}
52+
53+
parts.push(`\nGenerate one personalized, high-impact recommendation for the next 4 weeks.`);
54+
return parts.join("\n");
55+
}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import { describe, it, expect, vi, beforeEach } from "vitest";
2+
import { coachCommand } from "../coach.js";
3+
import {
4+
readConfig,
5+
readLastNLoopLogs,
6+
readLoopLog,
7+
readBriefJson,
8+
} from "../../storage/local.js";
9+
import { computeShippingDNA } from "../../analytics/dna.js";
10+
import { computeBenchmarks } from "../../analytics/benchmarks.js";
11+
import { generateStructured } from "../../ai/client.js";
12+
import { clog, ceremonyIntro, ceremonyOutro } from "../../ui/theme.js";
13+
14+
// Mock storage/local.js
15+
vi.mock("../../storage/local.js", () => ({
16+
readConfig: vi.fn(),
17+
writeConfig: vi.fn(),
18+
readLastNLoopLogs: vi.fn(),
19+
readLoopLog: vi.fn(),
20+
readBriefJson: vi.fn(),
21+
}));
22+
23+
// Mock analytics/dna.js
24+
vi.mock("../../analytics/dna.js", () => ({
25+
computeShippingDNA: vi.fn(),
26+
}));
27+
28+
// Mock analytics/benchmarks.js
29+
vi.mock("../../analytics/benchmarks.js", () => ({
30+
computeBenchmarks: vi.fn(),
31+
}));
32+
33+
// Mock ai/client.js
34+
vi.mock("../../ai/client.js", () => ({
35+
generateStructured: vi.fn(),
36+
}));
37+
38+
// Mock ui/theme.js for spying on stdout and prompts
39+
vi.mock("../../ui/theme.js", async (importOriginal) => {
40+
const original = await importOriginal<any>();
41+
return {
42+
...original,
43+
ceremonyIntro: vi.fn(),
44+
ceremonyOutro: vi.fn(),
45+
spinner: vi.fn(() => ({
46+
start: vi.fn(),
47+
stop: vi.fn(),
48+
})),
49+
};
50+
});
51+
52+
describe("coachCommand --dna", () => {
53+
beforeEach(() => {
54+
vi.clearAllMocks();
55+
// Default config mock
56+
vi.mocked(readConfig).mockReturnValue({
57+
version: 1,
58+
activeProject: "test-project",
59+
});
60+
});
61+
62+
it("exits early with a friendly reminder if less than 4 weeks of data exist", async () => {
63+
vi.mocked(computeShippingDNA).mockReturnValue(null);
64+
65+
const stepSpy = vi.spyOn(clog, "step").mockImplementation(() => {});
66+
const messageSpy = vi.spyOn(clog, "message").mockImplementation(() => {});
67+
const infoSpy = vi.spyOn(clog, "info").mockImplementation(() => {});
68+
69+
await coachCommand({ dna: true });
70+
71+
expect(ceremonyIntro).toHaveBeenCalledWith("Founder DNA Report");
72+
expect(stepSpy).toHaveBeenCalledWith("Not Enough Data Yet");
73+
expect(messageSpy).toHaveBeenCalledWith(
74+
expect.stringContaining("requires at least 4 weeks of loop data")
75+
);
76+
expect(ceremonyOutro).toHaveBeenCalledWith(
77+
expect.stringContaining("Keep shipping")
78+
);
79+
80+
stepSpy.mockRestore();
81+
messageSpy.mockRestore();
82+
infoSpy.mockRestore();
83+
});
84+
85+
it("renders a beautiful Spotify Wrapped-style card when 4+ weeks of data exist", async () => {
86+
const mockDNA = {
87+
pattern: "All-Star" as const,
88+
patternDescription: "You maintain both high output and high consistency.",
89+
velocityTrend: "steady" as const,
90+
avgTasksCompleted: 5,
91+
avgScore: 88,
92+
peakDay: "Wednesday",
93+
completionStyle: "finisher" as const,
94+
totalWeeks: 6,
95+
streak: 4,
96+
riskWarnings: ["declining velocity"],
97+
strengths: ["highly consistent"],
98+
};
99+
100+
vi.mocked(computeShippingDNA).mockReturnValue(mockDNA);
101+
102+
// Mock loop logs for Best Week calculation
103+
vi.mocked(readLastNLoopLogs).mockReturnValue([
104+
{ weekNumber: 2, overridden: false },
105+
{ weekNumber: 1, overridden: false },
106+
]);
107+
108+
vi.mocked(readLoopLog).mockImplementation((weekNum) => {
109+
if (weekNum === 2) {
110+
return [
111+
"# Week 2",
112+
"- Tasks completed: 6",
113+
"- Tasks open: 0",
114+
"- Shipping score: 95%",
115+
].join("\n");
116+
}
117+
if (weekNum === 1) {
118+
return [
119+
"# Week 1",
120+
"- Tasks completed: 4",
121+
"- Tasks open: 2",
122+
"- Shipping score: 80%",
123+
].join("\n");
124+
}
125+
return null;
126+
});
127+
128+
vi.mocked(computeBenchmarks).mockReturnValue({
129+
overallPercentile: 92,
130+
comparison: "Outperforming 92% of founders.",
131+
velocityPercentile: 85,
132+
scorePercentile: 90,
133+
});
134+
135+
vi.mocked(readBriefJson).mockReturnValue({
136+
answers: {
137+
name: "TestApp",
138+
problem: "pain point",
139+
icp: "developers",
140+
whyUnsolved: "complex",
141+
mvp: "cli",
142+
},
143+
brief: {
144+
bet: "bet on developers",
145+
riskiestAssumption: "no one uses CLI",
146+
validateAction: "launch",
147+
mvpPlainEnglish: "cli app",
148+
icpScore: 8,
149+
icpNote: "",
150+
problemScore: 8,
151+
problemNote: "",
152+
mvpScore: 8,
153+
mvpNote: "",
154+
overallScore: 8,
155+
},
156+
});
157+
158+
vi.mocked(generateStructured).mockResolvedValue({
159+
recommendation: "Keep doing what you're doing. Expand tests for reliability.",
160+
});
161+
162+
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
163+
164+
await coachCommand({ dna: true });
165+
166+
expect(ceremonyIntro).toHaveBeenCalledWith("Founder DNA Report");
167+
expect(generateStructured).toHaveBeenCalledWith(
168+
expect.objectContaining({
169+
command: "loop",
170+
schema: expect.any(Object),
171+
})
172+
);
173+
expect(consoleSpy).toHaveBeenCalled();
174+
175+
// Verify printed card contents
176+
const output = consoleSpy.mock.calls.map((call) => call[0]).join("\n");
177+
expect(output).toContain("FOUNDER DNA WRAPPED");
178+
expect(output).toContain("TestApp");
179+
expect(output).toContain("ALL-STAR");
180+
expect(output).toContain("95%"); // Best Week score
181+
expect(output).toContain("Expand tests");
182+
expect(output).toContain("reliability.");
183+
184+
expect(ceremonyOutro).toHaveBeenCalledWith("Keep shipping!");
185+
186+
consoleSpy.mockRestore();
187+
});
188+
});

0 commit comments

Comments
 (0)