@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,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file CLI entrypoint for the Stop-hook offer resolver (T037).
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* node dist/cli/getOffer.js get-offer --session <sessionId> --tool <toolId>
|
|
6
|
+
*
|
|
7
|
+
* Prints a single JSON line to stdout and exits 0. Callers treat any non-zero
|
|
8
|
+
* exit or non-JSON stdout as "no offer / suppress silently" — this binary must
|
|
9
|
+
* NEVER produce a non-zero exit that would surface to the user as a hook error.
|
|
10
|
+
*
|
|
11
|
+
* Output shapes (always valid JSON on stdout, then newline):
|
|
12
|
+
*
|
|
13
|
+
* {"offer":{"key":"…","title":"…","prompt":"…"}}
|
|
14
|
+
* {"suppressed":"cadence"} (or "declined" | "offers_off" | "no_candidate")
|
|
15
|
+
* {"suppressed":"no_candidate"} (SETUP_REQUIRED or any unexpected result)
|
|
16
|
+
*
|
|
17
|
+
* This module is intentionally thin: it validates argv, delegates to the
|
|
18
|
+
* existing `get_offer` handler (reusing all offer-engine + cadence logic),
|
|
19
|
+
* and serializes the result. It does NOT re-implement any logic.
|
|
20
|
+
*
|
|
21
|
+
* Guard: if VIBE_HERO_STOP_HOOK_ACTIVE=1 is set we are already inside a Stop
|
|
22
|
+
* hook invocation (the Claude Code host is running the hook again after the
|
|
23
|
+
* hook itself triggered an action). In that case we exit 0 immediately with a
|
|
24
|
+
* suppressed result to prevent infinite loops.
|
|
25
|
+
*
|
|
26
|
+
* Source of truth: specs/001-vibe-hero-mvp/contracts/mcp-tools.md (`get_offer`),
|
|
27
|
+
* hooks/claude-code/stop-offer.sh, research.md (§ Observation & hook correlation).
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Entry point. Called when this module is the process entrypoint (`node dist/cli/getOffer.js`).
|
|
31
|
+
*
|
|
32
|
+
* Resolves an offer for `sessionId` / `tool` by delegating to the existing
|
|
33
|
+
* `get_offer` tool handler, then writes the result as JSON and exits 0.
|
|
34
|
+
* Any error path writes to stderr and falls back to a suppressed result —
|
|
35
|
+
* the hook must NEVER propagate errors back to the user's session.
|
|
36
|
+
*/
|
|
37
|
+
export declare const main: () => Promise<void>;
|
|
38
|
+
//# sourceMappingURL=getOffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getOffer.d.ts","sourceRoot":"","sources":["../../src/cli/getOffer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AA4FH;;;;;;;GAOG;AACH,eAAO,MAAM,IAAI,QAAa,OAAO,CAAC,IAAI,CAwCzC,CAAC"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file CLI entrypoint for the Stop-hook offer resolver (T037).
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* node dist/cli/getOffer.js get-offer --session <sessionId> --tool <toolId>
|
|
6
|
+
*
|
|
7
|
+
* Prints a single JSON line to stdout and exits 0. Callers treat any non-zero
|
|
8
|
+
* exit or non-JSON stdout as "no offer / suppress silently" — this binary must
|
|
9
|
+
* NEVER produce a non-zero exit that would surface to the user as a hook error.
|
|
10
|
+
*
|
|
11
|
+
* Output shapes (always valid JSON on stdout, then newline):
|
|
12
|
+
*
|
|
13
|
+
* {"offer":{"key":"…","title":"…","prompt":"…"}}
|
|
14
|
+
* {"suppressed":"cadence"} (or "declined" | "offers_off" | "no_candidate")
|
|
15
|
+
* {"suppressed":"no_candidate"} (SETUP_REQUIRED or any unexpected result)
|
|
16
|
+
*
|
|
17
|
+
* This module is intentionally thin: it validates argv, delegates to the
|
|
18
|
+
* existing `get_offer` handler (reusing all offer-engine + cadence logic),
|
|
19
|
+
* and serializes the result. It does NOT re-implement any logic.
|
|
20
|
+
*
|
|
21
|
+
* Guard: if VIBE_HERO_STOP_HOOK_ACTIVE=1 is set we are already inside a Stop
|
|
22
|
+
* hook invocation (the Claude Code host is running the hook again after the
|
|
23
|
+
* hook itself triggered an action). In that case we exit 0 immediately with a
|
|
24
|
+
* suppressed result to prevent infinite loops.
|
|
25
|
+
*
|
|
26
|
+
* Source of truth: specs/001-vibe-hero-mvp/contracts/mcp-tools.md (`get_offer`),
|
|
27
|
+
* hooks/claude-code/stop-offer.sh, research.md (§ Observation & hook correlation).
|
|
28
|
+
*/
|
|
29
|
+
import { argv, env, stdout, stderr } from "node:process";
|
|
30
|
+
import { makeGetOfferTool } from "../tools/offers.js";
|
|
31
|
+
import { ToolIdSchema } from "../schemas/common.js";
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Infinite-loop guard
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
/**
|
|
36
|
+
* If the Stop hook itself triggers Claude Code activity that fires the Stop
|
|
37
|
+
* hook again, `stop_hook_active` would be `true` in the outer payload. We
|
|
38
|
+
* communicate that via this env var (set by the shell hook before calling us)
|
|
39
|
+
* so the CLI can exit immediately rather than re-entering the offer path.
|
|
40
|
+
*/
|
|
41
|
+
const isReentrant = () => env["VIBE_HERO_STOP_HOOK_ACTIVE"] === "1";
|
|
42
|
+
/**
|
|
43
|
+
* Parse `argv[2..]` into a {@link CliArgs}. Returns `undefined` (and writes a
|
|
44
|
+
* diagnostic to stderr) when required args are missing or invalid.
|
|
45
|
+
*/
|
|
46
|
+
const parseArgs = (args) => {
|
|
47
|
+
const [subcommand, ...rest] = args;
|
|
48
|
+
if (subcommand !== "get-offer") {
|
|
49
|
+
stderr.write(`vibe-hero cli: unknown subcommand ${JSON.stringify(subcommand ?? "(none)")}; expected "get-offer"\n`);
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
// Simple --key value flag parser (no external deps).
|
|
53
|
+
const flags = {};
|
|
54
|
+
for (let i = 0; i < rest.length - 1; i++) {
|
|
55
|
+
const flag = rest[i];
|
|
56
|
+
const value = rest[i + 1];
|
|
57
|
+
if (flag !== undefined && flag.startsWith("--") && value !== undefined && !value.startsWith("--")) {
|
|
58
|
+
flags[flag.slice(2)] = value;
|
|
59
|
+
i++; // skip consumed value
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const sessionId = flags["session"];
|
|
63
|
+
if (!sessionId) {
|
|
64
|
+
stderr.write("vibe-hero cli: --session <sessionId> is required\n");
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
const rawTool = flags["tool"];
|
|
68
|
+
if (!rawTool) {
|
|
69
|
+
stderr.write("vibe-hero cli: --tool <toolId> is required\n");
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
const toolParse = ToolIdSchema.safeParse(rawTool);
|
|
73
|
+
if (!toolParse.success) {
|
|
74
|
+
stderr.write(`vibe-hero cli: unknown tool ${JSON.stringify(rawTool)}; valid values: claude-code, codex, kiro-cli, kiro-ide\n`);
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
return { subcommand, sessionId, tool: toolParse.data };
|
|
78
|
+
};
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Suppress result helper
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
/** Emit a suppressed result and exit 0 (used on all non-offer paths). */
|
|
83
|
+
const suppress = (reason = "no_candidate") => {
|
|
84
|
+
stdout.write(JSON.stringify({ suppressed: reason }) + "\n");
|
|
85
|
+
// process.exit is called by the caller after this returns; we don't call it
|
|
86
|
+
// here so the function remains unit-testable without killing the test process.
|
|
87
|
+
};
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Main
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
/**
|
|
92
|
+
* Entry point. Called when this module is the process entrypoint (`node dist/cli/getOffer.js`).
|
|
93
|
+
*
|
|
94
|
+
* Resolves an offer for `sessionId` / `tool` by delegating to the existing
|
|
95
|
+
* `get_offer` tool handler, then writes the result as JSON and exits 0.
|
|
96
|
+
* Any error path writes to stderr and falls back to a suppressed result —
|
|
97
|
+
* the hook must NEVER propagate errors back to the user's session.
|
|
98
|
+
*/
|
|
99
|
+
export const main = async () => {
|
|
100
|
+
// Infinite-loop guard (FR-019 / research Stop hook).
|
|
101
|
+
if (isReentrant()) {
|
|
102
|
+
suppress("no_candidate");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const args = parseArgs(argv.slice(2));
|
|
106
|
+
if (args === undefined) {
|
|
107
|
+
suppress("no_candidate");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// Delegate entirely to the real get_offer handler — no logic duplication.
|
|
111
|
+
const tool = makeGetOfferTool();
|
|
112
|
+
let result;
|
|
113
|
+
try {
|
|
114
|
+
result = await tool.handler({ sessionId: args.sessionId, tool: args.tool });
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
stderr.write(`vibe-hero cli: get_offer handler threw unexpectedly: ${String(err)}\n`);
|
|
118
|
+
suppress("no_candidate");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
// The handler may return a SETUP_REQUIRED gate sentinel (profile not
|
|
122
|
+
// configured yet) or a GetOfferResult. In either case we serialize safely.
|
|
123
|
+
if (typeof result === "object" &&
|
|
124
|
+
result !== null &&
|
|
125
|
+
"status" in result &&
|
|
126
|
+
result.status === "SETUP_REQUIRED") {
|
|
127
|
+
// Setup not done yet — suppress silently; the hook must not break the session.
|
|
128
|
+
suppress("no_candidate");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
stdout.write(JSON.stringify(result) + "\n");
|
|
132
|
+
};
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// Entrypoint guard (same pattern as index.ts)
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
import { fileURLToPath } from "node:url";
|
|
137
|
+
const isEntrypoint = () => {
|
|
138
|
+
const entry = argv[1];
|
|
139
|
+
if (entry === undefined)
|
|
140
|
+
return false;
|
|
141
|
+
return fileURLToPath(import.meta.url) === entry;
|
|
142
|
+
};
|
|
143
|
+
if (isEntrypoint()) {
|
|
144
|
+
main().catch((err) => {
|
|
145
|
+
stderr.write(`vibe-hero cli: fatal: ${String(err)}\n`);
|
|
146
|
+
// Exit 0 — never propagate errors to the Stop hook caller.
|
|
147
|
+
process.exitCode = 0;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=getOffer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getOffer.js","sourceRoot":"","sources":["../../src/cli/getOffer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @file vibe-hero `bin` dispatcher (T004) — the published CLI entrypoint.
|
|
4
|
+
*
|
|
5
|
+
* This is the single `bin` target declared in package.json
|
|
6
|
+
* (`bin: { "vibe-hero": "dist/cli/index.js" }`). It is a THIN router over the
|
|
7
|
+
* two existing entry points; it contains NO business logic of its own:
|
|
8
|
+
*
|
|
9
|
+
* - no subcommand, or `mcp` → start the stdio MCP server ({@link serverMain}
|
|
10
|
+
* from `../index.js`). This is the primary
|
|
11
|
+
* invocation (`npx -y @vibe-hero/server`) used by
|
|
12
|
+
* the plugin's `.mcp.json` (FR-002, FR-008).
|
|
13
|
+
* - `get-offer …` → run the OPTIONAL Stop-hook offer utility
|
|
14
|
+
* ({@link getOfferMain} from `./getOffer.js`),
|
|
15
|
+
* passing the remaining flags through. The Claude
|
|
16
|
+
* Code Stop hook is agent-mediated and does NOT
|
|
17
|
+
* call this (FR-011); it is retained only for
|
|
18
|
+
* non-Claude-Code hosts and debugging.
|
|
19
|
+
* - any other subcommand → a usage line to stderr, nonzero exit.
|
|
20
|
+
*
|
|
21
|
+
* Routing keys off `process.argv[2]` (the first user arg). For `get-offer` the
|
|
22
|
+
* remaining args (`--session`, `--tool`, …) are already on `process.argv`, and
|
|
23
|
+
* {@link getOfferMain} reads them via `argv.slice(2)` itself — so the dispatcher
|
|
24
|
+
* does not re-parse or re-pass them.
|
|
25
|
+
*
|
|
26
|
+
* Import safety (no double-run): `../index.js` and `./getOffer.js` each auto-run
|
|
27
|
+
* their own `main` ONLY when THEY are the process entrypoint, via a
|
|
28
|
+
* `fileURLToPath(import.meta.url) === argv[1]` guard. When this dispatcher is the
|
|
29
|
+
* entrypoint, `argv[1]` is `dist/cli/index.js`, which never equals either of
|
|
30
|
+
* their module URLs — so importing their `main` here triggers nothing. This file
|
|
31
|
+
* carries the SAME guard, so importing IT (e.g. from tests) is likewise
|
|
32
|
+
* side-effect-free.
|
|
33
|
+
*
|
|
34
|
+
* Source of truth: specs/002-distribution/contracts/cli-and-plugin.md,
|
|
35
|
+
* spec.md FR-002 / FR-008 / FR-011, quickstart.md V2.
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* Route `process.argv` to the correct existing `main`. Returns the selected
|
|
39
|
+
* `main`'s promise so the caller can await it (and surface a nonzero exit on an
|
|
40
|
+
* unknown subcommand).
|
|
41
|
+
*
|
|
42
|
+
* @param args - The full `process.argv` (the dispatcher reads `args[2]`).
|
|
43
|
+
* @returns A promise that resolves when the routed entrypoint completes.
|
|
44
|
+
*/
|
|
45
|
+
export declare const dispatch: (args: readonly string[]) => Promise<void>;
|
|
46
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAcH;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,GAAU,MAAM,SAAS,MAAM,EAAE,KAAG,OAAO,CAAC,IAAI,CAsBpE,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @file vibe-hero `bin` dispatcher (T004) — the published CLI entrypoint.
|
|
4
|
+
*
|
|
5
|
+
* This is the single `bin` target declared in package.json
|
|
6
|
+
* (`bin: { "vibe-hero": "dist/cli/index.js" }`). It is a THIN router over the
|
|
7
|
+
* two existing entry points; it contains NO business logic of its own:
|
|
8
|
+
*
|
|
9
|
+
* - no subcommand, or `mcp` → start the stdio MCP server ({@link serverMain}
|
|
10
|
+
* from `../index.js`). This is the primary
|
|
11
|
+
* invocation (`npx -y @vibe-hero/server`) used by
|
|
12
|
+
* the plugin's `.mcp.json` (FR-002, FR-008).
|
|
13
|
+
* - `get-offer …` → run the OPTIONAL Stop-hook offer utility
|
|
14
|
+
* ({@link getOfferMain} from `./getOffer.js`),
|
|
15
|
+
* passing the remaining flags through. The Claude
|
|
16
|
+
* Code Stop hook is agent-mediated and does NOT
|
|
17
|
+
* call this (FR-011); it is retained only for
|
|
18
|
+
* non-Claude-Code hosts and debugging.
|
|
19
|
+
* - any other subcommand → a usage line to stderr, nonzero exit.
|
|
20
|
+
*
|
|
21
|
+
* Routing keys off `process.argv[2]` (the first user arg). For `get-offer` the
|
|
22
|
+
* remaining args (`--session`, `--tool`, …) are already on `process.argv`, and
|
|
23
|
+
* {@link getOfferMain} reads them via `argv.slice(2)` itself — so the dispatcher
|
|
24
|
+
* does not re-parse or re-pass them.
|
|
25
|
+
*
|
|
26
|
+
* Import safety (no double-run): `../index.js` and `./getOffer.js` each auto-run
|
|
27
|
+
* their own `main` ONLY when THEY are the process entrypoint, via a
|
|
28
|
+
* `fileURLToPath(import.meta.url) === argv[1]` guard. When this dispatcher is the
|
|
29
|
+
* entrypoint, `argv[1]` is `dist/cli/index.js`, which never equals either of
|
|
30
|
+
* their module URLs — so importing their `main` here triggers nothing. This file
|
|
31
|
+
* carries the SAME guard, so importing IT (e.g. from tests) is likewise
|
|
32
|
+
* side-effect-free.
|
|
33
|
+
*
|
|
34
|
+
* Source of truth: specs/002-distribution/contracts/cli-and-plugin.md,
|
|
35
|
+
* spec.md FR-002 / FR-008 / FR-011, quickstart.md V2.
|
|
36
|
+
*/
|
|
37
|
+
import { fileURLToPath } from "node:url";
|
|
38
|
+
import { argv, stderr } from "node:process";
|
|
39
|
+
import { main as serverMain } from "../index.js";
|
|
40
|
+
import { main as getOfferMain } from "./getOffer.js";
|
|
41
|
+
/** The bin name, used in the usage line. Matches package.json `bin`. */
|
|
42
|
+
const BIN_NAME = "vibe-hero";
|
|
43
|
+
/** One-line usage shown on stderr for an unknown subcommand. */
|
|
44
|
+
const USAGE = `usage: ${BIN_NAME} [mcp | get-offer --session <id> --tool <toolId>]`;
|
|
45
|
+
/**
|
|
46
|
+
* Route `process.argv` to the correct existing `main`. Returns the selected
|
|
47
|
+
* `main`'s promise so the caller can await it (and surface a nonzero exit on an
|
|
48
|
+
* unknown subcommand).
|
|
49
|
+
*
|
|
50
|
+
* @param args - The full `process.argv` (the dispatcher reads `args[2]`).
|
|
51
|
+
* @returns A promise that resolves when the routed entrypoint completes.
|
|
52
|
+
*/
|
|
53
|
+
export const dispatch = async (args) => {
|
|
54
|
+
// args[0] = node, args[1] = this script, args[2] = first user subcommand.
|
|
55
|
+
const subcommand = args[2];
|
|
56
|
+
// Default (no subcommand) or explicit `mcp` → the stdio MCP server.
|
|
57
|
+
if (subcommand === undefined || subcommand === "mcp") {
|
|
58
|
+
await serverMain();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// `get-offer` → the optional offer utility. It reads its own flags from
|
|
62
|
+
// `argv.slice(2)`, so the remaining args pass through untouched.
|
|
63
|
+
if (subcommand === "get-offer") {
|
|
64
|
+
await getOfferMain();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Anything else is an error: usage line to stderr, nonzero exit.
|
|
68
|
+
stderr.write(`${BIN_NAME}: unknown subcommand ${JSON.stringify(subcommand)}\n${USAGE}\n`);
|
|
69
|
+
process.exitCode = 2;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Entrypoint guard: only auto-dispatch when this module is the process
|
|
73
|
+
* entrypoint (`node .../cli/index.js`), not when imported by tests. Mirrors the
|
|
74
|
+
* `import.meta.url === argv[1]` pattern in `../index.js` and `./getOffer.js`.
|
|
75
|
+
*/
|
|
76
|
+
const isEntrypoint = () => {
|
|
77
|
+
const entry = argv[1];
|
|
78
|
+
if (entry === undefined)
|
|
79
|
+
return false;
|
|
80
|
+
return fileURLToPath(import.meta.url) === entry;
|
|
81
|
+
};
|
|
82
|
+
if (isEntrypoint()) {
|
|
83
|
+
dispatch(argv).catch((err) => {
|
|
84
|
+
stderr.write(`${BIN_NAME}: fatal: ${String(err)}\n`);
|
|
85
|
+
process.exitCode = 1;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAE,IAAI,IAAI,UAAU,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,IAAI,IAAI,YAAY,EAAE,MAAM,eAAe,CAAC;AAErD,wEAAwE;AACxE,MAAM,QAAQ,GAAG,WAAW,CAAC;AAE7B,gEAAgE;AAChE,MAAM,KAAK,GAAG,UAAU,QAAQ,mDAAmD,CAAC;AAEpF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,KAAK,EAAE,IAAuB,EAAiB,EAAE;IACvE,0EAA0E;IAC1E,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAE3B,oEAAoE;IACpE,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,KAAK,KAAK,EAAE,CAAC;QACrD,MAAM,UAAU,EAAE,CAAC;QACnB,OAAO;IACT,CAAC;IAED,wEAAwE;IACxE,iEAAiE;IACjE,IAAI,UAAU,KAAK,WAAW,EAAE,CAAC;QAC/B,MAAM,YAAY,EAAE,CAAC;QACrB,OAAO;IACT,CAAC;IAED,iEAAiE;IACjE,MAAM,CAAC,KAAK,CACV,GAAG,QAAQ,wBAAwB,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,KAAK,KAAK,IAAI,CAC5E,CAAC;IACF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,YAAY,GAAG,GAAY,EAAE;IACjC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACtB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACtC,OAAO,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC;AAClD,CAAC,CAAC;AAEF,IAAI,YAAY,EAAE,EAAE,CAAC;IACnB,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;QACpC,MAAM,CAAC,KAAK,CAAC,GAAG,QAAQ,YAAY,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tunable assessment configuration (OD-005, research.md).
|
|
3
|
+
* Pure data — no logic, no imports. All engine modules read from here.
|
|
4
|
+
* Change values here; nothing is hard-coded across the codebase.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ASSESSMENT_CONFIG: {
|
|
7
|
+
readonly scale: 400;
|
|
8
|
+
readonly startingAbility: 300;
|
|
9
|
+
readonly difficultySeeds: {
|
|
10
|
+
readonly easy: 200;
|
|
11
|
+
readonly medium: 300;
|
|
12
|
+
readonly hard: 400;
|
|
13
|
+
};
|
|
14
|
+
readonly kProvisional: 64;
|
|
15
|
+
readonly kSettled: 24;
|
|
16
|
+
readonly settleAfterItems: 15;
|
|
17
|
+
readonly tierCenters: readonly [100, 200, 300, 400, 500];
|
|
18
|
+
readonly tierBoundaries: readonly [150, 250, 350, 450];
|
|
19
|
+
readonly hysteresisMargin: 30;
|
|
20
|
+
readonly dwell: 2;
|
|
21
|
+
readonly decayHalfLifeDays: 60;
|
|
22
|
+
readonly stalenessWindowDays: 30;
|
|
23
|
+
readonly targetOffset: 50;
|
|
24
|
+
readonly selectWindow: 60;
|
|
25
|
+
readonly anchorWindow: 20;
|
|
26
|
+
readonly declineMuteThreshold: 3;
|
|
27
|
+
readonly backoffBaseHours: 24;
|
|
28
|
+
readonly backoffFactor: 2;
|
|
29
|
+
readonly freeFormPassThreshold: 0.6;
|
|
30
|
+
readonly defaultQuizLength: 4;
|
|
31
|
+
};
|
|
32
|
+
/** Inferred type of the assessment configuration object. */
|
|
33
|
+
export type AssessmentConfig = typeof ASSESSMENT_CONFIG;
|
|
34
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;CAkEpB,CAAC;AAEX,4DAA4D;AAC5D,MAAM,MAAM,gBAAgB,GAAG,OAAO,iBAAiB,CAAC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tunable assessment configuration (OD-005, research.md).
|
|
3
|
+
* Pure data — no logic, no imports. All engine modules read from here.
|
|
4
|
+
* Change values here; nothing is hard-coded across the codebase.
|
|
5
|
+
*/
|
|
6
|
+
export const ASSESSMENT_CONFIG = {
|
|
7
|
+
// --- Elo logistic scale -------------------------------------------------
|
|
8
|
+
// S=400 matches standard chess Elo: a 400-point gap → ~91% win expectation.
|
|
9
|
+
scale: 400,
|
|
10
|
+
// Starting ability for a fresh (topic × tool) estimate — mid-scale.
|
|
11
|
+
startingAbility: 300,
|
|
12
|
+
// Item difficulty seeds by tag; authored at content creation, NEVER updated
|
|
13
|
+
// at runtime (two-way Elo would corrupt the scale with a single learner).
|
|
14
|
+
difficultySeeds: {
|
|
15
|
+
easy: 200,
|
|
16
|
+
medium: 300,
|
|
17
|
+
hard: 400,
|
|
18
|
+
},
|
|
19
|
+
// --- K-factor (learning rate) -------------------------------------------
|
|
20
|
+
// High K while provisional to converge quickly from a cold start.
|
|
21
|
+
kProvisional: 64,
|
|
22
|
+
// Smaller K once settled — reduces noise from individual questions.
|
|
23
|
+
kSettled: 24,
|
|
24
|
+
// Number of graded items after which the estimate is considered settled.
|
|
25
|
+
settleAfterItems: 15,
|
|
26
|
+
// --- Tier centers and boundaries ----------------------------------------
|
|
27
|
+
// Five tiers spread across the 0–600 ability range.
|
|
28
|
+
tierCenters: [100, 200, 300, 400, 500],
|
|
29
|
+
// Boundaries sit halfway between adjacent centers.
|
|
30
|
+
tierBoundaries: [150, 250, 350, 450],
|
|
31
|
+
// --- Hysteresis + dwell (anti-flip-flop, FR-008 / SC-014) ---------------
|
|
32
|
+
// Promote at boundary+30; demote/review only below boundary-30.
|
|
33
|
+
hysteresisMargin: 30,
|
|
34
|
+
// Crossing must hold for this many consecutive graded items before acting.
|
|
35
|
+
dwell: 2,
|
|
36
|
+
// --- Spaced-review / lapse model (OD-003) --------------------------------
|
|
37
|
+
// Exponential ability-decay half-life in days (tier-tunable; e.g. 90 for
|
|
38
|
+
// tier 500 where knowledge is harder-won and decays more slowly).
|
|
39
|
+
decayHalfLifeDays: 60,
|
|
40
|
+
// Topic is due for review when days_since_last >= this threshold.
|
|
41
|
+
stalenessWindowDays: 30,
|
|
42
|
+
// --- Item selection (OD-005 table) --------------------------------------
|
|
43
|
+
// Target difficulty = min(θ + targetOffset, nextBoundary + hysteresisMargin).
|
|
44
|
+
targetOffset: 50,
|
|
45
|
+
// ± window around the target difficulty in which items are eligible.
|
|
46
|
+
selectWindow: 60,
|
|
47
|
+
// One anchor item must fall within ±anchorWindow of the current ability θ.
|
|
48
|
+
anchorWindow: 20,
|
|
49
|
+
// --- Offer / decline suppression (FR-020b) ------------------------------
|
|
50
|
+
// After this many consecutive declines across sessions, mute offers globally.
|
|
51
|
+
declineMuteThreshold: 3,
|
|
52
|
+
// Cross-session backoff base delay (hours) after the first decline.
|
|
53
|
+
backoffBaseHours: 24,
|
|
54
|
+
// Exponential factor applied per additional consecutive decline.
|
|
55
|
+
backoffFactor: 2,
|
|
56
|
+
// --- Free-form judging (OD-002) -----------------------------------------
|
|
57
|
+
// Minimum fraction of criteria that must be met for a free-form pass.
|
|
58
|
+
freeFormPassThreshold: 0.6,
|
|
59
|
+
// --- Quiz length (FR-022) -----------------------------------------------
|
|
60
|
+
// Default number of items per quiz session; configurable 3–5 by the user.
|
|
61
|
+
defaultQuizLength: 4,
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,2EAA2E;IAC3E,4EAA4E;IAC5E,KAAK,EAAE,GAAG;IAEV,oEAAoE;IACpE,eAAe,EAAE,GAAG;IAEpB,4EAA4E;IAC5E,0EAA0E;IAC1E,eAAe,EAAE;QACf,IAAI,EAAE,GAAG;QACT,MAAM,EAAE,GAAG;QACX,IAAI,EAAE,GAAG;KACV;IAED,2EAA2E;IAC3E,kEAAkE;IAClE,YAAY,EAAE,EAAE;IAChB,oEAAoE;IACpE,QAAQ,EAAE,EAAE;IACZ,yEAAyE;IACzE,gBAAgB,EAAE,EAAE;IAEpB,2EAA2E;IAC3E,oDAAoD;IACpD,WAAW,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAU;IAC/C,mDAAmD;IACnD,cAAc,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAU;IAE7C,2EAA2E;IAC3E,gEAAgE;IAChE,gBAAgB,EAAE,EAAE;IACpB,2EAA2E;IAC3E,KAAK,EAAE,CAAC;IAER,4EAA4E;IAC5E,yEAAyE;IACzE,kEAAkE;IAClE,iBAAiB,EAAE,EAAE;IACrB,kEAAkE;IAClE,mBAAmB,EAAE,EAAE;IAEvB,2EAA2E;IAC3E,8EAA8E;IAC9E,YAAY,EAAE,EAAE;IAChB,qEAAqE;IACrE,YAAY,EAAE,EAAE;IAChB,2EAA2E;IAC3E,YAAY,EAAE,EAAE;IAEhB,2EAA2E;IAC3E,8EAA8E;IAC9E,oBAAoB,EAAE,CAAC;IACvB,oEAAoE;IACpE,gBAAgB,EAAE,EAAE;IACpB,iEAAiE;IACjE,aAAa,EAAE,CAAC;IAEhB,2EAA2E;IAC3E,sEAAsE;IACtE,qBAAqB,EAAE,GAAG;IAE1B,2EAA2E;IAC3E,0EAA0E;IAC1E,iBAAiB,EAAE,CAAC;CACZ,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file PURE Elo ability-estimation engine (T010, OD-005 / research.md).
|
|
3
|
+
*
|
|
4
|
+
* Single-learner Elo: a learner's continuous `ability` (θ) is updated online
|
|
5
|
+
* against FIXED, authored item difficulties (d). Item difficulty is INPUT DATA
|
|
6
|
+
* and is never mutated here — only the learner's ability moves (invariant E3).
|
|
7
|
+
*
|
|
8
|
+
* This module is IO-FREE and time-free (invariant E5): it never reads the
|
|
9
|
+
* clock, filesystem, or network. Any time-dependent behaviour (decay,
|
|
10
|
+
* staleness) lives elsewhere and passes time in explicitly.
|
|
11
|
+
*
|
|
12
|
+
* Formulas (OD-005):
|
|
13
|
+
* E = 1 / (1 + 10^((d − θ) / S)) // expected score
|
|
14
|
+
* K = provisional (64) while itemsSeen < settleAfterItems (15), else 24
|
|
15
|
+
* θ' = θ + K · (score − E) // score ∈ [0,1] partial credit
|
|
16
|
+
*
|
|
17
|
+
* Source of truth: specs/001-vibe-hero-mvp/research.md (OD-005);
|
|
18
|
+
* constants in ../config.ts (ASSESSMENT_CONFIG).
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Expected score (win probability) of a learner with ability `ability` (θ)
|
|
22
|
+
* facing an item of difficulty `itemDifficulty` (d), under the logistic Elo
|
|
23
|
+
* model: `E = 1 / (1 + 10^((d − θ) / scale))`.
|
|
24
|
+
*
|
|
25
|
+
* Monotonically increasing in `(θ − d)`: a higher relative ability yields a
|
|
26
|
+
* higher expected score. When `θ === d` the expectation is exactly `0.5`.
|
|
27
|
+
*
|
|
28
|
+
* @param ability - The learner's current ability estimate (θ).
|
|
29
|
+
* @param itemDifficulty - The item's FIXED authored difficulty (d). Read-only.
|
|
30
|
+
* @param scale - Logistic scale `S` (default {@link ASSESSMENT_CONFIG.scale}).
|
|
31
|
+
* @returns The expected score in the open interval (0, 1).
|
|
32
|
+
*/
|
|
33
|
+
export declare const expectedScore: (ability: number, itemDifficulty: number, scale?: number) => number;
|
|
34
|
+
/**
|
|
35
|
+
* The learning-rate `K` for the next update. High while the estimate is
|
|
36
|
+
* provisional (cold start) so it converges quickly, then smaller once settled
|
|
37
|
+
* to damp single-question noise.
|
|
38
|
+
*
|
|
39
|
+
* @param itemsSeen - Count of graded items already incorporated for this
|
|
40
|
+
* (topic × tool) estimate.
|
|
41
|
+
* @returns {@link ASSESSMENT_CONFIG.kProvisional} when
|
|
42
|
+
* `itemsSeen < ASSESSMENT_CONFIG.settleAfterItems`, otherwise
|
|
43
|
+
* {@link ASSESSMENT_CONFIG.kSettled}.
|
|
44
|
+
*/
|
|
45
|
+
export declare const kFactor: (itemsSeen: number) => number;
|
|
46
|
+
/** The result of an ability update: the new ability and incremented count. */
|
|
47
|
+
export interface AbilityUpdate {
|
|
48
|
+
/** The updated ability estimate θ'. */
|
|
49
|
+
readonly value: number;
|
|
50
|
+
/** `itemsSeen` after incorporating this graded item (input + 1). */
|
|
51
|
+
readonly itemsSeen: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Apply one graded item to a learner's ability estimate (PURE).
|
|
55
|
+
*
|
|
56
|
+
* Computes `θ' = θ + K · (score − E)` where `E = expectedScore(θ, d)` and
|
|
57
|
+
* `K = kFactor(itemsSeen)`, then increments `itemsSeen`. The `score` is partial
|
|
58
|
+
* credit in `[0, 1]` (e.g. 1 = fully correct, 0 = fully wrong, intermediate for
|
|
59
|
+
* a partially-met free-form rubric).
|
|
60
|
+
*
|
|
61
|
+
* The fixed-difficulty invariant (E3) holds: `itemDifficulty` is read only and
|
|
62
|
+
* never returned or mutated — this function moves ONLY the learner's ability.
|
|
63
|
+
*
|
|
64
|
+
* A correct answer (`score > E`) raises θ; a wrong one (`score < E`) lowers it.
|
|
65
|
+
* Because the update is proportional to `(score − E)`, a correct answer on a
|
|
66
|
+
* HARDER item (lower E) raises θ more than the same correct answer on an EASIER
|
|
67
|
+
* item (higher E).
|
|
68
|
+
*
|
|
69
|
+
* @param ability - The learner's current ability estimate (θ).
|
|
70
|
+
* @param itemsSeen - Graded items already incorporated (≥ 0).
|
|
71
|
+
* @param itemDifficulty - The item's FIXED authored difficulty (d). Read-only.
|
|
72
|
+
* @param score - Partial-credit outcome in `[0, 1]`.
|
|
73
|
+
* @returns The new ability and `itemsSeen + 1`.
|
|
74
|
+
*/
|
|
75
|
+
export declare const updateAbility: (ability: number, itemsSeen: number, itemDifficulty: number, score: number) => AbilityUpdate;
|
|
76
|
+
//# sourceMappingURL=elo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elo.d.ts","sourceRoot":"","sources":["../../src/engine/elo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa,GACxB,SAAS,MAAM,EACf,gBAAgB,MAAM,EACtB,QAAO,MAAgC,KACtC,MAA8D,CAAC;AAElE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,OAAO,GAAI,WAAW,MAAM,KAAG,MAGZ,CAAC;AAEjC,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,uCAAuC;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,aAAa,GACxB,SAAS,MAAM,EACf,WAAW,MAAM,EACjB,gBAAgB,MAAM,EACtB,OAAO,MAAM,KACZ,aAOF,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file PURE Elo ability-estimation engine (T010, OD-005 / research.md).
|
|
3
|
+
*
|
|
4
|
+
* Single-learner Elo: a learner's continuous `ability` (θ) is updated online
|
|
5
|
+
* against FIXED, authored item difficulties (d). Item difficulty is INPUT DATA
|
|
6
|
+
* and is never mutated here — only the learner's ability moves (invariant E3).
|
|
7
|
+
*
|
|
8
|
+
* This module is IO-FREE and time-free (invariant E5): it never reads the
|
|
9
|
+
* clock, filesystem, or network. Any time-dependent behaviour (decay,
|
|
10
|
+
* staleness) lives elsewhere and passes time in explicitly.
|
|
11
|
+
*
|
|
12
|
+
* Formulas (OD-005):
|
|
13
|
+
* E = 1 / (1 + 10^((d − θ) / S)) // expected score
|
|
14
|
+
* K = provisional (64) while itemsSeen < settleAfterItems (15), else 24
|
|
15
|
+
* θ' = θ + K · (score − E) // score ∈ [0,1] partial credit
|
|
16
|
+
*
|
|
17
|
+
* Source of truth: specs/001-vibe-hero-mvp/research.md (OD-005);
|
|
18
|
+
* constants in ../config.ts (ASSESSMENT_CONFIG).
|
|
19
|
+
*/
|
|
20
|
+
import { ASSESSMENT_CONFIG } from "../config.js";
|
|
21
|
+
/**
|
|
22
|
+
* Expected score (win probability) of a learner with ability `ability` (θ)
|
|
23
|
+
* facing an item of difficulty `itemDifficulty` (d), under the logistic Elo
|
|
24
|
+
* model: `E = 1 / (1 + 10^((d − θ) / scale))`.
|
|
25
|
+
*
|
|
26
|
+
* Monotonically increasing in `(θ − d)`: a higher relative ability yields a
|
|
27
|
+
* higher expected score. When `θ === d` the expectation is exactly `0.5`.
|
|
28
|
+
*
|
|
29
|
+
* @param ability - The learner's current ability estimate (θ).
|
|
30
|
+
* @param itemDifficulty - The item's FIXED authored difficulty (d). Read-only.
|
|
31
|
+
* @param scale - Logistic scale `S` (default {@link ASSESSMENT_CONFIG.scale}).
|
|
32
|
+
* @returns The expected score in the open interval (0, 1).
|
|
33
|
+
*/
|
|
34
|
+
export const expectedScore = (ability, itemDifficulty, scale = ASSESSMENT_CONFIG.scale) => 1 / (1 + 10 ** ((itemDifficulty - ability) / scale));
|
|
35
|
+
/**
|
|
36
|
+
* The learning-rate `K` for the next update. High while the estimate is
|
|
37
|
+
* provisional (cold start) so it converges quickly, then smaller once settled
|
|
38
|
+
* to damp single-question noise.
|
|
39
|
+
*
|
|
40
|
+
* @param itemsSeen - Count of graded items already incorporated for this
|
|
41
|
+
* (topic × tool) estimate.
|
|
42
|
+
* @returns {@link ASSESSMENT_CONFIG.kProvisional} when
|
|
43
|
+
* `itemsSeen < ASSESSMENT_CONFIG.settleAfterItems`, otherwise
|
|
44
|
+
* {@link ASSESSMENT_CONFIG.kSettled}.
|
|
45
|
+
*/
|
|
46
|
+
export const kFactor = (itemsSeen) => itemsSeen < ASSESSMENT_CONFIG.settleAfterItems
|
|
47
|
+
? ASSESSMENT_CONFIG.kProvisional
|
|
48
|
+
: ASSESSMENT_CONFIG.kSettled;
|
|
49
|
+
/**
|
|
50
|
+
* Apply one graded item to a learner's ability estimate (PURE).
|
|
51
|
+
*
|
|
52
|
+
* Computes `θ' = θ + K · (score − E)` where `E = expectedScore(θ, d)` and
|
|
53
|
+
* `K = kFactor(itemsSeen)`, then increments `itemsSeen`. The `score` is partial
|
|
54
|
+
* credit in `[0, 1]` (e.g. 1 = fully correct, 0 = fully wrong, intermediate for
|
|
55
|
+
* a partially-met free-form rubric).
|
|
56
|
+
*
|
|
57
|
+
* The fixed-difficulty invariant (E3) holds: `itemDifficulty` is read only and
|
|
58
|
+
* never returned or mutated — this function moves ONLY the learner's ability.
|
|
59
|
+
*
|
|
60
|
+
* A correct answer (`score > E`) raises θ; a wrong one (`score < E`) lowers it.
|
|
61
|
+
* Because the update is proportional to `(score − E)`, a correct answer on a
|
|
62
|
+
* HARDER item (lower E) raises θ more than the same correct answer on an EASIER
|
|
63
|
+
* item (higher E).
|
|
64
|
+
*
|
|
65
|
+
* @param ability - The learner's current ability estimate (θ).
|
|
66
|
+
* @param itemsSeen - Graded items already incorporated (≥ 0).
|
|
67
|
+
* @param itemDifficulty - The item's FIXED authored difficulty (d). Read-only.
|
|
68
|
+
* @param score - Partial-credit outcome in `[0, 1]`.
|
|
69
|
+
* @returns The new ability and `itemsSeen + 1`.
|
|
70
|
+
*/
|
|
71
|
+
export const updateAbility = (ability, itemsSeen, itemDifficulty, score) => {
|
|
72
|
+
const expected = expectedScore(ability, itemDifficulty);
|
|
73
|
+
const k = kFactor(itemsSeen);
|
|
74
|
+
return {
|
|
75
|
+
value: ability + k * (score - expected),
|
|
76
|
+
itemsSeen: itemsSeen + 1,
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=elo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elo.js","sourceRoot":"","sources":["../../src/engine/elo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,OAAe,EACf,cAAsB,EACtB,QAAgB,iBAAiB,CAAC,KAAK,EAC/B,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,cAAc,GAAG,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;AAElE;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,SAAiB,EAAU,EAAE,CACnD,SAAS,GAAG,iBAAiB,CAAC,gBAAgB;IAC5C,CAAC,CAAC,iBAAiB,CAAC,YAAY;IAChC,CAAC,CAAC,iBAAiB,CAAC,QAAQ,CAAC;AAUjC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,OAAe,EACf,SAAiB,EACjB,cAAsB,EACtB,KAAa,EACE,EAAE;IACjB,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IACxD,MAAM,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAC7B,OAAO;QACL,KAAK,EAAE,OAAO,GAAG,CAAC,GAAG,CAAC,KAAK,GAAG,QAAQ,CAAC;QACvC,SAAS,EAAE,SAAS,GAAG,CAAC;KACzB,CAAC;AACJ,CAAC,CAAC"}
|