@vibe-hero/server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +151 -0
- package/dist/catalog/bundled/claude-code/.gitkeep +0 -0
- package/dist/catalog/bundled/claude-code/context-management.yaml +302 -0
- package/dist/catalog/bundled/claude-code/planning.yaml +313 -0
- package/dist/catalog/bundled/claude-code/subagents.yaml +357 -0
- package/dist/catalog/bundled/general/.gitkeep +0 -0
- package/dist/catalog/bundled/general/_placeholder.yaml +39 -0
- package/dist/catalog/bundled/general/task-decomposition.yaml +390 -0
- package/dist/catalog/bundled/index.d.ts +39 -0
- package/dist/catalog/bundled/index.d.ts.map +1 -0
- package/dist/catalog/bundled/index.js +41 -0
- package/dist/catalog/bundled/index.js.map +1 -0
- package/dist/catalog/fetcher.d.ts +201 -0
- package/dist/catalog/fetcher.d.ts.map +1 -0
- package/dist/catalog/fetcher.js +452 -0
- package/dist/catalog/fetcher.js.map +1 -0
- package/dist/catalog/loader.d.ts +165 -0
- package/dist/catalog/loader.d.ts.map +1 -0
- package/dist/catalog/loader.js +241 -0
- package/dist/catalog/loader.js.map +1 -0
- package/dist/catalog/resolve.d.ts +85 -0
- package/dist/catalog/resolve.d.ts.map +1 -0
- package/dist/catalog/resolve.js +103 -0
- package/dist/catalog/resolve.js.map +1 -0
- package/dist/cli/getOffer.d.ts +38 -0
- package/dist/cli/getOffer.d.ts.map +1 -0
- package/dist/cli/getOffer.js +150 -0
- package/dist/cli/getOffer.js.map +1 -0
- package/dist/cli/index.d.ts +46 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +88 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config.d.ts +34 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +63 -0
- package/dist/config.js.map +1 -0
- package/dist/engine/elo.d.ts +76 -0
- package/dist/engine/elo.d.ts.map +1 -0
- package/dist/engine/elo.js +79 -0
- package/dist/engine/elo.js.map +1 -0
- package/dist/engine/graduation.d.ts +108 -0
- package/dist/engine/graduation.d.ts.map +1 -0
- package/dist/engine/graduation.js +161 -0
- package/dist/engine/graduation.js.map +1 -0
- package/dist/engine/lapse.d.ts +80 -0
- package/dist/engine/lapse.d.ts.map +1 -0
- package/dist/engine/lapse.js +125 -0
- package/dist/engine/lapse.js.map +1 -0
- package/dist/engine/selection.d.ts +84 -0
- package/dist/engine/selection.d.ts.map +1 -0
- package/dist/engine/selection.js +119 -0
- package/dist/engine/selection.js.map +1 -0
- package/dist/grading/deterministic.d.ts +102 -0
- package/dist/grading/deterministic.d.ts.map +1 -0
- package/dist/grading/deterministic.js +118 -0
- package/dist/grading/deterministic.js.map +1 -0
- package/dist/grading/freeform.d.ts +64 -0
- package/dist/grading/freeform.d.ts.map +1 -0
- package/dist/grading/freeform.js +85 -0
- package/dist/grading/freeform.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +91 -0
- package/dist/index.js.map +1 -0
- package/dist/observation/hookEvents.d.ts +113 -0
- package/dist/observation/hookEvents.d.ts.map +1 -0
- package/dist/observation/hookEvents.js +170 -0
- package/dist/observation/hookEvents.js.map +1 -0
- package/dist/observation/offers.d.ts +215 -0
- package/dist/observation/offers.d.ts.map +1 -0
- package/dist/observation/offers.js +327 -0
- package/dist/observation/offers.js.map +1 -0
- package/dist/observation/source.d.ts +133 -0
- package/dist/observation/source.d.ts.map +1 -0
- package/dist/observation/source.js +105 -0
- package/dist/observation/source.js.map +1 -0
- package/dist/profile/migrate.d.ts +122 -0
- package/dist/profile/migrate.d.ts.map +1 -0
- package/dist/profile/migrate.js +147 -0
- package/dist/profile/migrate.js.map +1 -0
- package/dist/profile/store.d.ts +84 -0
- package/dist/profile/store.d.ts.map +1 -0
- package/dist/profile/store.js +267 -0
- package/dist/profile/store.js.map +1 -0
- package/dist/schemas/common.d.ts +95 -0
- package/dist/schemas/common.d.ts.map +1 -0
- package/dist/schemas/common.js +106 -0
- package/dist/schemas/common.js.map +1 -0
- package/dist/schemas/content.d.ts +828 -0
- package/dist/schemas/content.d.ts.map +1 -0
- package/dist/schemas/content.js +219 -0
- package/dist/schemas/content.js.map +1 -0
- package/dist/schemas/profile.d.ts +599 -0
- package/dist/schemas/profile.d.ts.map +1 -0
- package/dist/schemas/profile.js +177 -0
- package/dist/schemas/profile.js.map +1 -0
- package/dist/schemas/tools.d.ts +1581 -0
- package/dist/schemas/tools.d.ts.map +1 -0
- package/dist/schemas/tools.js +286 -0
- package/dist/schemas/tools.js.map +1 -0
- package/dist/tools/config.d.ts +51 -0
- package/dist/tools/config.d.ts.map +1 -0
- package/dist/tools/config.js +104 -0
- package/dist/tools/config.js.map +1 -0
- package/dist/tools/gate.d.ts +50 -0
- package/dist/tools/gate.d.ts.map +1 -0
- package/dist/tools/gate.js +67 -0
- package/dist/tools/gate.js.map +1 -0
- package/dist/tools/guidance.d.ts +36 -0
- package/dist/tools/guidance.d.ts.map +1 -0
- package/dist/tools/guidance.js +117 -0
- package/dist/tools/guidance.js.map +1 -0
- package/dist/tools/listTopics.d.ts +55 -0
- package/dist/tools/listTopics.d.ts.map +1 -0
- package/dist/tools/listTopics.js +78 -0
- package/dist/tools/listTopics.js.map +1 -0
- package/dist/tools/offers.d.ts +60 -0
- package/dist/tools/offers.d.ts.map +1 -0
- package/dist/tools/offers.js +152 -0
- package/dist/tools/offers.js.map +1 -0
- package/dist/tools/placeholders.d.ts +27 -0
- package/dist/tools/placeholders.d.ts.map +1 -0
- package/dist/tools/placeholders.js +49 -0
- package/dist/tools/placeholders.js.map +1 -0
- package/dist/tools/recordObservation.d.ts +52 -0
- package/dist/tools/recordObservation.d.ts.map +1 -0
- package/dist/tools/recordObservation.js +87 -0
- package/dist/tools/recordObservation.js.map +1 -0
- package/dist/tools/startQuiz.d.ts +82 -0
- package/dist/tools/startQuiz.d.ts.map +1 -0
- package/dist/tools/startQuiz.js +180 -0
- package/dist/tools/startQuiz.js.map +1 -0
- package/dist/tools/status.d.ts +59 -0
- package/dist/tools/status.d.ts.map +1 -0
- package/dist/tools/status.js +133 -0
- package/dist/tools/status.js.map +1 -0
- package/dist/tools/submitAnswer.d.ts +156 -0
- package/dist/tools/submitAnswer.d.ts.map +1 -0
- package/dist/tools/submitAnswer.js +402 -0
- package/dist/tools/submitAnswer.js.map +1 -0
- package/dist/tools/types.d.ts +82 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +48 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/us2/standing.d.ts +111 -0
- package/dist/tools/us2/standing.d.ts.map +1 -0
- package/dist/tools/us2/standing.js +143 -0
- package/dist/tools/us2/standing.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tool-registry contract shared by every MCP tool module (T020).
|
|
3
|
+
*
|
|
4
|
+
* Each tool lives in its own module exporting a {@link ToolModule}: a name, a
|
|
5
|
+
* human description, a Zod `inputSchema` (an *object* schema — `index.ts` passes
|
|
6
|
+
* its `.shape` to the SDK's `registerTool`), and a `handler`. The handler is the
|
|
7
|
+
* pure tool logic; `index.ts` wraps it with the setup gate and adapts its plain
|
|
8
|
+
* JSON result into the SDK's `CallToolResult` shape.
|
|
9
|
+
*
|
|
10
|
+
* Keeping the handler return type a plain JSON object (not a `CallToolResult`)
|
|
11
|
+
* means tool logic stays decoupled from the transport: tests call handlers
|
|
12
|
+
* directly and assert on the JSON, and later tasks can replace placeholder
|
|
13
|
+
* handlers without touching transport wiring.
|
|
14
|
+
*
|
|
15
|
+
* Source of truth: specs/001-vibe-hero-mvp/contracts/mcp-tools.md.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Adapt a typed {@link ToolModule} into a schema-erased {@link AnyToolModule}
|
|
19
|
+
* for the registry. The returned handler parses raw input through the module's
|
|
20
|
+
* `inputSchema` (so the typed handler always receives validated, narrowed args)
|
|
21
|
+
* and delegates. Placeholder handlers that ignore their input pass through
|
|
22
|
+
* unchanged.
|
|
23
|
+
*
|
|
24
|
+
* @param tool - A fully-typed tool module.
|
|
25
|
+
* @returns The erased registry entry.
|
|
26
|
+
*/
|
|
27
|
+
export const defineTool = (tool) => ({
|
|
28
|
+
name: tool.name,
|
|
29
|
+
description: tool.description,
|
|
30
|
+
inputSchema: tool.inputSchema,
|
|
31
|
+
handler: async (args) => tool.handler(tool.inputSchema.parse(args)),
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* Adapt a plain JSON {@link ToolResult} into the SDK's `CallToolResult`.
|
|
35
|
+
*
|
|
36
|
+
* We populate both `content` (a JSON text block, the universally-readable form
|
|
37
|
+
* for hosts/tests with no output schema) and `structuredContent` (the same
|
|
38
|
+
* object, machine-readable). This keeps results inspectable without registering
|
|
39
|
+
* an output schema for every tool yet.
|
|
40
|
+
*
|
|
41
|
+
* @param result - The tool's JSON result.
|
|
42
|
+
* @returns A well-formed `CallToolResult`.
|
|
43
|
+
*/
|
|
44
|
+
export const toCallToolResult = (result) => ({
|
|
45
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
46
|
+
structuredContent: result,
|
|
47
|
+
});
|
|
48
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/tools/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAmDH;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CACxB,IAAwB,EACT,EAAE,CAAC,CAAC;IACnB,IAAI,EAAE,IAAI,CAAC,IAAI;IACf,WAAW,EAAE,IAAI,CAAC,WAAW;IAC7B,WAAW,EAAE,IAAI,CAAC,WAAW;IAC7B,OAAO,EAAE,KAAK,EAAE,IAAa,EAAuB,EAAE,CACpD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAoB,CAAC;CAChE,CAAC,CAAC;AAEH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,MAAkB,EAAkB,EAAE,CAAC,CAAC;IACvE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;IACzD,iBAAiB,EAAE,MAAM;CAC1B,CAAC,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Shared standing/ranking helpers for the US-2 read tools (T025–T027).
|
|
3
|
+
*
|
|
4
|
+
* `get_status`, `get_guidance`, and (indirectly) `list_topics` all need the same
|
|
5
|
+
* derivation: given the loaded catalog topics + the learner profile, compute the
|
|
6
|
+
* per-(topic × class) standing — its {@link AbilityKey}, displayed tier, status,
|
|
7
|
+
* and current ability — and rank topics by weakness so the weakest/stale one can
|
|
8
|
+
* be suggested or auto-selected. Centralizing it here keeps the three tools DRY
|
|
9
|
+
* and guarantees they agree on what "weak", "not_started", and "due_for_review"
|
|
10
|
+
* mean.
|
|
11
|
+
*
|
|
12
|
+
* Pure + telemetry-free (SC-011): every value is derived from the catalog
|
|
13
|
+
* (bundled or fetched) plus the profile's `abilities`/`graduations` — NO
|
|
14
|
+
* observation/offer state is consulted, so the whole pull path works with zero
|
|
15
|
+
* telemetry.
|
|
16
|
+
*
|
|
17
|
+
* Source of truth: specs/001-vibe-hero-mvp/contracts/mcp-tools.md
|
|
18
|
+
* (`get_status` / `get_guidance`), spec.md US-2 / FR-021 / SC-011.
|
|
19
|
+
*/
|
|
20
|
+
import { type AbilityKey, type ToolId } from "../../schemas/common.js";
|
|
21
|
+
import type { Topic } from "../../schemas/content.js";
|
|
22
|
+
import type { Profile } from "../../schemas/profile.js";
|
|
23
|
+
import type { StatusTopic } from "../../schemas/tools.js";
|
|
24
|
+
/**
|
|
25
|
+
* A topic's computed standing, plus the source {@link Topic} so callers can read
|
|
26
|
+
* its items/summary (e.g. for guidance text) without re-joining.
|
|
27
|
+
*/
|
|
28
|
+
export interface TopicStanding {
|
|
29
|
+
/** The serialized `(class, topicId)` key this standing is keyed by. */
|
|
30
|
+
readonly key: AbilityKey;
|
|
31
|
+
/** The source catalog topic. */
|
|
32
|
+
readonly topic: Topic;
|
|
33
|
+
/** The per-topic status row, contract-shaped for `get_status`. */
|
|
34
|
+
readonly row: StatusTopic;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Whether `topic` is in scope for `tool`. A `general` topic is always in scope
|
|
38
|
+
* (it applies to every tool); a tool-scoped topic is in scope only when its tool
|
|
39
|
+
* matches. With no `tool` filter, every topic is in scope.
|
|
40
|
+
*/
|
|
41
|
+
export declare const topicInScope: (topic: Topic, tool?: ToolId) => boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Derive the standing for one topic from the profile (telemetry-free).
|
|
44
|
+
*
|
|
45
|
+
* - `tier`/`status` come from `graduations[key]`: a learner with no graduation
|
|
46
|
+
* entry has never graduated, so tier is `0` and status is `not_started`.
|
|
47
|
+
* Otherwise the displayed tier is `currentTier` and the status mirrors the
|
|
48
|
+
* graduation's `current` / `due_for_review`.
|
|
49
|
+
* - `ability` comes from `abilities[key].value`, defaulting to the cold-start
|
|
50
|
+
* {@link ASSESSMENT_CONFIG.startingAbility} when the learner has no estimate
|
|
51
|
+
* yet (so a never-touched topic reports a sensible baseline, not `0`).
|
|
52
|
+
*/
|
|
53
|
+
export declare const standingFor: (topic: Topic, profile: Profile) => TopicStanding;
|
|
54
|
+
/**
|
|
55
|
+
* Compute every in-scope topic's standing, in catalog order.
|
|
56
|
+
*
|
|
57
|
+
* @param topics - The loaded catalog topics (bundled or fetched).
|
|
58
|
+
* @param profile - The learner profile (only `abilities`/`graduations` are read).
|
|
59
|
+
* @param tool - Optional tool filter; see {@link topicInScope}.
|
|
60
|
+
*/
|
|
61
|
+
export declare const computeStandings: (topics: readonly Topic[], profile: Profile, tool?: ToolId) => TopicStanding[];
|
|
62
|
+
/**
|
|
63
|
+
* Detect knowledge lapse (FR-009 / OD-003) across a set of standings, given the
|
|
64
|
+
* caller-injected `now` (the engine math is PURE; status.ts reads the clock and
|
|
65
|
+
* passes it in — E5). For each currently-graduated topic that has gone stale and
|
|
66
|
+
* whose decayed effective ability has fallen near/under its lower band
|
|
67
|
+
* ({@link isDueForReview}), the topic is surfaced for review:
|
|
68
|
+
*
|
|
69
|
+
* - its status row is overlaid to `due_for_review` (so `get_status` reports the
|
|
70
|
+
* stale topic correctly even before the profile is rewritten), and
|
|
71
|
+
* - its key is collected as `newlyLapsed` so the caller can enqueue a
|
|
72
|
+
* `ReviewEntry{reason:"lapsed"}` and persist the `due_for_review` status.
|
|
73
|
+
*
|
|
74
|
+
* Topics already flagged `due_for_review` are skipped (idempotent — they are
|
|
75
|
+
* already surfaced). Ungraduated / `not_started` topics never lapse.
|
|
76
|
+
*
|
|
77
|
+
* Pure (clock injected). Returns NEW standing objects for the overlaid rows; the
|
|
78
|
+
* input array is not mutated.
|
|
79
|
+
*
|
|
80
|
+
* @param standings - In-scope standings (from {@link computeStandings}).
|
|
81
|
+
* @param profile - The learner profile (reads `abilities`/`graduations`).
|
|
82
|
+
* @param now - Reference ISO datetime, injected by the caller (E5).
|
|
83
|
+
* @returns The (possibly overlaid) standings plus the keys newly found lapsed.
|
|
84
|
+
*/
|
|
85
|
+
export declare const detectLapses: (standings: readonly TopicStanding[], profile: Profile, now: string) => {
|
|
86
|
+
standings: TopicStanding[];
|
|
87
|
+
newlyLapsed: AbilityKey[];
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Weakness ordering for suggestions / weakest-pick (lower ⇒ weaker ⇒ surfaced
|
|
91
|
+
* first):
|
|
92
|
+
* 1. `not_started` topics rank ahead of any started topic (they are the most
|
|
93
|
+
* valuable next step and need no telemetry to identify).
|
|
94
|
+
* 2. within each group, lower `ability` ranks first (the weakest area).
|
|
95
|
+
* 3. ties break on `key` for a stable, deterministic total order.
|
|
96
|
+
*
|
|
97
|
+
* @returns A negative/zero/positive comparator result for `Array#sort`.
|
|
98
|
+
*/
|
|
99
|
+
export declare const byWeakness: (a: TopicStanding, b: TopicStanding) => number;
|
|
100
|
+
/**
|
|
101
|
+
* Rank in-scope standings weakest-first (stable, deterministic).
|
|
102
|
+
* A copy is returned; the input is never mutated.
|
|
103
|
+
*/
|
|
104
|
+
export declare const rankByWeakness: (standings: readonly TopicStanding[]) => TopicStanding[];
|
|
105
|
+
/**
|
|
106
|
+
* A short, human reason for why a topic is suggested as a next step.
|
|
107
|
+
* `not_started` topics are framed as "not started yet"; everything else as a
|
|
108
|
+
* relative-weakness nudge.
|
|
109
|
+
*/
|
|
110
|
+
export declare const suggestionReason: (standing: TopicStanding) => string;
|
|
111
|
+
//# sourceMappingURL=standing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"standing.d.ts","sourceRoot":"","sources":["../../../src/tools/us2/standing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,MAAM,EACZ,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE1D;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;IACzB,gCAAgC;IAChC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,kEAAkE;IAClE,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;CAC3B;AAED;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,KAAK,EAAE,OAAO,MAAM,KAAG,OAG1D,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,KAAK,EAAE,SAAS,OAAO,KAAG,aAiB5D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAC3B,QAAQ,SAAS,KAAK,EAAE,EACxB,SAAS,OAAO,EAChB,OAAO,MAAM,KACZ,aAAa,EAGgC,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,YAAY,GACvB,WAAW,SAAS,aAAa,EAAE,EACnC,SAAS,OAAO,EAChB,KAAK,MAAM,KACV;IAAE,SAAS,EAAE,aAAa,EAAE,CAAC;IAAC,WAAW,EAAE,UAAU,EAAE,CAAA;CAezD,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,GAAI,GAAG,aAAa,EAAE,GAAG,aAAa,KAAG,MAM/D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,GACzB,WAAW,SAAS,aAAa,EAAE,KAClC,aAAa,EAAqC,CAAC;AAEtD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,UAAU,aAAa,KAAG,MAKmB,CAAC"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Shared standing/ranking helpers for the US-2 read tools (T025–T027).
|
|
3
|
+
*
|
|
4
|
+
* `get_status`, `get_guidance`, and (indirectly) `list_topics` all need the same
|
|
5
|
+
* derivation: given the loaded catalog topics + the learner profile, compute the
|
|
6
|
+
* per-(topic × class) standing — its {@link AbilityKey}, displayed tier, status,
|
|
7
|
+
* and current ability — and rank topics by weakness so the weakest/stale one can
|
|
8
|
+
* be suggested or auto-selected. Centralizing it here keeps the three tools DRY
|
|
9
|
+
* and guarantees they agree on what "weak", "not_started", and "due_for_review"
|
|
10
|
+
* mean.
|
|
11
|
+
*
|
|
12
|
+
* Pure + telemetry-free (SC-011): every value is derived from the catalog
|
|
13
|
+
* (bundled or fetched) plus the profile's `abilities`/`graduations` — NO
|
|
14
|
+
* observation/offer state is consulted, so the whole pull path works with zero
|
|
15
|
+
* telemetry.
|
|
16
|
+
*
|
|
17
|
+
* Source of truth: specs/001-vibe-hero-mvp/contracts/mcp-tools.md
|
|
18
|
+
* (`get_status` / `get_guidance`), spec.md US-2 / FR-021 / SC-011.
|
|
19
|
+
*/
|
|
20
|
+
import { ASSESSMENT_CONFIG } from "../../config.js";
|
|
21
|
+
import { isDueForReview } from "../../engine/lapse.js";
|
|
22
|
+
import { abilityKey, } from "../../schemas/common.js";
|
|
23
|
+
/**
|
|
24
|
+
* Whether `topic` is in scope for `tool`. A `general` topic is always in scope
|
|
25
|
+
* (it applies to every tool); a tool-scoped topic is in scope only when its tool
|
|
26
|
+
* matches. With no `tool` filter, every topic is in scope.
|
|
27
|
+
*/
|
|
28
|
+
export const topicInScope = (topic, tool) => {
|
|
29
|
+
if (tool === undefined)
|
|
30
|
+
return true;
|
|
31
|
+
return topic.class.kind === "general" || topic.class.tool === tool;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Derive the standing for one topic from the profile (telemetry-free).
|
|
35
|
+
*
|
|
36
|
+
* - `tier`/`status` come from `graduations[key]`: a learner with no graduation
|
|
37
|
+
* entry has never graduated, so tier is `0` and status is `not_started`.
|
|
38
|
+
* Otherwise the displayed tier is `currentTier` and the status mirrors the
|
|
39
|
+
* graduation's `current` / `due_for_review`.
|
|
40
|
+
* - `ability` comes from `abilities[key].value`, defaulting to the cold-start
|
|
41
|
+
* {@link ASSESSMENT_CONFIG.startingAbility} when the learner has no estimate
|
|
42
|
+
* yet (so a never-touched topic reports a sensible baseline, not `0`).
|
|
43
|
+
*/
|
|
44
|
+
export const standingFor = (topic, profile) => {
|
|
45
|
+
const key = abilityKey(topic.class, topic.id);
|
|
46
|
+
const graduation = profile.graduations[key];
|
|
47
|
+
const ability = profile.abilities[key]?.value ?? ASSESSMENT_CONFIG.startingAbility;
|
|
48
|
+
const row = graduation === undefined
|
|
49
|
+
? { key, title: topic.title, tier: 0, status: "not_started", ability }
|
|
50
|
+
: {
|
|
51
|
+
key,
|
|
52
|
+
title: topic.title,
|
|
53
|
+
tier: graduation.currentTier,
|
|
54
|
+
status: graduation.status,
|
|
55
|
+
ability,
|
|
56
|
+
};
|
|
57
|
+
return { key, topic, row };
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Compute every in-scope topic's standing, in catalog order.
|
|
61
|
+
*
|
|
62
|
+
* @param topics - The loaded catalog topics (bundled or fetched).
|
|
63
|
+
* @param profile - The learner profile (only `abilities`/`graduations` are read).
|
|
64
|
+
* @param tool - Optional tool filter; see {@link topicInScope}.
|
|
65
|
+
*/
|
|
66
|
+
export const computeStandings = (topics, profile, tool) => topics
|
|
67
|
+
.filter((topic) => topicInScope(topic, tool))
|
|
68
|
+
.map((topic) => standingFor(topic, profile));
|
|
69
|
+
/**
|
|
70
|
+
* Detect knowledge lapse (FR-009 / OD-003) across a set of standings, given the
|
|
71
|
+
* caller-injected `now` (the engine math is PURE; status.ts reads the clock and
|
|
72
|
+
* passes it in — E5). For each currently-graduated topic that has gone stale and
|
|
73
|
+
* whose decayed effective ability has fallen near/under its lower band
|
|
74
|
+
* ({@link isDueForReview}), the topic is surfaced for review:
|
|
75
|
+
*
|
|
76
|
+
* - its status row is overlaid to `due_for_review` (so `get_status` reports the
|
|
77
|
+
* stale topic correctly even before the profile is rewritten), and
|
|
78
|
+
* - its key is collected as `newlyLapsed` so the caller can enqueue a
|
|
79
|
+
* `ReviewEntry{reason:"lapsed"}` and persist the `due_for_review` status.
|
|
80
|
+
*
|
|
81
|
+
* Topics already flagged `due_for_review` are skipped (idempotent — they are
|
|
82
|
+
* already surfaced). Ungraduated / `not_started` topics never lapse.
|
|
83
|
+
*
|
|
84
|
+
* Pure (clock injected). Returns NEW standing objects for the overlaid rows; the
|
|
85
|
+
* input array is not mutated.
|
|
86
|
+
*
|
|
87
|
+
* @param standings - In-scope standings (from {@link computeStandings}).
|
|
88
|
+
* @param profile - The learner profile (reads `abilities`/`graduations`).
|
|
89
|
+
* @param now - Reference ISO datetime, injected by the caller (E5).
|
|
90
|
+
* @returns The (possibly overlaid) standings plus the keys newly found lapsed.
|
|
91
|
+
*/
|
|
92
|
+
export const detectLapses = (standings, profile, now) => {
|
|
93
|
+
const newlyLapsed = [];
|
|
94
|
+
const overlaid = standings.map((standing) => {
|
|
95
|
+
const graduation = profile.graduations[standing.key];
|
|
96
|
+
const ability = profile.abilities[standing.key];
|
|
97
|
+
if (graduation === undefined || ability === undefined)
|
|
98
|
+
return standing;
|
|
99
|
+
if (!isDueForReview(graduation, ability, now))
|
|
100
|
+
return standing;
|
|
101
|
+
newlyLapsed.push(standing.key);
|
|
102
|
+
return {
|
|
103
|
+
...standing,
|
|
104
|
+
row: { ...standing.row, status: "due_for_review" },
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
return { standings: overlaid, newlyLapsed };
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Weakness ordering for suggestions / weakest-pick (lower ⇒ weaker ⇒ surfaced
|
|
111
|
+
* first):
|
|
112
|
+
* 1. `not_started` topics rank ahead of any started topic (they are the most
|
|
113
|
+
* valuable next step and need no telemetry to identify).
|
|
114
|
+
* 2. within each group, lower `ability` ranks first (the weakest area).
|
|
115
|
+
* 3. ties break on `key` for a stable, deterministic total order.
|
|
116
|
+
*
|
|
117
|
+
* @returns A negative/zero/positive comparator result for `Array#sort`.
|
|
118
|
+
*/
|
|
119
|
+
export const byWeakness = (a, b) => {
|
|
120
|
+
const aNotStarted = a.row.status === "not_started" ? 0 : 1;
|
|
121
|
+
const bNotStarted = b.row.status === "not_started" ? 0 : 1;
|
|
122
|
+
if (aNotStarted !== bNotStarted)
|
|
123
|
+
return aNotStarted - bNotStarted;
|
|
124
|
+
if (a.row.ability !== b.row.ability)
|
|
125
|
+
return a.row.ability - b.row.ability;
|
|
126
|
+
return a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* Rank in-scope standings weakest-first (stable, deterministic).
|
|
130
|
+
* A copy is returned; the input is never mutated.
|
|
131
|
+
*/
|
|
132
|
+
export const rankByWeakness = (standings) => [...standings].sort(byWeakness);
|
|
133
|
+
/**
|
|
134
|
+
* A short, human reason for why a topic is suggested as a next step.
|
|
135
|
+
* `not_started` topics are framed as "not started yet"; everything else as a
|
|
136
|
+
* relative-weakness nudge.
|
|
137
|
+
*/
|
|
138
|
+
export const suggestionReason = (standing) => standing.row.status === "not_started"
|
|
139
|
+
? "Not started yet — a good place to begin."
|
|
140
|
+
: standing.row.status === "due_for_review"
|
|
141
|
+
? "Due for review — knowledge may be going stale."
|
|
142
|
+
: `Weakest area so far (ability ≈ ${Math.round(standing.row.ability)}).`;
|
|
143
|
+
//# sourceMappingURL=standing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"standing.js","sourceRoot":"","sources":["../../../src/tools/us2/standing.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vibe-hero/server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server engine for the vibe-hero adaptive-learning plugin — not used standalone; install the vibe-hero Claude Code plugin, which launches this and ships the skills + hook.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"model-context-protocol",
|
|
8
|
+
"claude-code",
|
|
9
|
+
"codex",
|
|
10
|
+
"learning",
|
|
11
|
+
"quiz",
|
|
12
|
+
"adaptive-learning",
|
|
13
|
+
"agentic-coding",
|
|
14
|
+
"education"
|
|
15
|
+
],
|
|
16
|
+
"author": "Sjors Robroek",
|
|
17
|
+
"license": "Apache-2.0",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/srobroek/vibe-hero.git",
|
|
21
|
+
"directory": "packages/server"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/srobroek/vibe-hero#readme",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/srobroek/vibe-hero/issues"
|
|
26
|
+
},
|
|
27
|
+
"private": false,
|
|
28
|
+
"type": "module",
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"bin": {
|
|
33
|
+
"vibe-hero": "dist/cli/index.js"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
43
|
+
"js-yaml": "^4.1.0",
|
|
44
|
+
"proper-lockfile": "^4.1.2",
|
|
45
|
+
"zod": "^3.24.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/js-yaml": "^4.0.9",
|
|
49
|
+
"@types/node": "^22.0.0",
|
|
50
|
+
"@types/proper-lockfile": "^4.1.4",
|
|
51
|
+
"typescript": "^5.7.0",
|
|
52
|
+
"vitest": "^3.0.0"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsc -p tsconfig.json && pnpm run copy-assets",
|
|
56
|
+
"copy-assets": "node -e \"const{cpSync,existsSync,mkdirSync}=require('node:fs');const path=require('node:path');function cp(s,d){if(existsSync(s)){mkdirSync(d,{recursive:true});cpSync(s,d,{recursive:true,filter:p=>!p.endsWith('.ts')});}}cp('src/catalog/bundled','dist/catalog/bundled');const root=path.resolve('../..');['claude-code','general'].forEach(cls=>{const s=path.join(root,'content',cls);const d=path.join('dist','catalog','bundled',cls);if(existsSync(s)){mkdirSync(d,{recursive:true});cpSync(s,d,{recursive:true});}});\"",
|
|
57
|
+
"test": "vitest run",
|
|
58
|
+
"test:watch": "vitest",
|
|
59
|
+
"start": "node dist/index.js",
|
|
60
|
+
"typecheck": "tsc --noEmit"
|
|
61
|
+
}
|
|
62
|
+
}
|