@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.
Files changed (150) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +151 -0
  3. package/dist/catalog/bundled/claude-code/.gitkeep +0 -0
  4. package/dist/catalog/bundled/claude-code/context-management.yaml +302 -0
  5. package/dist/catalog/bundled/claude-code/planning.yaml +313 -0
  6. package/dist/catalog/bundled/claude-code/subagents.yaml +357 -0
  7. package/dist/catalog/bundled/general/.gitkeep +0 -0
  8. package/dist/catalog/bundled/general/_placeholder.yaml +39 -0
  9. package/dist/catalog/bundled/general/task-decomposition.yaml +390 -0
  10. package/dist/catalog/bundled/index.d.ts +39 -0
  11. package/dist/catalog/bundled/index.d.ts.map +1 -0
  12. package/dist/catalog/bundled/index.js +41 -0
  13. package/dist/catalog/bundled/index.js.map +1 -0
  14. package/dist/catalog/fetcher.d.ts +201 -0
  15. package/dist/catalog/fetcher.d.ts.map +1 -0
  16. package/dist/catalog/fetcher.js +452 -0
  17. package/dist/catalog/fetcher.js.map +1 -0
  18. package/dist/catalog/loader.d.ts +165 -0
  19. package/dist/catalog/loader.d.ts.map +1 -0
  20. package/dist/catalog/loader.js +241 -0
  21. package/dist/catalog/loader.js.map +1 -0
  22. package/dist/catalog/resolve.d.ts +85 -0
  23. package/dist/catalog/resolve.d.ts.map +1 -0
  24. package/dist/catalog/resolve.js +103 -0
  25. package/dist/catalog/resolve.js.map +1 -0
  26. package/dist/cli/getOffer.d.ts +38 -0
  27. package/dist/cli/getOffer.d.ts.map +1 -0
  28. package/dist/cli/getOffer.js +150 -0
  29. package/dist/cli/getOffer.js.map +1 -0
  30. package/dist/cli/index.d.ts +46 -0
  31. package/dist/cli/index.d.ts.map +1 -0
  32. package/dist/cli/index.js +88 -0
  33. package/dist/cli/index.js.map +1 -0
  34. package/dist/config.d.ts +34 -0
  35. package/dist/config.d.ts.map +1 -0
  36. package/dist/config.js +63 -0
  37. package/dist/config.js.map +1 -0
  38. package/dist/engine/elo.d.ts +76 -0
  39. package/dist/engine/elo.d.ts.map +1 -0
  40. package/dist/engine/elo.js +79 -0
  41. package/dist/engine/elo.js.map +1 -0
  42. package/dist/engine/graduation.d.ts +108 -0
  43. package/dist/engine/graduation.d.ts.map +1 -0
  44. package/dist/engine/graduation.js +161 -0
  45. package/dist/engine/graduation.js.map +1 -0
  46. package/dist/engine/lapse.d.ts +80 -0
  47. package/dist/engine/lapse.d.ts.map +1 -0
  48. package/dist/engine/lapse.js +125 -0
  49. package/dist/engine/lapse.js.map +1 -0
  50. package/dist/engine/selection.d.ts +84 -0
  51. package/dist/engine/selection.d.ts.map +1 -0
  52. package/dist/engine/selection.js +119 -0
  53. package/dist/engine/selection.js.map +1 -0
  54. package/dist/grading/deterministic.d.ts +102 -0
  55. package/dist/grading/deterministic.d.ts.map +1 -0
  56. package/dist/grading/deterministic.js +118 -0
  57. package/dist/grading/deterministic.js.map +1 -0
  58. package/dist/grading/freeform.d.ts +64 -0
  59. package/dist/grading/freeform.d.ts.map +1 -0
  60. package/dist/grading/freeform.js +85 -0
  61. package/dist/grading/freeform.js.map +1 -0
  62. package/dist/index.d.ts +52 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +91 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/observation/hookEvents.d.ts +113 -0
  67. package/dist/observation/hookEvents.d.ts.map +1 -0
  68. package/dist/observation/hookEvents.js +170 -0
  69. package/dist/observation/hookEvents.js.map +1 -0
  70. package/dist/observation/offers.d.ts +215 -0
  71. package/dist/observation/offers.d.ts.map +1 -0
  72. package/dist/observation/offers.js +327 -0
  73. package/dist/observation/offers.js.map +1 -0
  74. package/dist/observation/source.d.ts +133 -0
  75. package/dist/observation/source.d.ts.map +1 -0
  76. package/dist/observation/source.js +105 -0
  77. package/dist/observation/source.js.map +1 -0
  78. package/dist/profile/migrate.d.ts +122 -0
  79. package/dist/profile/migrate.d.ts.map +1 -0
  80. package/dist/profile/migrate.js +147 -0
  81. package/dist/profile/migrate.js.map +1 -0
  82. package/dist/profile/store.d.ts +84 -0
  83. package/dist/profile/store.d.ts.map +1 -0
  84. package/dist/profile/store.js +267 -0
  85. package/dist/profile/store.js.map +1 -0
  86. package/dist/schemas/common.d.ts +95 -0
  87. package/dist/schemas/common.d.ts.map +1 -0
  88. package/dist/schemas/common.js +106 -0
  89. package/dist/schemas/common.js.map +1 -0
  90. package/dist/schemas/content.d.ts +828 -0
  91. package/dist/schemas/content.d.ts.map +1 -0
  92. package/dist/schemas/content.js +219 -0
  93. package/dist/schemas/content.js.map +1 -0
  94. package/dist/schemas/profile.d.ts +599 -0
  95. package/dist/schemas/profile.d.ts.map +1 -0
  96. package/dist/schemas/profile.js +177 -0
  97. package/dist/schemas/profile.js.map +1 -0
  98. package/dist/schemas/tools.d.ts +1581 -0
  99. package/dist/schemas/tools.d.ts.map +1 -0
  100. package/dist/schemas/tools.js +286 -0
  101. package/dist/schemas/tools.js.map +1 -0
  102. package/dist/tools/config.d.ts +51 -0
  103. package/dist/tools/config.d.ts.map +1 -0
  104. package/dist/tools/config.js +104 -0
  105. package/dist/tools/config.js.map +1 -0
  106. package/dist/tools/gate.d.ts +50 -0
  107. package/dist/tools/gate.d.ts.map +1 -0
  108. package/dist/tools/gate.js +67 -0
  109. package/dist/tools/gate.js.map +1 -0
  110. package/dist/tools/guidance.d.ts +36 -0
  111. package/dist/tools/guidance.d.ts.map +1 -0
  112. package/dist/tools/guidance.js +117 -0
  113. package/dist/tools/guidance.js.map +1 -0
  114. package/dist/tools/listTopics.d.ts +55 -0
  115. package/dist/tools/listTopics.d.ts.map +1 -0
  116. package/dist/tools/listTopics.js +78 -0
  117. package/dist/tools/listTopics.js.map +1 -0
  118. package/dist/tools/offers.d.ts +60 -0
  119. package/dist/tools/offers.d.ts.map +1 -0
  120. package/dist/tools/offers.js +152 -0
  121. package/dist/tools/offers.js.map +1 -0
  122. package/dist/tools/placeholders.d.ts +27 -0
  123. package/dist/tools/placeholders.d.ts.map +1 -0
  124. package/dist/tools/placeholders.js +49 -0
  125. package/dist/tools/placeholders.js.map +1 -0
  126. package/dist/tools/recordObservation.d.ts +52 -0
  127. package/dist/tools/recordObservation.d.ts.map +1 -0
  128. package/dist/tools/recordObservation.js +87 -0
  129. package/dist/tools/recordObservation.js.map +1 -0
  130. package/dist/tools/startQuiz.d.ts +82 -0
  131. package/dist/tools/startQuiz.d.ts.map +1 -0
  132. package/dist/tools/startQuiz.js +180 -0
  133. package/dist/tools/startQuiz.js.map +1 -0
  134. package/dist/tools/status.d.ts +59 -0
  135. package/dist/tools/status.d.ts.map +1 -0
  136. package/dist/tools/status.js +133 -0
  137. package/dist/tools/status.js.map +1 -0
  138. package/dist/tools/submitAnswer.d.ts +156 -0
  139. package/dist/tools/submitAnswer.d.ts.map +1 -0
  140. package/dist/tools/submitAnswer.js +402 -0
  141. package/dist/tools/submitAnswer.js.map +1 -0
  142. package/dist/tools/types.d.ts +82 -0
  143. package/dist/tools/types.d.ts.map +1 -0
  144. package/dist/tools/types.js +48 -0
  145. package/dist/tools/types.js.map +1 -0
  146. package/dist/tools/us2/standing.d.ts +111 -0
  147. package/dist/tools/us2/standing.d.ts.map +1 -0
  148. package/dist/tools/us2/standing.js +143 -0
  149. package/dist/tools/us2/standing.js.map +1 -0
  150. package/package.json +62 -0
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @file Real `get_guidance` tool module (T027, US-2).
3
+ *
4
+ * Returns teaching guidance plus a concrete next step for a topic. Given a `key`
5
+ * it guides on that exact `(topic × class)`; with NO `key` it auto-selects the
6
+ * learner's weakest/stale in-scope topic (the same weakness ranking
7
+ * `get_status` uses for `suggestions`) so "what should I learn next?" resolves
8
+ * without the user naming a topic.
9
+ *
10
+ * Guidance text is pulled from the topic's authored content: the `guidance` of
11
+ * an item at — or, failing that, nearest above — the learner's current tier,
12
+ * falling back to the topic `summary` when the topic has no items. The next step
13
+ * is a `quiz` when the topic has gradeable items (the practice path, FR-021),
14
+ * else a `read` nudge toward the summary.
15
+ *
16
+ * Telemetry-free (SC-011): everything is derived from the bundled catalog + the
17
+ * profile's `abilities`/`graduations`; no observation/offer state is read.
18
+ * Gated (FR-032) — `index.ts` returns SETUP_REQUIRED before the handler runs
19
+ * when unconfigured, so `config.toolsLearning` is available for the default tool.
20
+ *
21
+ * Exposed as a `dirOverride`-closing factory mirroring `config.ts`.
22
+ *
23
+ * Source of truth: specs/001-vibe-hero-mvp/contracts/mcp-tools.md
24
+ * (`get_guidance`), spec.md US-2 / FR-021 / SC-011.
25
+ */
26
+ import { loadBundledCatalog } from "../catalog/bundled/index.js";
27
+ import { loadProfile } from "../profile/store.js";
28
+ import { abilityKey, parseAbilityKey, } from "../schemas/common.js";
29
+ import { GetGuidanceInputSchema, } from "../schemas/tools.js";
30
+ import { defineTool } from "./types.js";
31
+ import { computeStandings, rankByWeakness, standingFor, } from "./us2/standing.js";
32
+ /** Resolve the default tool (explicit → first learning → claude-code). */
33
+ const resolveTool = (requested, toolsLearning) => requested ?? toolsLearning[0] ?? "claude-code";
34
+ /** Find the catalog topic whose `(class, id)` serializes to `key`. */
35
+ const findTopicByKey = (topics, key) => topics.find((topic) => abilityKey(topic.class, topic.id) === key);
36
+ /**
37
+ * Pick the authored item whose `guidance` best fits `currentTier`: prefer an
38
+ * item AT the current tier (or tier 100 when the learner has not graduated, so
39
+ * tier is 0), else the nearest item ABOVE it, else any item. Returns `undefined`
40
+ * only when the topic has no items at all.
41
+ */
42
+ const guidanceItemFor = (topic, currentTier) => {
43
+ if (topic.items.length === 0)
44
+ return undefined;
45
+ const floor = currentTier === 0 ? 100 : currentTier;
46
+ const atTier = topic.items.find((item) => item.tier === floor);
47
+ if (atTier !== undefined)
48
+ return atTier;
49
+ const above = [...topic.items]
50
+ .filter((item) => item.tier >= floor)
51
+ .sort((a, b) => a.tier - b.tier)[0];
52
+ if (above !== undefined)
53
+ return above;
54
+ // Below the floor (learner graduated past authored content): take the highest.
55
+ return [...topic.items].sort((a, b) => b.tier - a.tier)[0];
56
+ };
57
+ /** Build the contract result for one resolved topic standing. */
58
+ const guidanceResultFor = (standing) => {
59
+ const { topic, key, row } = standing;
60
+ const item = guidanceItemFor(topic, row.tier);
61
+ const guidance = item?.guidance ?? topic.summary;
62
+ const nextStep = topic.items.length > 0
63
+ ? {
64
+ action: "quiz",
65
+ detail: `Try a short quiz on "${topic.title}" to ${row.status === "not_started" ? "establish a baseline" : "reinforce and advance"}.`,
66
+ }
67
+ : {
68
+ action: "read",
69
+ detail: `Review "${topic.title}": ${topic.summary}`,
70
+ };
71
+ return { key, title: topic.title, currentTier: row.tier, guidance, nextStep };
72
+ };
73
+ /**
74
+ * Resolve the standing `get_guidance` should report on. With a `key`, guide on
75
+ * that exact topic (scoped via its own class). With no `key`, auto-select the
76
+ * weakest/stale in-scope topic for the resolved tool.
77
+ *
78
+ * @throws {Error} if a supplied `key` matches no catalog topic.
79
+ */
80
+ const resolveStanding = (input, topics, profile) => {
81
+ if (input.key !== undefined) {
82
+ // Validate the key shape (throws on malformed) before lookup.
83
+ parseAbilityKey(input.key);
84
+ const topic = findTopicByKey(topics, input.key);
85
+ if (topic === undefined) {
86
+ throw new Error(`get_guidance: no catalog topic matches key ${JSON.stringify(input.key)}`);
87
+ }
88
+ return standingFor(topic, profile);
89
+ }
90
+ const tool = resolveTool(input.tool, profile.config?.toolsLearning ?? []);
91
+ const ranked = rankByWeakness(computeStandings(topics, profile, tool));
92
+ const weakest = ranked[0];
93
+ if (weakest === undefined) {
94
+ throw new Error(`get_guidance: no topics available for tool ${JSON.stringify(tool)}`);
95
+ }
96
+ return weakest;
97
+ };
98
+ /**
99
+ * Build the `get_guidance` tool module (US-2).
100
+ *
101
+ * @param dirOverride - Profile-directory override (test seam); see `profileDir`.
102
+ * @returns The erased registry entry for `get_guidance`.
103
+ */
104
+ export const makeGetGuidanceTool = (dirOverride) => defineTool({
105
+ name: "get_guidance",
106
+ description: "Return teaching guidance and what to learn next for a topic or the weakest area. Read-only.",
107
+ inputSchema: GetGuidanceInputSchema,
108
+ handler: async (input) => {
109
+ const profile = await loadProfile(dirOverride);
110
+ const { topics } = loadBundledCatalog();
111
+ const standing = resolveStanding(input, topics, profile);
112
+ return guidanceResultFor(standing);
113
+ },
114
+ });
115
+ /** Default `get_guidance` module (env / `~/.vibe-hero`), used by the registry. */
116
+ export const getGuidanceTool = makeGetGuidanceTool();
117
+ //# sourceMappingURL=guidance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guidance.js","sourceRoot":"","sources":["../../src/tools/guidance.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @file Real `list_topics` tool module (T026, US-2).
3
+ *
4
+ * Enumerates the catalog's topics, optionally filtered by `tool` and/or `class`,
5
+ * returning one row per topic (`{ key, id, class, title, tiers, itemCount }`)
6
+ * plus the catalog `version`. Works offline against the bundled snapshot
7
+ * (FR-025); `catalogVersion` comes from the manifest derived over the loaded
8
+ * topics (the placeholder `0.0.0-bundled` until the fetch layer supplies a real
9
+ * semver — see `buildManifest`).
10
+ *
11
+ * Telemetry-free (SC-011): enumeration reads only the catalog, never the
12
+ * profile's observation/offer state. Gated (FR-032) — `index.ts` returns
13
+ * SETUP_REQUIRED before the handler runs when unconfigured.
14
+ *
15
+ * Catalog source: resolves via {@link resolveCatalog} (fresh-fetch → cache →
16
+ * bundled, T054). With no `VIBE_HERO_CONTENT_URL` configured this is exactly the
17
+ * prior bundled behavior; when a source is configured the reported
18
+ * `catalogVersion` advances after a published update (SC-007). All remote
19
+ * failures degrade silently to cache/bundled (FR-027).
20
+ *
21
+ * Filters:
22
+ * - `class` ("general" | "tool") matches a topic's `class.kind`.
23
+ * - `tool` keeps `general` topics (they apply to every tool) plus tool-scoped
24
+ * topics whose tool matches — the same scoping rule `get_status` uses.
25
+ *
26
+ * Exposed as a `(dirOverride, resolver)` factory: `dirOverride` flows to the
27
+ * content-cache dir (sibling of the profile home), and `resolver` is the catalog
28
+ * source seam (defaults to {@link resolveCatalog}) so tests can inject a fake
29
+ * fetch/cache without touching the network.
30
+ *
31
+ * Source of truth: specs/001-vibe-hero-mvp/contracts/mcp-tools.md (`list_topics`),
32
+ * spec.md US-2 / FR-025 / FR-026 / FR-027 / SC-006 / SC-007.
33
+ */
34
+ import { type ResolvedCatalog } from "../catalog/resolve.js";
35
+ import { type AnyToolModule } from "./types.js";
36
+ /**
37
+ * Catalog-resolution seam for {@link makeListTopicsTool}. Mirrors
38
+ * {@link resolveCatalog}'s signature so tests can inject a fake fetch/cache while
39
+ * production uses the real fresh-fetch → cache → bundled resolution.
40
+ */
41
+ export type CatalogResolver = (dirOverride?: string) => Promise<ResolvedCatalog>;
42
+ /**
43
+ * Build the `list_topics` tool module (US-2).
44
+ *
45
+ * @param dirOverride - Profile-home override (test seam); flows to the content
46
+ * cache dir used by {@link resolveCatalog}.
47
+ * @param resolver - Catalog-resolution seam (test seam); defaults to
48
+ * {@link resolveCatalog} (fresh-fetch → cache → bundled). Tests inject a fake
49
+ * resolver/fetch so no real network is hit.
50
+ * @returns The erased registry entry for `list_topics`.
51
+ */
52
+ export declare const makeListTopicsTool: (dirOverride?: string, resolver?: CatalogResolver) => AnyToolModule;
53
+ /** Default `list_topics` module (env / `~/.vibe-hero`), used by the registry. */
54
+ export declare const listTopicsTool: AnyToolModule;
55
+ //# sourceMappingURL=listTopics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listTopics.d.ts","sourceRoot":"","sources":["../../src/tools/listTopics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAkB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAS7E,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAG5D;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,CAC5B,WAAW,CAAC,EAAE,MAAM,KACjB,OAAO,CAAC,eAAe,CAAC,CAAC;AAS9B;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,GAC7B,cAAc,MAAM,EACpB,WAAU,eAAgC,KACzC,aA6BC,CAAC;AAEL,iFAAiF;AACjF,eAAO,MAAM,cAAc,EAAE,aAAoC,CAAC"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @file Real `list_topics` tool module (T026, US-2).
3
+ *
4
+ * Enumerates the catalog's topics, optionally filtered by `tool` and/or `class`,
5
+ * returning one row per topic (`{ key, id, class, title, tiers, itemCount }`)
6
+ * plus the catalog `version`. Works offline against the bundled snapshot
7
+ * (FR-025); `catalogVersion` comes from the manifest derived over the loaded
8
+ * topics (the placeholder `0.0.0-bundled` until the fetch layer supplies a real
9
+ * semver — see `buildManifest`).
10
+ *
11
+ * Telemetry-free (SC-011): enumeration reads only the catalog, never the
12
+ * profile's observation/offer state. Gated (FR-032) — `index.ts` returns
13
+ * SETUP_REQUIRED before the handler runs when unconfigured.
14
+ *
15
+ * Catalog source: resolves via {@link resolveCatalog} (fresh-fetch → cache →
16
+ * bundled, T054). With no `VIBE_HERO_CONTENT_URL` configured this is exactly the
17
+ * prior bundled behavior; when a source is configured the reported
18
+ * `catalogVersion` advances after a published update (SC-007). All remote
19
+ * failures degrade silently to cache/bundled (FR-027).
20
+ *
21
+ * Filters:
22
+ * - `class` ("general" | "tool") matches a topic's `class.kind`.
23
+ * - `tool` keeps `general` topics (they apply to every tool) plus tool-scoped
24
+ * topics whose tool matches — the same scoping rule `get_status` uses.
25
+ *
26
+ * Exposed as a `(dirOverride, resolver)` factory: `dirOverride` flows to the
27
+ * content-cache dir (sibling of the profile home), and `resolver` is the catalog
28
+ * source seam (defaults to {@link resolveCatalog}) so tests can inject a fake
29
+ * fetch/cache without touching the network.
30
+ *
31
+ * Source of truth: specs/001-vibe-hero-mvp/contracts/mcp-tools.md (`list_topics`),
32
+ * spec.md US-2 / FR-025 / FR-026 / FR-027 / SC-006 / SC-007.
33
+ */
34
+ import { resolveCatalog } from "../catalog/resolve.js";
35
+ import { abilityKey } from "../schemas/common.js";
36
+ import { ListTopicsInputSchema, } from "../schemas/tools.js";
37
+ import { defineTool } from "./types.js";
38
+ import { topicInScope } from "./us2/standing.js";
39
+ /** The narrowed `class` discriminator surfaced in a {@link ListTopicsRow}. */
40
+ const classKind = (topic) => topic.class.kind;
41
+ /** Distinct tiers present in a topic's items, ascending (manifest convention). */
42
+ const topicTiers = (topic) => [...new Set(topic.items.map((item) => item.tier))].sort((a, b) => a - b);
43
+ /**
44
+ * Build the `list_topics` tool module (US-2).
45
+ *
46
+ * @param dirOverride - Profile-home override (test seam); flows to the content
47
+ * cache dir used by {@link resolveCatalog}.
48
+ * @param resolver - Catalog-resolution seam (test seam); defaults to
49
+ * {@link resolveCatalog} (fresh-fetch → cache → bundled). Tests inject a fake
50
+ * resolver/fetch so no real network is hit.
51
+ * @returns The erased registry entry for `list_topics`.
52
+ */
53
+ export const makeListTopicsTool = (dirOverride, resolver = resolveCatalog) => defineTool({
54
+ name: "list_topics",
55
+ description: "Enumerate catalog topics, optionally filtered by tool or class. Read-only.",
56
+ inputSchema: ListTopicsInputSchema,
57
+ handler: async (input) => {
58
+ // Resolve via fresh-fetch → cache → bundled (T054). `catalogVersion` is
59
+ // the winning source's version, independent of the caller's filter, so it
60
+ // stays stable across queries and advances after a published update.
61
+ const { topics, catalogVersion } = await resolver(dirOverride);
62
+ const filtered = topics
63
+ .filter((topic) => topicInScope(topic, input.tool))
64
+ .filter((topic) => input.class === undefined || classKind(topic) === input.class);
65
+ const rows = filtered.map((topic) => ({
66
+ key: abilityKey(topic.class, topic.id),
67
+ id: topic.id,
68
+ class: classKind(topic),
69
+ title: topic.title,
70
+ tiers: topicTiers(topic),
71
+ itemCount: topic.items.length,
72
+ }));
73
+ return { topics: rows, catalogVersion };
74
+ },
75
+ });
76
+ /** Default `list_topics` module (env / `~/.vibe-hero`), used by the registry. */
77
+ export const listTopicsTool = makeListTopicsTool();
78
+ //# sourceMappingURL=listTopics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listTopics.js","sourceRoot":"","sources":["../../src/tools/listTopics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAE,cAAc,EAAwB,MAAM,uBAAuB,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EACL,qBAAqB,GAItB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAsB,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAWjD,8EAA8E;AAC9E,MAAM,SAAS,GAAG,CAAC,KAAY,EAAsB,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC;AAEzE,kFAAkF;AAClF,MAAM,UAAU,GAAG,CAAC,KAAY,EAA0B,EAAE,CAC1D,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAE3E;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,WAAoB,EACpB,WAA4B,cAAc,EAC3B,EAAE,CACjB,UAAU,CAAC;IACT,IAAI,EAAE,aAAa;IACnB,WAAW,EACT,4EAA4E;IAC9E,WAAW,EAAE,qBAAqB;IAClC,OAAO,EAAE,KAAK,EAAE,KAAsB,EAA6B,EAAE;QACnE,wEAAwE;QACxE,0EAA0E;QAC1E,qEAAqE;QACrE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAC;QAE/D,MAAM,QAAQ,GAAG,MAAM;aACpB,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;aAClD,MAAM,CACL,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,SAAS,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC,KAAK,CACzE,CAAC;QAEJ,MAAM,IAAI,GAAoB,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACrD,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC;YACtC,EAAE,EAAE,KAAK,CAAC,EAAE;YACZ,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC;YACvB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC;YACxB,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM;SAC9B,CAAC,CAAC,CAAC;QAEJ,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;IAC1C,CAAC;CACF,CAAC,CAAC;AAEL,iFAAiF;AACjF,MAAM,CAAC,MAAM,cAAc,GAAkB,kBAAkB,EAAE,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @file Real `get_offer` / `record_offer_response` tool modules (T036, US-1).
3
+ *
4
+ * These two tools are the end-of-work offer surface, thin wrappers over the pure
5
+ * offer engine (`../observation/offers.js`):
6
+ *
7
+ * - `get_offer` ({sessionId, tool}) resolves whether an offer may surface at an
8
+ * end-of-work breakpoint and for which key, honoring cadence + anti-fatigue
9
+ * via {@link resolveOffer}. The candidate pool is the per-session
10
+ * `OfferLedger.candidateKeys` accumulated by `record_observation` (get_offer
11
+ * receives no signals of its own). When an offer surfaces it is recorded via
12
+ * {@link markOffered} so the cadence caps apply on the next call, and the
13
+ * offer's `prompt` is built from the catalog topic's summary.
14
+ *
15
+ * - `record_offer_response` ({sessionId, key, response}) persists the
16
+ * accept/decline/defer outcome: a decline applies within-session suppression
17
+ * (FR-020) AND cross-session backoff + eventual global mute (FR-020b) via
18
+ * {@link applyDecline}; an accept resets the consecutive-decline counter and
19
+ * clears the mute via {@link applyAccept}; a defer leaves state unchanged.
20
+ *
21
+ * Both delegate the decision/state math to the pure engine and own only the IO:
22
+ * clock, catalog load, and the atomic {@link updateProfile} write. Neither tool
23
+ * EVER touches abilities/graduations/quizHistory — offers never score (FR-005,
24
+ * SC-003).
25
+ *
26
+ * Gated (FR-032): NOT exempt — `index.ts`/`withSetupGate` returns SETUP_REQUIRED
27
+ * when `profile.config` is absent. The handlers assume a configured profile.
28
+ *
29
+ * Each tool is a `(dirOverride, catalogLoader)` factory mirroring
30
+ * `startQuiz.ts`/`recordObservation.ts`.
31
+ *
32
+ * Source of truth: specs/001-vibe-hero-mvp/contracts/mcp-tools.md (`get_offer`,
33
+ * `record_offer_response`), spec.md FR-019/020/020a/020b, data-model.md
34
+ * (§ OfferLedger), src/config.ts (ASSESSMENT_CONFIG).
35
+ */
36
+ import type { CatalogLoadResult } from "../catalog/loader.js";
37
+ import { type AnyToolModule } from "./types.js";
38
+ /** Catalog source override (test seam); defaults to the bundled snapshot. */
39
+ export type CatalogLoader = () => CatalogLoadResult;
40
+ /**
41
+ * Build the `get_offer` tool module (US-1).
42
+ *
43
+ * @param dirOverride - Profile-directory override (test seam); see `profileDir`.
44
+ * @param catalogLoader - Catalog source override (test seam); defaults to the
45
+ * bundled snapshot {@link loadBundledCatalog}.
46
+ * @returns The erased registry entry for `get_offer`.
47
+ */
48
+ export declare const makeGetOfferTool: (dirOverride?: string, catalogLoader?: CatalogLoader) => AnyToolModule;
49
+ /**
50
+ * Build the `record_offer_response` tool module (US-1).
51
+ *
52
+ * @param dirOverride - Profile-directory override (test seam); see `profileDir`.
53
+ * @returns The erased registry entry for `record_offer_response`.
54
+ */
55
+ export declare const makeRecordOfferResponseTool: (dirOverride?: string) => AnyToolModule;
56
+ /** Default `get_offer` module (env / `~/.vibe-hero` + bundled catalog). */
57
+ export declare const getOfferTool: AnyToolModule;
58
+ /** Default `record_offer_response` module (env / `~/.vibe-hero`). */
59
+ export declare const recordOfferResponseTool: AnyToolModule;
60
+ //# sourceMappingURL=offers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offers.d.ts","sourceRoot":"","sources":["../../src/tools/offers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAqB9D,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAE5D,6EAA6E;AAC7E,MAAM,MAAM,aAAa,GAAG,MAAM,iBAAiB,CAAC;AAapD;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAC3B,cAAc,MAAM,EACpB,gBAAe,aAAkC,KAChD,aA0DC,CAAC;AAiBL;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,GACtC,cAAc,MAAM,KACnB,aA8CC,CAAC;AAEL,2EAA2E;AAC3E,eAAO,MAAM,YAAY,EAAE,aAAkC,CAAC;AAE9D,qEAAqE;AACrE,eAAO,MAAM,uBAAuB,EAAE,aACP,CAAC"}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * @file Real `get_offer` / `record_offer_response` tool modules (T036, US-1).
3
+ *
4
+ * These two tools are the end-of-work offer surface, thin wrappers over the pure
5
+ * offer engine (`../observation/offers.js`):
6
+ *
7
+ * - `get_offer` ({sessionId, tool}) resolves whether an offer may surface at an
8
+ * end-of-work breakpoint and for which key, honoring cadence + anti-fatigue
9
+ * via {@link resolveOffer}. The candidate pool is the per-session
10
+ * `OfferLedger.candidateKeys` accumulated by `record_observation` (get_offer
11
+ * receives no signals of its own). When an offer surfaces it is recorded via
12
+ * {@link markOffered} so the cadence caps apply on the next call, and the
13
+ * offer's `prompt` is built from the catalog topic's summary.
14
+ *
15
+ * - `record_offer_response` ({sessionId, key, response}) persists the
16
+ * accept/decline/defer outcome: a decline applies within-session suppression
17
+ * (FR-020) AND cross-session backoff + eventual global mute (FR-020b) via
18
+ * {@link applyDecline}; an accept resets the consecutive-decline counter and
19
+ * clears the mute via {@link applyAccept}; a defer leaves state unchanged.
20
+ *
21
+ * Both delegate the decision/state math to the pure engine and own only the IO:
22
+ * clock, catalog load, and the atomic {@link updateProfile} write. Neither tool
23
+ * EVER touches abilities/graduations/quizHistory — offers never score (FR-005,
24
+ * SC-003).
25
+ *
26
+ * Gated (FR-032): NOT exempt — `index.ts`/`withSetupGate` returns SETUP_REQUIRED
27
+ * when `profile.config` is absent. The handlers assume a configured profile.
28
+ *
29
+ * Each tool is a `(dirOverride, catalogLoader)` factory mirroring
30
+ * `startQuiz.ts`/`recordObservation.ts`.
31
+ *
32
+ * Source of truth: specs/001-vibe-hero-mvp/contracts/mcp-tools.md (`get_offer`,
33
+ * `record_offer_response`), spec.md FR-019/020/020a/020b, data-model.md
34
+ * (§ OfferLedger), src/config.ts (ASSESSMENT_CONFIG).
35
+ */
36
+ import { ASSESSMENT_CONFIG } from "../config.js";
37
+ import { loadBundledCatalog } from "../catalog/bundled/index.js";
38
+ import { loadProfile, updateProfile } from "../profile/store.js";
39
+ import { abilityKey } from "../schemas/common.js";
40
+ import { GetOfferInputSchema, RecordOfferResponseInputSchema, } from "../schemas/tools.js";
41
+ import { applyAccept, applyDecline, applyDefer, ledgerForSession, markOffered, resolveOffer, } from "../observation/offers.js";
42
+ import { defineTool } from "./types.js";
43
+ /** Find the catalog topic whose `(class, id)` serializes to `key`. */
44
+ const findTopicByKey = (topics, key) => topics.find((topic) => abilityKey(topic.class, topic.id) === key);
45
+ /** Build the user-facing offer prompt for a topic (privacy-safe, no scoring). */
46
+ const offerPrompt = (topic) => `You just exercised "${topic.title}". Want a quick quiz to check your grasp? (${topic.summary})`;
47
+ /**
48
+ * Build the `get_offer` tool module (US-1).
49
+ *
50
+ * @param dirOverride - Profile-directory override (test seam); see `profileDir`.
51
+ * @param catalogLoader - Catalog source override (test seam); defaults to the
52
+ * bundled snapshot {@link loadBundledCatalog}.
53
+ * @returns The erased registry entry for `get_offer`.
54
+ */
55
+ export const makeGetOfferTool = (dirOverride, catalogLoader = loadBundledCatalog) => defineTool({
56
+ name: "get_offer",
57
+ description: "Resolve whether to surface an end-of-work learning offer for the session.",
58
+ inputSchema: GetOfferInputSchema,
59
+ handler: async (input) => {
60
+ const now = new Date();
61
+ const profile = await loadProfile(dirOverride);
62
+ const config = profile.config;
63
+ // Gated: config is present. Reconcile the ledger to THIS session (a new
64
+ // session id rolls over the per-session accounting) before deciding.
65
+ const ledger = ledgerForSession(profile.offers, input.sessionId);
66
+ const decision = resolveOffer({
67
+ proactiveOffers: config?.proactiveOffers ?? false,
68
+ offerCadence: config?.offerCadence ?? "off",
69
+ ledger,
70
+ backoff: profile.backoff,
71
+ candidates: ledger.candidateKeys,
72
+ }, now);
73
+ if (decision.kind === "suppressed") {
74
+ // Persist any session rollover (so a fresh session id is recorded) but
75
+ // never touch scoring state.
76
+ if (ledger.sessionId !== profile.offers.sessionId) {
77
+ await persistLedger(ledger, dirOverride);
78
+ }
79
+ return { suppressed: decision.reason };
80
+ }
81
+ const { topics } = catalogLoader();
82
+ const topic = findTopicByKey(topics, decision.key);
83
+ if (topic === undefined) {
84
+ // The candidate key has no resolvable topic (catalog drift): treat as
85
+ // no candidate rather than throwing — offers must never crash the path.
86
+ if (ledger.sessionId !== profile.offers.sessionId) {
87
+ await persistLedger(ledger, dirOverride);
88
+ }
89
+ return { suppressed: "no_candidate" };
90
+ }
91
+ // Record that the offer surfaced so the cadence caps apply next time.
92
+ const offered = markOffered(ledger, decision.key);
93
+ await persistLedger(offered, dirOverride);
94
+ return {
95
+ offer: {
96
+ key: decision.key,
97
+ title: topic.title,
98
+ prompt: offerPrompt(topic),
99
+ },
100
+ };
101
+ },
102
+ });
103
+ /**
104
+ * Persist ONLY the `offers` ledger block (atomic). Never touches abilities,
105
+ * graduations, quizHistory, reviewSchedule, or backoff — keeping the offer path
106
+ * score-free (FR-005 / SC-003).
107
+ */
108
+ const persistLedger = async (ledger, dirOverride) => {
109
+ await updateProfile((current) => ({ ...current, offers: ledger }), dirOverride);
110
+ };
111
+ /**
112
+ * Build the `record_offer_response` tool module (US-1).
113
+ *
114
+ * @param dirOverride - Profile-directory override (test seam); see `profileDir`.
115
+ * @returns The erased registry entry for `record_offer_response`.
116
+ */
117
+ export const makeRecordOfferResponseTool = (dirOverride) => defineTool({
118
+ name: "record_offer_response",
119
+ description: "Record an accept/decline/defer offer response so cadence and anti-nag are honored.",
120
+ inputSchema: RecordOfferResponseInputSchema,
121
+ handler: async (input) => {
122
+ const now = new Date();
123
+ // Single atomic read-modify-write so concurrent sessions serialize. Only
124
+ // the `offers` ledger + `backoff` change — scoring state is untouched.
125
+ await updateProfile((current) => {
126
+ const ledger = ledgerForSession(current.offers, input.sessionId);
127
+ switch (input.response) {
128
+ case "decline": {
129
+ const { ledger: nextLedger, backoff } = applyDecline(ledger, current.backoff, input.key, now, ASSESSMENT_CONFIG);
130
+ return { ...current, offers: nextLedger, backoff };
131
+ }
132
+ case "accept": {
133
+ return {
134
+ ...current,
135
+ offers: ledger,
136
+ backoff: applyAccept(current.backoff),
137
+ };
138
+ }
139
+ case "defer": {
140
+ const { ledger: nextLedger, backoff } = applyDefer(ledger, current.backoff);
141
+ return { ...current, offers: nextLedger, backoff };
142
+ }
143
+ }
144
+ }, dirOverride);
145
+ return { ok: true };
146
+ },
147
+ });
148
+ /** Default `get_offer` module (env / `~/.vibe-hero` + bundled catalog). */
149
+ export const getOfferTool = makeGetOfferTool();
150
+ /** Default `record_offer_response` module (env / `~/.vibe-hero`). */
151
+ export const recordOfferResponseTool = makeRecordOfferResponseTool();
152
+ //# sourceMappingURL=offers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offers.js","sourceRoot":"","sources":["../../src/tools/offers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @file Tool registry (T020).
3
+ *
4
+ * This wires all 10 MCP tools into a single {@link TOOL_REGISTRY} that
5
+ * `index.ts` iterates over to register them. Each module carries its real input
6
+ * schema (from `schemas/tools.ts`) so the host-facing tool signatures are
7
+ * correct. `save_config` / `get_config` are the real US-0 implementations
8
+ * (`./config.js`, T022); `get_status` / `list_topics` / `get_guidance` are the
9
+ * real US-2 read tools (`./status.js`, `./listTopics.js`, `./guidance.js`,
10
+ * T025–T027); `start_quiz` / `submit_answer` are the real US-1 core-loop tools
11
+ * (`./startQuiz.js`, `./submitAnswer.js`, T031/T032); `record_observation` /
12
+ * `get_offer` / `record_offer_response` are the real US-1 offer tools
13
+ * (`./recordObservation.js`, `./offers.js`, T034/T036). All 10 are now real —
14
+ * no placeholders remain.
15
+ *
16
+ * The registration architecture and gate wiring delivered here stay unchanged.
17
+ *
18
+ * Source of truth: specs/001-vibe-hero-mvp/contracts/mcp-tools.md.
19
+ */
20
+ import { type AnyToolModule } from "./types.js";
21
+ /**
22
+ * All 10 vibe-hero MCP tools, in contract order. `index.ts` registers each one
23
+ * (wrapping the handler with the setup gate). This list is the single source of
24
+ * which tools exist.
25
+ */
26
+ export declare const TOOL_REGISTRY: readonly AnyToolModule[];
27
+ //# sourceMappingURL=placeholders.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"placeholders.d.ts","sourceRoot":"","sources":["../../src/tools/placeholders.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAUhD;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,aAAa,EAejD,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @file Tool registry (T020).
3
+ *
4
+ * This wires all 10 MCP tools into a single {@link TOOL_REGISTRY} that
5
+ * `index.ts` iterates over to register them. Each module carries its real input
6
+ * schema (from `schemas/tools.ts`) so the host-facing tool signatures are
7
+ * correct. `save_config` / `get_config` are the real US-0 implementations
8
+ * (`./config.js`, T022); `get_status` / `list_topics` / `get_guidance` are the
9
+ * real US-2 read tools (`./status.js`, `./listTopics.js`, `./guidance.js`,
10
+ * T025–T027); `start_quiz` / `submit_answer` are the real US-1 core-loop tools
11
+ * (`./startQuiz.js`, `./submitAnswer.js`, T031/T032); `record_observation` /
12
+ * `get_offer` / `record_offer_response` are the real US-1 offer tools
13
+ * (`./recordObservation.js`, `./offers.js`, T034/T036). All 10 are now real —
14
+ * no placeholders remain.
15
+ *
16
+ * The registration architecture and gate wiring delivered here stay unchanged.
17
+ *
18
+ * Source of truth: specs/001-vibe-hero-mvp/contracts/mcp-tools.md.
19
+ */
20
+ import { saveConfigTool, getConfigTool } from "./config.js";
21
+ import { getStatusTool } from "./status.js";
22
+ import { listTopicsTool } from "./listTopics.js";
23
+ import { getGuidanceTool } from "./guidance.js";
24
+ import { startQuizTool } from "./startQuiz.js";
25
+ import { submitAnswerTool } from "./submitAnswer.js";
26
+ import { recordObservationTool } from "./recordObservation.js";
27
+ import { getOfferTool, recordOfferResponseTool } from "./offers.js";
28
+ /**
29
+ * All 10 vibe-hero MCP tools, in contract order. `index.ts` registers each one
30
+ * (wrapping the handler with the setup gate). This list is the single source of
31
+ * which tools exist.
32
+ */
33
+ export const TOOL_REGISTRY = [
34
+ // Real US-2 read tools (T025–T027).
35
+ getStatusTool,
36
+ listTopicsTool,
37
+ getGuidanceTool,
38
+ // Real US-1 core-loop tools (T031/T032).
39
+ startQuizTool,
40
+ submitAnswerTool,
41
+ // Real US-0 implementations (T022).
42
+ saveConfigTool,
43
+ getConfigTool,
44
+ // Real US-1 observation/offer tools (T034/T036).
45
+ recordObservationTool,
46
+ getOfferTool,
47
+ recordOfferResponseTool,
48
+ ];
49
+ //# sourceMappingURL=placeholders.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"placeholders.js","sourceRoot":"","sources":["../../src/tools/placeholders.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAEpE;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAA6B;IACrD,oCAAoC;IACpC,aAAa;IACb,cAAc;IACd,eAAe;IACf,yCAAyC;IACzC,aAAa;IACb,gBAAgB;IAChB,oCAAoC;IACpC,cAAc;IACd,aAAa;IACb,iDAAiD;IACjD,qBAAqB;IACrB,YAAY;IACZ,uBAAuB;CACxB,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @file Real `record_observation` tool module (T034, US-1).
3
+ *
4
+ * Intake for the observation source (the real-time Claude Code hook in v1). It
5
+ * accepts derived, privacy-safe signals (`toolName` / `mcpTool` / `success` /
6
+ * `toolUseId`) plus a `sessionId`, matches them to candidate topic keys against
7
+ * the catalog's {@link TriggerSignal}s, accumulates the candidates into the
8
+ * per-session {@link OfferLedger}, and returns `{ offerCandidates }`.
9
+ *
10
+ * Trigger-only — AWARDS NOTHING (FR-005 / SC-003). It deliberately touches ONLY
11
+ * the profile's `offers` ledger (its `sessionId` / candidate accounting). It
12
+ * never reads or writes `abilities`, `graduations`, `quizHistory`, or
13
+ * `reviewSchedule`, so observed usage with no answered quiz produces exactly 0
14
+ * change to points or graduation state (SC-003) — the integration test asserts
15
+ * those fields are byte-identical before/after.
16
+ *
17
+ * v1 scope note: a SINGLE real-time hook source feeds this tool. FR-017's
18
+ * two-source (hook + transcript backfill) correlation by `tool_use_id` is
19
+ * architecture-ready (signals carry `toolUseId`; the {@link ObservationSource}
20
+ * seam exists) but is NOT built here, per tasks.md.
21
+ *
22
+ * Gated (FR-032): NOT exempt — `index.ts`/`withSetupGate` returns SETUP_REQUIRED
23
+ * before this handler runs when `profile.config` is absent. The handler assumes
24
+ * a configured profile and reads `config.proactiveOffers` / `config.offerCadence`
25
+ * only to short-circuit candidate work when offers are disabled (so a disabled
26
+ * user never accrues candidate state needlessly).
27
+ *
28
+ * Exposed as a `(dirOverride, catalogLoader)` factory mirroring
29
+ * `startQuiz.ts` / `status.ts`: the registry uses the default instance
30
+ * (env / `~/.vibe-hero` + bundled catalog); tests inject a temp home + fixture
31
+ * catalog.
32
+ *
33
+ * Source of truth: specs/001-vibe-hero-mvp/contracts/mcp-tools.md
34
+ * (`record_observation`), spec.md FR-005 / FR-015..017 / FR-018 / SC-003,
35
+ * data-model.md (§ OfferLedger).
36
+ */
37
+ import type { CatalogLoadResult } from "../catalog/loader.js";
38
+ import { type AnyToolModule } from "./types.js";
39
+ /** Catalog source override (test seam); defaults to the bundled snapshot. */
40
+ export type CatalogLoader = () => CatalogLoadResult;
41
+ /**
42
+ * Build the `record_observation` tool module (US-1).
43
+ *
44
+ * @param dirOverride - Profile-directory override (test seam); see `profileDir`.
45
+ * @param catalogLoader - Catalog source override (test seam); defaults to the
46
+ * bundled snapshot {@link loadBundledCatalog}.
47
+ * @returns The erased registry entry for `record_observation`.
48
+ */
49
+ export declare const makeRecordObservationTool: (dirOverride?: string, catalogLoader?: CatalogLoader) => AnyToolModule;
50
+ /** Default `record_observation` module (env / `~/.vibe-hero` + bundled catalog). */
51
+ export declare const recordObservationTool: AnyToolModule;
52
+ //# sourceMappingURL=recordObservation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recordObservation.d.ts","sourceRoot":"","sources":["../../src/tools/recordObservation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAe9D,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAE5D,6EAA6E;AAC7E,MAAM,MAAM,aAAa,GAAG,MAAM,iBAAiB,CAAC;AAEpD;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB,GACpC,cAAc,MAAM,EACpB,gBAAe,aAAkC,KAChD,aAuDC,CAAC;AAEL,oFAAoF;AACpF,eAAO,MAAM,qBAAqB,EAAE,aAA2C,CAAC"}