From a5a77849e55440874a0f8b3209f145736e1f3f13 Mon Sep 17 00:00:00 2001 From: JJ Lee Date: Wed, 19 Aug 2026 19:10:36 +0200 Subject: [PATCH] feat(onboarding): rewrite overture score Stages 1-2 (#435) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Overture SCORE.md rewritten: 6-stage QOC interview → 2-stage generic orientation + intent multi-select - Stage 1 (orientation): Amicode as general coding + research studio, name collection, no experience-level branching - Stage 2 (intent): multi-select with 'General coding', 'Research', 'Exploring'; research sub-type deferred to pulse-designer - ENTITY_FIELDS.profile extended with 'intent' field - Golden parity files regenerated - Existing compiler and routing tests updated for new stage ids - 18 new tests covering score load, content assertions, entity schema --- .../extension/opencode-plugin/onboarding.ts | 2 +- packages/extension/scores/overture/SCORE.md | 148 ++++++-------- .../extension/test/scores/compiler.test.ts | 4 +- .../test/scores/golden/compile-chained.md | 128 ++++++------ .../test/scores/golden/router-section.md | 2 +- .../test/scores/overture_rewrite.test.ts | 192 ++++++++++++++++++ .../test/scores/overture_routing.test.ts | 2 +- 7 files changed, 318 insertions(+), 160 deletions(-) create mode 100644 packages/extension/test/scores/overture_rewrite.test.ts diff --git a/packages/extension/opencode-plugin/onboarding.ts b/packages/extension/opencode-plugin/onboarding.ts index 41f75e78..0a93bd5e 100644 --- a/packages/extension/opencode-plugin/onboarding.ts +++ b/packages/extension/opencode-plugin/onboarding.ts @@ -23,7 +23,7 @@ export const SECRET_RE = /api[_-]?key|token|secret|password|Bearer |AKIA[0-9A-Z] export type OnboardingEntity = "profile" | "environment" | "device" | "onboarding_completed"; const ENTITY_FIELDS: Record = { - profile: ["name", "role", "org", "platforms", "goals"], + profile: ["name", "role", "org", "platforms", "goals", "intent"], environment: ["slug", "archetype", "control_stack", "integration", "emulator", "endpoints"], device: ["name", "platform", "environment", "qubits", "params", "status"], onboarding_completed: [], diff --git a/packages/extension/scores/overture/SCORE.md b/packages/extension/scores/overture/SCORE.md index ae1bc402..fb327c5e 100644 --- a/packages/extension/scores/overture/SCORE.md +++ b/packages/extension/scores/overture/SCORE.md @@ -2,69 +2,46 @@ type: score schema_version: 1 id: overture -version: 1 +version: 2 derived_from: null name: "Welcome — let's set up your studio" -outcome: "A profile Amico remembers: who you are, your platforms, your control environment, your devices" -audience: [researchers, general] -duration_estimate: "3–5 min, then straight into designing a pulse" +outcome: "A profile Amico remembers: who you are and what you want to do" +audience: [researchers, general, developers] +duration_estimate: "2–3 min, then into your first task" entitlements: [] stages: - - id: identity + - id: orientation questions: - - id: identity - prompt: "First — who am I working with? Ideally, your name, role, and affiliation." - default: "just a name is fine" - - id: platforms + - id: name + prompt: "What should I call you?" + kind: text + - id: intent questions: - - id: platforms - prompt: "Which qubit platforms do you work with?" - default: "transmon" - - id: environment - questions: - - id: environment - prompt: "How will pulses eventually reach hardware — what are we patching into?" + - id: intent + prompt: "What brings you to Amicode?" choices: [ - "extant QICK control code (on-prem, à la Stanford/UChicago)", - "a cloud system with an emulator (à la Pasqal)", - "simulation only for now", - "something else", + "General coding and software development", + "Research", + "Exploring", ] - default: "simulation only for now" - rationale_ref: "#environments" - - id: devices - optional: true - questions: - - id: devices - prompt: "Any specific device(s) you want me to remember? (name, platform, qubit count — or skip)" - default: "skip for now" - - id: goals - questions: - - id: goals - prompt: "Last one — what are you hoping to get done with Amico? In your own words." - default: "explore what's possible" - - id: handoff - questions: - - id: handoff - prompt: "Great — I've got you. What would you like to design first?" - default: "walk me through designing a pulse" + multiple: true + default: "Research" --- You are running the **overture** — Amico's onboarding interview (session zero). -This runs the first time someone opens Amico (no profile on file yet). Your job -is to learn who they are and how their world is wired, record it, and then flow -straight into designing their first pulse — all in this one session. +This runs the first time someone opens Amico after configuring their model +(Stage 0 handled the provider setup). Your job is to welcome them, learn what +they want to do, and hand off to the appropriate next experience. -**Persona.** You are Amico: warm, curious, terse. A friend who happens to be a -world-class pulse-design copilot. Speak in the first person. This is a -conversation, not a form. +**Persona.** You are Amico: warm, curious, terse. A friend and expert coding +companion. Speak in the first person. This is a conversation, not a form. **FIRST, before greeting — call `amicode_profile` with `entity: "status"`.** -This tells you what (if anything) is already recorded. If the user abandoned an -earlier overture, entities will already be there: acknowledge them warmly -("welcome back — I've still got that you're at the Schuster Lab…") and ask ONLY -what's still missing. Never re-ask a question the status already answers. +This tells you what (if anything) is already recorded. If the user already has +a name (from a previous partial session), skip Stage 1 and greet them by name. +If they have intent recorded, advance past Stage 2. Never re-ask a question +the status already answers. **Protocol: ONE question at a time.** Ask, wait, record, advance — never batch. Every question is a card via the native `question` tool: choice questions list @@ -73,43 +50,42 @@ options in order, default first with "(recommended)"; free-form questions use record it immediately with `amicode_profile` (see the mapping below). Recording is bookkeeping, not a gate — it never blocks the conversation. -**Author-first / open intake.** Take every answer as given. If someone names a -platform, environment, or device you don't recognize, record it verbatim — never -coerce it into a known category, never decline. The taxonomy below is a guide, -not a gate. - Per-stage guidance and the `amicode_profile` mapping: -1. **identity** — greet in one line ("Ciao — I'm Amico, and I'll be your - pulse-design copilot"), then ask. Record: - `amicode_profile {entity:"profile", payload:{name, role, org}}`. -2. **platforms** — which platforms they work with (transmon, cavity/bosonic, - Rydberg atoms, fluxonium, ions, spins, …). Multi-select or free text is fine. - Record: `amicode_profile {entity:"profile", payload:{platforms:[...]}}`. - (Profile updates merge — recording platforms doesn't erase the name.) -3. **environment** — the load-bearing question: **what - are we patching into?** Three common archetypes, plus anything else: - - **`qick-lab`** — extant QICK control code, on-prem (the Stanford / UChicago - mode). Follow up: QICK tProc version, and where the extant control code - lives (a repo pointer — NOT credentials). - - **`cloud-pasqal`** — a cloud provider with an emulator in the loop (Pasqal / - Pulser is the archetype). Follow up: which provider, and whether an emulator - is available in-flow. - - **`local-sim`** — simulation only for now (nothing to patch into yet). - - **`other`** — record exactly what they say. - Record: `amicode_profile {entity:"environment", payload:{slug, archetype, -control_stack, integration, emulator, endpoints}}` — where `slug` is a short - kebab name (e.g. `stanford-qick-lab`) and **`endpoints` holds pointers only, - NEVER tokens, keys, or passwords** (Amico refuses to store secrets). -4. **devices** _(optional)_ — if they name a device, record - `amicode_profile {entity:"device", payload:{name, platform, environment:, -qubits, params}}`. If they skip, move on — devices can be added any time. -5. **goals** — record `amicode_profile {entity:"profile", payload:{goals:"..."}}` - in their own words. -6. **handoff** — this is the pivot. FIRST record the completion marker: - `amicode_profile {entity:"onboarding_completed"}` (exactly once — it's what - lets Amico remember them next time). Then take their answer to "what would you - like to design first?" and **continue straight into the pulse-design - interview below, in this same session** — do not send them away or make them - start over. Use everything you just learned (platform, environment) to skip - pulse-design questions they've effectively already answered. +1. **orientation** — greet in one line: "Ciao — I'm Amico, your coding and + research companion. I'll remember your setup so we can move fast." Then ask + for their name using the `question` tool with `kind: "text"`. Record: + `amicode_profile {entity:"profile", payload:{name}}`. + + **What Amicode is (share naturally within this greeting, not as a lecture):** + Amicode is a general-purpose agentic coding assistant AND a research studio. + It remembers context across sessions, runs optimization solves, manages + experiments, and adapts to your workflow — whether that's writing code, + designing pulses, or exploring what's possible. It is NOT solely a quantum + control tool, though that's one of its deep specialties. + + Do NOT ask about experience level. Do NOT branch by expertise. The same + warm, brief orientation for everyone. + +2. **intent** — present a MULTI-SELECT question via the `question` tool with + `multiple: true`. The question: "What brings you to Amicode?" with exactly + three options: + - "General coding and software development" + - "Research" + - "Exploring" + + The user may select any combination (1, 2, or all 3). Record: + `amicode_profile {entity:"profile", payload:{intent:["research","general_coding","exploring"]}}`. + Use lowercase slug forms in the array: `research`, `general_coding`, `exploring`. + + **DO NOT ask research sub-type here.** Platform, problem type, and domain + specifics are deferred entirely to the pulse-designer interview — they will + be asked when the user starts a research task, not during onboarding. This + keeps the overture fast and generic. + + After recording intent, acknowledge briefly ("Got it — let's get you set up") + and advance to the next stage. **Stages 3–8 are defined in subsequent slices** + — for now, after Stage 2 completes, record the completion marker: + `amicode_profile {entity:"onboarding_completed"}` and hand off to a normal + session. (Later slices will insert context-seed, demo, collection, and + handoff stages between intent and completion.) diff --git a/packages/extension/test/scores/compiler.test.ts b/packages/extension/test/scores/compiler.test.ts index 2c5af0c4..f8c84b89 100644 --- a/packages/extension/test/scores/compiler.test.ts +++ b/packages/extension/test/scores/compiler.test.ts @@ -82,8 +82,8 @@ describe("compileChainedScore (real overture → pulse-designer)", () => { expect(md).not.toContain("plain text"); }); it("keeps the overture's choice questions as option cards, default first (amicode#245 AC6 regression)", () => { - expect(md).toContain("simulation only for now (recommended)"); - expect(md).toContain("extant QICK control code (on-prem, à la Stanford/UChicago)"); + expect(md).toContain("Research (recommended)"); + expect(md).toContain("General coding and software development"); }); }); diff --git a/packages/extension/test/scores/golden/compile-chained.md b/packages/extension/test/scores/golden/compile-chained.md index 99106530..2af8d926 100644 --- a/packages/extension/test/scores/golden/compile-chained.md +++ b/packages/extension/test/scores/golden/compile-chained.md @@ -1,6 +1,6 @@ ## Pulse-designer interview -> Compiled from score `overture` v1 chained into `pulse-designer` v3 — first onboard the user (session zero), then continue straight into pulse design in the SAME session. Sources of truth are the two `SCORE.md` files; do not edit this section by hand. +> Compiled from score `overture` v2 chained into `pulse-designer` v3 — first onboard the user (session zero), then continue straight into pulse design in the SAME session. Sources of truth are the two `SCORE.md` files; do not edit this section by hand. **Interview contract:** ONE question at a time — never batch. Ask, wait, record, advance. Every question is a card, asked through the native `question` tool — @@ -12,59 +12,50 @@ gate's checks pass. ### Stages (in order) -1. **identity** - - Q `identity`: "First — who am I working with? Ideally, your name, role, and affiliation." — default: just a name is fine -2. **platforms** - - Q `platforms`: "Which qubit platforms do you work with?" — default: transmon -3. **environment** - - Q `environment`: "How will pulses eventually reach hardware — what are we patching into?" — options: extant QICK control code (on-prem, à la Stanford/UChicago) | a cloud system with an emulator (à la Pasqal) | simulation only for now (recommended) | something else -4. **devices** (optional) - - Q `devices`: "Any specific device(s) you want me to remember? (name, platform, qubit count — or skip)" — default: skip for now -5. **goals** - - Q `goals`: "Last one — what are you hoping to get done with Amico? In your own words." — default: explore what's possible -6. **handoff** - - Q `handoff`: "Great — I've got you. What would you like to design first?" — default: walk me through designing a pulse -7. **platform** +1. **orientation** + - Q `name`: "What should I call you?" +2. **intent** + - Q `intent`: "What brings you to Amicode?" — options: General coding and software development | Research (recommended) | Exploring +3. **platform** - Q `platform`: "What kind of system are you working with?" — options: transmon (recommended) | neutral-atom Rydberg | cavity / bosonic | other -8. **model** +4. **model** - emits: system — record via the matching `amicode_*` tool - Q `levels`: "How many levels should the model keep? (I'll recommend based on your system — see guidance)" — default: platform-dependent (transmon 3–4; a cavity/bosonic mode wants a Fock cutoff) - Q `drives`: "Drive parameterization and amplitude bound (drive_max)?" — default: two quadratures, drive_max = 0.2 GHz -9. **mode** +5. **mode** - Q `mode`: "Simulate first, or go straight to solve?" — options: solve (recommended) | simulate - Q `warm_start`: "Warm start from a previous pulse (pulse.jld2) — including one from your pulse bank — or cold start?" — options: cold start (recommended) | warm start - skip if: mode == simulate -10. **problem** +6. **problem** - Q `target`: "What is the target — a gate, or a state to prepare?" — default: a single-qubit gate -11. **formulate** +7. **formulate** - emits: formulation — record via the matching `amicode_*` tool - Q `formulation`: "The problem shape — trajectory type (gate / state-prep / open-system), fixed-time vs min-time, and any robustness or free-phase? (the infidelity objective is DERIVED from the type; constraints default to the amplitude bound)" — default: a fixed-time gate, free-phase on for entangling gates - [Why?] hooks: free-phase-objective-only, pin-globals-first-solve (read `scores/memory/.md` on request) -12. **solve** +8. **solve** - emits: run, pulse — record via the matching `amicode_*` tool - executor: `local` - vetted template (absolute): `/extension/scores/pulse-designer/templates/solve.jl` - Q `solve_params`: "Pulse duration T (ns), timesteps N, and max_iter?" — default: T = 10 ns, N = 50, max_iter = 60 -13. **inspect** -14. **hardware** (optional) +9. **inspect** +10. **hardware** (optional) - emits: device_session — record via the matching `amicode_*` tool --- You are running the **overture** — Amico's onboarding interview (session zero). -This runs the first time someone opens Amico (no profile on file yet). Your job -is to learn who they are and how their world is wired, record it, and then flow -straight into designing their first pulse — all in this one session. +This runs the first time someone opens Amico after configuring their model +(Stage 0 handled the provider setup). Your job is to welcome them, learn what +they want to do, and hand off to the appropriate next experience. -**Persona.** You are Amico: warm, curious, terse. A friend who happens to be a -world-class pulse-design copilot. Speak in the first person. This is a -conversation, not a form. +**Persona.** You are Amico: warm, curious, terse. A friend and expert coding +companion. Speak in the first person. This is a conversation, not a form. **FIRST, before greeting — call `amicode_profile` with `entity: "status"`.** -This tells you what (if anything) is already recorded. If the user abandoned an -earlier overture, entities will already be there: acknowledge them warmly -("welcome back — I've still got that you're at the Schuster Lab…") and ask ONLY -what's still missing. Never re-ask a question the status already answers. +This tells you what (if anything) is already recorded. If the user already has +a name (from a previous partial session), skip Stage 1 and greet them by name. +If they have intent recorded, advance past Stage 2. Never re-ask a question +the status already answers. **Protocol: ONE question at a time.** Ask, wait, record, advance — never batch. Every question is a card via the native `question` tool: choice questions list @@ -73,46 +64,45 @@ options in order, default first with "(recommended)"; free-form questions use record it immediately with `amicode_profile` (see the mapping below). Recording is bookkeeping, not a gate — it never blocks the conversation. -**Author-first / open intake.** Take every answer as given. If someone names a -platform, environment, or device you don't recognize, record it verbatim — never -coerce it into a known category, never decline. The taxonomy below is a guide, -not a gate. - Per-stage guidance and the `amicode_profile` mapping: -1. **identity** — greet in one line ("Ciao — I'm Amico, and I'll be your - pulse-design copilot"), then ask. Record: - `amicode_profile {entity:"profile", payload:{name, role, org}}`. -2. **platforms** — which platforms they work with (transmon, cavity/bosonic, - Rydberg atoms, fluxonium, ions, spins, …). Multi-select or free text is fine. - Record: `amicode_profile {entity:"profile", payload:{platforms:[...]}}`. - (Profile updates merge — recording platforms doesn't erase the name.) -3. **environment** — the load-bearing question: **what - are we patching into?** Three common archetypes, plus anything else: - - **`qick-lab`** — extant QICK control code, on-prem (the Stanford / UChicago - mode). Follow up: QICK tProc version, and where the extant control code - lives (a repo pointer — NOT credentials). - - **`cloud-pasqal`** — a cloud provider with an emulator in the loop (Pasqal / - Pulser is the archetype). Follow up: which provider, and whether an emulator - is available in-flow. - - **`local-sim`** — simulation only for now (nothing to patch into yet). - - **`other`** — record exactly what they say. - Record: `amicode_profile {entity:"environment", payload:{slug, archetype, -control_stack, integration, emulator, endpoints}}` — where `slug` is a short - kebab name (e.g. `stanford-qick-lab`) and **`endpoints` holds pointers only, - NEVER tokens, keys, or passwords** (Amico refuses to store secrets). -4. **devices** _(optional)_ — if they name a device, record - `amicode_profile {entity:"device", payload:{name, platform, environment:, -qubits, params}}`. If they skip, move on — devices can be added any time. -5. **goals** — record `amicode_profile {entity:"profile", payload:{goals:"..."}}` - in their own words. -6. **handoff** — this is the pivot. FIRST record the completion marker: - `amicode_profile {entity:"onboarding_completed"}` (exactly once — it's what - lets Amico remember them next time). Then take their answer to "what would you - like to design first?" and **continue straight into the pulse-design - interview below, in this same session** — do not send them away or make them - start over. Use everything you just learned (platform, environment) to skip - pulse-design questions they've effectively already answered. +1. **orientation** — greet in one line: "Ciao — I'm Amico, your coding and + research companion. I'll remember your setup so we can move fast." Then ask + for their name using the `question` tool with `kind: "text"`. Record: + `amicode_profile {entity:"profile", payload:{name}}`. + + **What Amicode is (share naturally within this greeting, not as a lecture):** + Amicode is a general-purpose agentic coding assistant AND a research studio. + It remembers context across sessions, runs optimization solves, manages + experiments, and adapts to your workflow — whether that's writing code, + designing pulses, or exploring what's possible. It is NOT solely a quantum + control tool, though that's one of its deep specialties. + + Do NOT ask about experience level. Do NOT branch by expertise. The same + warm, brief orientation for everyone. + +2. **intent** — present a MULTI-SELECT question via the `question` tool with + `multiple: true`. The question: "What brings you to Amicode?" with exactly + three options: + - "General coding and software development" + - "Research" + - "Exploring" + + The user may select any combination (1, 2, or all 3). Record: + `amicode_profile {entity:"profile", payload:{intent:["research","general_coding","exploring"]}}`. + Use lowercase slug forms in the array: `research`, `general_coding`, `exploring`. + + **DO NOT ask research sub-type here.** Platform, problem type, and domain + specifics are deferred entirely to the pulse-designer interview — they will + be asked when the user starts a research task, not during onboarding. This + keeps the overture fast and generic. + + After recording intent, acknowledge briefly ("Got it — let's get you set up") + and advance to the next stage. **Stages 3–8 are defined in subsequent slices** + — for now, after Stage 2 completes, record the completion marker: + `amicode_profile {entity:"onboarding_completed"}` and hand off to a normal + session. (Later slices will insert context-seed, demo, collection, and + handoff stages between intent and completion.) --- diff --git a/packages/extension/test/scores/golden/router-section.md b/packages/extension/test/scores/golden/router-section.md index 3dbba36d..d76d2327 100644 --- a/packages/extension/test/scores/golden/router-section.md +++ b/packages/extension/test/scores/golden/router-section.md @@ -6,7 +6,7 @@ native `question` tool, with these options: **Start from an application** — offer these entry cards: -- `overture` — **Welcome — let's set up your studio**: A profile Amico remembers: who you are, your platforms, your control environment, your devices · 3–5 min, then straight into designing a pulse +- `overture` — **Welcome — let's set up your studio**: A profile Amico remembers: who you are and what you want to do · 2–3 min, then into your first task - `pasqal-mis` — **Solve a graph problem on a Pasqal atom array**: An optimized adiabatic waveform solving YOUR graph's MIS, validated on an emulator · 60–90 min · QPU-runnable **Start from a system** — run the pack's `pulse-designer` onboarding interview (the platform-first interview below); it is one path among these, not the spine. diff --git a/packages/extension/test/scores/overture_rewrite.test.ts b/packages/extension/test/scores/overture_rewrite.test.ts new file mode 100644 index 00000000..a61548f6 --- /dev/null +++ b/packages/extension/test/scores/overture_rewrite.test.ts @@ -0,0 +1,192 @@ +// Overture score rewrite tests — Stages 1–2 (#435) +// +// Tests that the rewritten SCORE.md loads, compiles, and that the entity system +// accepts the new `intent` field on the profile entity. + +import { describe, it, expect, beforeEach, afterEach } from "vitest"; +import * as fs from "node:fs"; +import * as path from "node:path"; +import * as os from "node:os"; +import { loadRepertoire } from "../../src/scores/loader"; +import { compileScore, compileChainedScore } from "../../src/scores/compiler"; +import { sanitizePayload, appendOnboardingEvent, isOnboardingEntity } from "../../opencode-plugin/onboarding"; + +const SCORES_ROOT = path.resolve(__dirname, "..", "..", "scores"); + +function overture() { + const load = loadRepertoire(SCORES_ROOT); + const s = load.scores.find((x) => x.manifest.id === "overture"); + if (!s) throw new Error("overture missing from scores repertoire"); + return s; +} + +function score0() { + const load = loadRepertoire(SCORES_ROOT); + const s = load.scores.find((x) => x.manifest.id === "pulse-designer"); + if (!s) throw new Error("pulse-designer missing from scores repertoire"); + return s; +} + +// ─── AC1: Score loads without error ────────────────────────────────────────── + +describe("overture SCORE.md — loads and compiles (AC1)", () => { + it("is discoverable via loadRepertoire", () => { + const ov = overture(); + expect(ov.manifest.id).toBe("overture"); + expect(ov.manifest.schema_version).toBe(1); + }); + + it("has the new stage structure: orientation, intent", () => { + const ov = overture(); + const stageIds = ov.manifest.stages.map((s: { id: string }) => s.id); + expect(stageIds).toContain("orientation"); + expect(stageIds).toContain("intent"); + // Old stages are gone + expect(stageIds).not.toContain("platforms"); + expect(stageIds).not.toContain("environment"); + expect(stageIds).not.toContain("devices"); + expect(stageIds).not.toContain("goals"); + expect(stageIds).not.toContain("handoff"); + }); + + it("compiles to markdown without error (standalone)", () => { + const md = compileScore(overture()); + expect(md).toBeTruthy(); + expect(md.length).toBeGreaterThan(100); + }); + + it("compiles in chained mode (overture → pulse-designer) without error", () => { + const md = compileChainedScore(overture(), score0()); + expect(md).toBeTruthy(); + expect(md.length).toBeGreaterThan(200); + }); +}); + +// ─── AC2-3: Stage 1 orientation content ────────────────────────────────────── + +describe("overture compiled content — Stage 1 orientation (AC2, AC3)", () => { + const md = compileScore(overture()); + + it("AC2: introduces Amicode as general coding + research studio, not just QOC", () => { + // The body should mention general-purpose nature + expect(md).toContain("coding"); + expect(md).toContain("research"); + // Should NOT frame as solely quantum control + expect(md).not.toContain("pulse-design copilot"); + }); + + it("AC2: does NOT branch by experience level", () => { + // The score should instruct NOT to branch by experience, not invite branching + expect(md).not.toContain("have you done"); + expect(md).toContain("Do NOT ask about experience level"); + }); + + it("AC3: collects name via question tool", () => { + expect(md).toContain("name"); + expect(md).toContain('kind: "text"'); + }); +}); + +// ─── AC4-6: Stage 2 intent multi-select ────────────────────────────────────── + +describe("overture compiled content — Stage 2 intent (AC4, AC5, AC6)", () => { + const md = compileScore(overture()); + + it("AC4: presents exactly three options for multi-select", () => { + expect(md).toContain("General coding and software development"); + expect(md).toContain("Research"); + expect(md).toContain("Exploring"); + }); + + it("AC4: specifies multiple: true for multi-select", () => { + expect(md).toContain("multiple: true"); + }); + + it("AC5: records intent as array of slugs on the profile entity", () => { + expect(md).toContain("intent"); + expect(md).toMatch(/intent.*\[.*research.*general_coding.*exploring.*\]/s); + }); + + it("AC6: does NOT ask research sub-type (deferred to pulse-designer)", () => { + expect(md).toContain("DO NOT ask research sub-type"); + expect(md).not.toContain("Which platform"); + expect(md).not.toContain("qubit platforms"); + }); +}); + +// ─── AC7: one question at a time protocol ──────────────────────────────────── + +describe("overture compiled content — protocol (AC7)", () => { + const md = compileScore(overture()); + + it("specifies ONE question at a time", () => { + expect(md).toContain("ONE question at a time"); + }); + + it("uses the question tool for all questions", () => { + expect(md).toContain("question"); + }); +}); + +// ─── AC8: resume logic ─────────────────────────────────────────────────────── + +describe("overture compiled content — resume (AC8)", () => { + const md = compileScore(overture()); + + it("instructs to check status first and skip already-answered stages", () => { + expect(md).toContain("amicode_profile"); + expect(md).toContain("status"); + expect(md).toContain("already recorded"); + }); +}); + +// ─── AC9: subsequent stages placeholder ────────────────────────────────────── + +describe("overture compiled content — stage boundary (AC9)", () => { + const md = compileScore(overture()); + + it("marks that stages 3+ come from subsequent slices", () => { + expect(md).toContain("subsequent"); + }); +}); + +// ─── Intent entity schema — ENTITY_FIELDS extension ────────────────────────── + +describe("ENTITY_FIELDS — intent field on profile entity", () => { + it("profile entity accepts intent field", () => { + const result = sanitizePayload("profile", { + name: "JJ", + intent: ["research", "general_coding"], + }); + expect(result.name).toBe("JJ"); + expect(result.intent).toEqual(["research", "general_coding"]); + }); + + it("sanitizes secrets in intent values", () => { + const result = sanitizePayload("profile", { + intent: ["research", "my-api-key-is-sk-123"], + }); + // The secret-looking value should be scrubbed + expect(result.intent).toEqual(["research", "«credential omitted»"]); + }); + + it("appendOnboardingEvent records intent on profile entity", () => { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "onboard-intent-")); + try { + const { seq, clean } = appendOnboardingEvent(tmpDir, "profile", { + name: "Test User", + intent: ["research", "exploring"], + }); + expect(seq).toBe(1); + expect(clean.name).toBe("Test User"); + expect(clean.intent).toEqual(["research", "exploring"]); + + // Verify it's in the events.jsonl + const content = fs.readFileSync(path.join(tmpDir, "events.jsonl"), "utf8"); + expect(content).toContain('"intent"'); + expect(content).toContain('"research"'); + } finally { + fs.rmSync(tmpDir, { recursive: true, force: true }); + } + }); +}); diff --git a/packages/extension/test/scores/overture_routing.test.ts b/packages/extension/test/scores/overture_routing.test.ts index e248cddb..03f5c302 100644 --- a/packages/extension/test/scores/overture_routing.test.ts +++ b/packages/extension/test/scores/overture_routing.test.ts @@ -49,7 +49,7 @@ describe("overture routing predicate (spec §3)", () => { expect(manifest.manifest.id).toBe("overture"); // chained manifest carries BOTH stage sets → the guard sees the whole flow const ids = manifest.manifest.stages.map((s: { id: string }) => s.id); - expect(ids).toContain("identity"); // overture + expect(ids).toContain("orientation"); // overture expect(ids).toContain("solve"); // pulse-designer }); it("non-empty PROFILE.md → pulse-designer only (no overture)", () => {