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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Versions follow [SemVer](https://semver.org/) (`0.1.0-alpha.x` while the public
- Default **PE Engineering Career Framework** (Graduate → CTO) seeded on empty and demo workspaces
- **Cursor Cloud Agents** AI provider — use dashboard `crsr_…` keys via the Cloud Agents API (no-repo agents for digests/drafts)
- Guided **/setup** onboarding: team → assign-first roles → optional AI → integrations with credential guides → evidence backfill with progress
- **1:1 sessions** on the person dossier — manual Q&A agenda plus optional AI-suggested questions grounded in evidence

### Changed

Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export type AiFeature =
| "peer_synthesize"
| "framework_extract"
| "meeting_summary"
| "skip_level_talking_points";
| "skip_level_talking_points"
| "one_on_one_questions";

export type ChatMessage = { role: "system" | "user" | "assistant"; content: string };

Expand Down
10 changes: 10 additions & 0 deletions apps/api/src/featureRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,16 @@ export function registerFeatureRoutes(app: Hono<any>, helpers: Helpers) {
content: `WORKSPACE EVIDENCE (${ctx.scopeLabel}):\n${ctx.lines.join("\n")}\n\nUser question: (preview — actual question sent on confirm)`,
},
];
} else if (body.feature === "one_on_one_questions" && body.personId) {
const ctx = helpers.buildEvidenceContext(body.personId, body.cycleId);
dataClassesSent = ctx.dataClassesSent;
messages = [
{ role: "system", content: buildSystemPrompt() },
{
role: "user",
content: `Suggest 1:1 questions.\n\nEVIDENCE:\n${ctx.lines.join("\n") || "(thin evidence)"}`,
},
];
} else if (body.personId && body.cycleId) {
const ctx = helpers.buildEvidenceContext(body.personId, body.cycleId);
dataClassesSent = ctx.dataClassesSent;
Expand Down
3 changes: 3 additions & 0 deletions apps/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ import { extractCitationIds, filterCitationsAgainstAllowlist } from "./citations
import { runBiasToneLint } from "./bias.js";
import { mergeLintFindings, runFaithfulnessLint } from "./faithfulness.js";
import { registerP1Routes } from "./p1Routes.js";
import { registerOneOnOneRoutes } from "./oneOnOnes.js";
import { indexReviewFts } from "./reviewSearch.js";
import { renderPromotionPacketHtml, renderSubjectPacketHtml } from "./packetHtml.js";
import {
Expand Down Expand Up @@ -4692,6 +4693,8 @@ registerP1Routes(app, {
frameworkInsufficient,
});

registerOneOnOneRoutes(app, { buildEvidenceContext });

registerFeatureRoutes(app, {
buildEvidenceContext,
buildWorkspaceContext,
Expand Down
27 changes: 27 additions & 0 deletions apps/api/src/oneOnOnes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { parseSuggestedQuestions } from "../src/oneOnOnes.js";

describe("parseSuggestedQuestions", () => {
it("parses a JSON array of strings", () => {
const qs = parseSuggestedQuestions(`Here you go:\n["How is capacity?", "What blocked [ach_1]?"]\n`);
assert.deepEqual(qs, ["How is capacity?", "What blocked [ach_1]?"]);
});

it("parses objects with question fields", () => {
const qs = parseSuggestedQuestions(
JSON.stringify([{ question: "What went well last sprint?" }, { question: "Any risks?" }]),
);
assert.deepEqual(qs, ["What went well last sprint?", "Any risks?"]);
});

it("falls back to numbered lines with question marks", () => {
const qs = parseSuggestedQuestions(`1. How are you feeling about goals?\n2. What support do you need?\nNote: thin evidence`);
assert.equal(qs.length, 2);
assert.match(qs[0], /feeling about goals/);
});

it("returns empty for blank input", () => {
assert.deepEqual(parseSuggestedQuestions(" "), []);
});
});
Loading