@tostudy-ai/cli 0.8.0 → 0.9.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 (3) hide show
  1. package/dist/cli.js +1551 -774
  2. package/dist/cli.js.map +4 -4
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1289,29 +1289,227 @@ var init_secret_store = __esm({
1289
1289
  }
1290
1290
  });
1291
1291
 
1292
- // src/auth/session.ts
1293
- import fs2 from "node:fs";
1294
- import path2 from "node:path";
1292
+ // src/workspace/workspace-marker.ts
1293
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync } from "node:fs";
1295
1294
  import os2 from "node:os";
1295
+ import path2 from "node:path";
1296
+ function resolveMarkerPath(workspacePath) {
1297
+ if (path2.basename(workspacePath) === ".tostudy") {
1298
+ return path2.join(workspacePath, "workspace.json");
1299
+ }
1300
+ return path2.join(workspacePath, ".tostudy", "workspace.json");
1301
+ }
1302
+ async function readWorkspaceMarker(workspacePath) {
1303
+ const filePath = resolveMarkerPath(workspacePath);
1304
+ if (!existsSync2(filePath)) return null;
1305
+ try {
1306
+ const raw = readFileSync(filePath, "utf-8");
1307
+ const parsed = JSON.parse(raw);
1308
+ if (parsed.version !== CURRENT_VERSION) return null;
1309
+ if (typeof parsed.courseId !== "string" || typeof parsed.enrollmentId !== "string" || typeof parsed.slug !== "string" || typeof parsed.courseTitle !== "string" || typeof parsed.createdAt !== "string" || typeof parsed.updatedAt !== "string") {
1310
+ return null;
1311
+ }
1312
+ return parsed;
1313
+ } catch {
1314
+ return null;
1315
+ }
1316
+ }
1317
+ async function writeWorkspaceMarker(workspacePath, input2, options = {}) {
1318
+ const homeDir = path2.resolve(options.homeDir ?? os2.homedir());
1319
+ const resolvedWorkspace = path2.resolve(workspacePath);
1320
+ if (resolvedWorkspace === homeDir || homeDir.startsWith(resolvedWorkspace + path2.sep)) {
1321
+ throw new Error(
1322
+ `Refusing to write workspace marker at ${workspacePath}: it is the home directory or an ancestor of it.`
1323
+ );
1324
+ }
1325
+ const filePath = resolveMarkerPath(workspacePath);
1326
+ const dir = path2.dirname(filePath);
1327
+ if (!existsSync2(dir)) {
1328
+ mkdirSync2(dir, { recursive: true });
1329
+ }
1330
+ const existing = await readWorkspaceMarker(workspacePath);
1331
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1332
+ const marker = {
1333
+ version: CURRENT_VERSION,
1334
+ courseId: input2.courseId,
1335
+ enrollmentId: input2.enrollmentId,
1336
+ slug: input2.slug,
1337
+ courseTitle: input2.courseTitle,
1338
+ createdAt: existing?.createdAt ?? now,
1339
+ updatedAt: now
1340
+ };
1341
+ writeFileSync2(filePath, JSON.stringify(marker, null, 2), { mode: 384 });
1342
+ return filePath;
1343
+ }
1344
+ var CURRENT_VERSION;
1345
+ var init_workspace_marker = __esm({
1346
+ "src/workspace/workspace-marker.ts"() {
1347
+ "use strict";
1348
+ CURRENT_VERSION = 1;
1349
+ }
1350
+ });
1351
+
1352
+ // src/workspace/resolve.ts
1353
+ var resolve_exports = {};
1354
+ __export(resolve_exports, {
1355
+ courseSlug: () => courseSlug,
1356
+ findCwdWorkspaceUpwards: () => findCwdWorkspaceUpwards,
1357
+ findExistingVault: () => findExistingVault,
1358
+ isCwdWorkspace: () => isCwdWorkspace,
1359
+ resolveActiveCourseWithOverride: () => resolveActiveCourseWithOverride,
1360
+ resolveCwdWorkspacePath: () => resolveCwdWorkspacePath,
1361
+ resolveEffectiveWorkspace: () => resolveEffectiveWorkspace,
1362
+ resolveVaultPath: () => resolveVaultPath,
1363
+ resolveWorkspace: () => resolveWorkspace
1364
+ });
1365
+ import fs2 from "node:fs/promises";
1366
+ import path3 from "node:path";
1367
+ import os3 from "node:os";
1368
+ function courseSlug(title) {
1369
+ return title.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
1370
+ }
1371
+ async function resolveWorkspace(courseTitle, basePath = DEFAULT_BASE) {
1372
+ const slug = courseSlug(courseTitle);
1373
+ const candidate = path3.join(basePath, slug);
1374
+ try {
1375
+ await fs2.access(path3.join(candidate, ".ana-config.json"));
1376
+ return { found: true, workspacePath: candidate, source: "default" };
1377
+ } catch {
1378
+ return { found: false, workspacePath: null };
1379
+ }
1380
+ }
1381
+ async function isCwdWorkspace(cwd = process.cwd()) {
1382
+ return await resolveCwdWorkspacePath(cwd) !== null;
1383
+ }
1384
+ async function resolveCwdWorkspacePath(cwd = process.cwd()) {
1385
+ const tostudyDir = path3.join(cwd, ".tostudy");
1386
+ try {
1387
+ const stat = await fs2.stat(tostudyDir);
1388
+ if (stat.isDirectory()) return tostudyDir;
1389
+ } catch {
1390
+ }
1391
+ try {
1392
+ await fs2.access(path3.join(cwd, ".ana-config.json"));
1393
+ return cwd;
1394
+ } catch {
1395
+ return null;
1396
+ }
1397
+ }
1398
+ async function findCwdWorkspaceUpwards(startCwd = process.cwd(), homeDir = os3.homedir()) {
1399
+ const home = path3.resolve(homeDir);
1400
+ let current = path3.resolve(startCwd);
1401
+ while (true) {
1402
+ if (current === home) return null;
1403
+ const direct = await resolveCwdWorkspacePath(current);
1404
+ if (direct) return direct;
1405
+ const parent = path3.dirname(current);
1406
+ if (parent === current) return null;
1407
+ current = parent;
1408
+ }
1409
+ }
1410
+ function resolveVaultPath(workspacePath, slug) {
1411
+ const base = path3.basename(workspacePath) === ".tostudy" ? path3.dirname(workspacePath) : workspacePath;
1412
+ return path3.join(base, `vault-${slug}`);
1413
+ }
1414
+ async function findExistingVault(workspacePath, slug) {
1415
+ const slugged = resolveVaultPath(workspacePath, slug);
1416
+ try {
1417
+ await fs2.access(path3.join(slugged, ".ana-vault.json"));
1418
+ return slugged;
1419
+ } catch {
1420
+ }
1421
+ const legacy = path3.join(workspacePath, "vault");
1422
+ try {
1423
+ await fs2.access(path3.join(legacy, ".ana-vault.json"));
1424
+ return legacy;
1425
+ } catch {
1426
+ return null;
1427
+ }
1428
+ }
1429
+ async function resolveEffectiveWorkspace(courseTitle, storedPath, cwd = process.cwd(), defaultBasePath = DEFAULT_BASE, homeDir = os3.homedir()) {
1430
+ const cwdWorkspace = await findCwdWorkspaceUpwards(cwd, homeDir);
1431
+ if (cwdWorkspace) {
1432
+ return { found: true, workspacePath: cwdWorkspace, source: "cwd" };
1433
+ }
1434
+ if (storedPath) {
1435
+ try {
1436
+ await fs2.access(path3.join(storedPath, ".ana-config.json"));
1437
+ return { found: true, workspacePath: storedPath, source: "stored" };
1438
+ } catch {
1439
+ }
1440
+ }
1441
+ const result = await resolveWorkspace(courseTitle, defaultBasePath);
1442
+ if (result.found) {
1443
+ return { ...result, source: "default" };
1444
+ }
1445
+ return { found: false, workspacePath: null };
1446
+ }
1447
+ async function findWorkspaceMarkerUpwards(startCwd, homeDir) {
1448
+ const home = path3.resolve(homeDir);
1449
+ let current = path3.resolve(startCwd);
1450
+ while (true) {
1451
+ if (current === home) return null;
1452
+ const marker = await readWorkspaceMarker(current);
1453
+ if (marker) return marker;
1454
+ const parent = path3.dirname(current);
1455
+ if (parent === current) return null;
1456
+ current = parent;
1457
+ }
1458
+ }
1459
+ async function resolveActiveCourseWithOverride(options = {}) {
1460
+ const cwd = options.cwd ?? process.cwd();
1461
+ const homeDir = options.homeDir ?? os3.homedir();
1462
+ const marker = await findWorkspaceMarkerUpwards(cwd, homeDir);
1463
+ if (marker) {
1464
+ return {
1465
+ courseId: marker.courseId,
1466
+ enrollmentId: marker.enrollmentId,
1467
+ courseTitle: marker.courseTitle,
1468
+ source: "cwd-marker"
1469
+ };
1470
+ }
1471
+ const global = await getActiveCourse(options.configDir);
1472
+ if (!global) return null;
1473
+ return {
1474
+ courseId: global.courseId,
1475
+ enrollmentId: global.enrollmentId,
1476
+ courseTitle: global.courseTitle,
1477
+ source: "global"
1478
+ };
1479
+ }
1480
+ var DEFAULT_BASE;
1481
+ var init_resolve = __esm({
1482
+ "src/workspace/resolve.ts"() {
1483
+ "use strict";
1484
+ init_workspace_marker();
1485
+ init_session();
1486
+ DEFAULT_BASE = path3.join(os3.homedir(), "study");
1487
+ }
1488
+ });
1489
+
1490
+ // src/auth/session.ts
1491
+ import fs3 from "node:fs";
1492
+ import path4 from "node:path";
1493
+ import os4 from "node:os";
1296
1494
  function getConfigDir(override) {
1297
1495
  if (override) return override;
1298
1496
  if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
1299
- return path2.join(process.env["XDG_CONFIG_HOME"], "tostudy");
1497
+ return path4.join(process.env["XDG_CONFIG_HOME"], "tostudy");
1300
1498
  }
1301
- return path2.join(os2.homedir(), ".tostudy");
1499
+ return path4.join(os4.homedir(), ".tostudy");
1302
1500
  }
1303
1501
  function getCourseOnboardingPath(configDir) {
1304
- return path2.join(getConfigDir(configDir), "course-onboarding.json");
1502
+ return path4.join(getConfigDir(configDir), "course-onboarding.json");
1305
1503
  }
1306
1504
  function readCourseOnboardingState(configDir) {
1307
1505
  const onboardingPath = getCourseOnboardingPath(configDir);
1308
- if (!fs2.existsSync(onboardingPath)) return {};
1309
- return JSON.parse(fs2.readFileSync(onboardingPath, "utf-8"));
1506
+ if (!fs3.existsSync(onboardingPath)) return {};
1507
+ return JSON.parse(fs3.readFileSync(onboardingPath, "utf-8"));
1310
1508
  }
1311
1509
  function writeCourseOnboardingState(state, configDir) {
1312
1510
  const dir = getConfigDir(configDir);
1313
- fs2.mkdirSync(dir, { recursive: true });
1314
- fs2.writeFileSync(getCourseOnboardingPath(configDir), JSON.stringify(state, null, 2), {
1511
+ fs3.mkdirSync(dir, { recursive: true });
1512
+ fs3.writeFileSync(getCourseOnboardingPath(configDir), JSON.stringify(state, null, 2), {
1315
1513
  mode: 384
1316
1514
  });
1317
1515
  }
@@ -1343,27 +1541,27 @@ async function saveCourseLearnerProfile(course, learnerProfile, artifacts, confi
1343
1541
  await saveUserProfile(learnerProfile, configDir);
1344
1542
  }
1345
1543
  function getUserProfilePath(configDir) {
1346
- return path2.join(getConfigDir(configDir), "user-profile.json");
1544
+ return path4.join(getConfigDir(configDir), "user-profile.json");
1347
1545
  }
1348
1546
  async function getUserProfile(configDir) {
1349
1547
  const profilePath = getUserProfilePath(configDir);
1350
- if (!fs2.existsSync(profilePath)) return null;
1548
+ if (!fs3.existsSync(profilePath)) return null;
1351
1549
  try {
1352
- return JSON.parse(fs2.readFileSync(profilePath, "utf-8"));
1550
+ return JSON.parse(fs3.readFileSync(profilePath, "utf-8"));
1353
1551
  } catch {
1354
1552
  return null;
1355
1553
  }
1356
1554
  }
1357
1555
  async function saveUserProfile(profile, configDir) {
1358
1556
  const dir = getConfigDir(configDir);
1359
- fs2.mkdirSync(dir, { recursive: true });
1360
- fs2.writeFileSync(getUserProfilePath(configDir), JSON.stringify(profile, null, 2), {
1557
+ fs3.mkdirSync(dir, { recursive: true });
1558
+ fs3.writeFileSync(getUserProfilePath(configDir), JSON.stringify(profile, null, 2), {
1361
1559
  mode: 384
1362
1560
  });
1363
1561
  }
1364
1562
  async function saveSession(session, configDir) {
1365
1563
  const dir = getConfigDir(configDir);
1366
- fs2.mkdirSync(dir, { recursive: true });
1564
+ fs3.mkdirSync(dir, { recursive: true });
1367
1565
  await saveSessionSecrets(
1368
1566
  {
1369
1567
  accessToken: session.token,
@@ -1378,15 +1576,15 @@ async function saveSession(session, configDir) {
1378
1576
  apiUrl: session.apiUrl,
1379
1577
  sessionId: session.sessionId
1380
1578
  };
1381
- fs2.writeFileSync(path2.join(dir, "config.json"), JSON.stringify(stored, null, 2), {
1579
+ fs3.writeFileSync(path4.join(dir, "config.json"), JSON.stringify(stored, null, 2), {
1382
1580
  mode: 384
1383
1581
  });
1384
1582
  }
1385
1583
  async function getSession(configDir) {
1386
1584
  const dir = getConfigDir(configDir);
1387
- const p = path2.join(dir, "config.json");
1388
- if (!fs2.existsSync(p)) return null;
1389
- const stored = JSON.parse(fs2.readFileSync(p, "utf-8"));
1585
+ const p = path4.join(dir, "config.json");
1586
+ if (!fs3.existsSync(p)) return null;
1587
+ const stored = JSON.parse(fs3.readFileSync(p, "utf-8"));
1390
1588
  const secrets = await loadStoredSessionSecrets(dir);
1391
1589
  if (!secrets) return null;
1392
1590
  return {
@@ -1401,26 +1599,26 @@ async function getSession(configDir) {
1401
1599
  }
1402
1600
  async function clearSession(configDir) {
1403
1601
  const dir = getConfigDir(configDir);
1404
- const p = path2.join(dir, "config.json");
1405
- if (fs2.existsSync(p)) fs2.unlinkSync(p);
1406
- const ap = path2.join(dir, "active-course.json");
1407
- if (fs2.existsSync(ap)) fs2.unlinkSync(ap);
1602
+ const p = path4.join(dir, "config.json");
1603
+ if (fs3.existsSync(p)) fs3.unlinkSync(p);
1604
+ const ap = path4.join(dir, "active-course.json");
1605
+ if (fs3.existsSync(ap)) fs3.unlinkSync(ap);
1408
1606
  const onboardingPath = getCourseOnboardingPath(configDir);
1409
- if (fs2.existsSync(onboardingPath)) fs2.unlinkSync(onboardingPath);
1607
+ if (fs3.existsSync(onboardingPath)) fs3.unlinkSync(onboardingPath);
1410
1608
  await deleteStoredSessionSecrets(dir);
1411
1609
  }
1412
1610
  async function getActiveCourse(configDir) {
1413
1611
  const dir = getConfigDir(configDir);
1414
- const p = path2.join(dir, "active-course.json");
1415
- if (!fs2.existsSync(p)) return null;
1416
- return JSON.parse(fs2.readFileSync(p, "utf-8"));
1612
+ const p = path4.join(dir, "active-course.json");
1613
+ if (!fs3.existsSync(p)) return null;
1614
+ return JSON.parse(fs3.readFileSync(p, "utf-8"));
1417
1615
  }
1418
1616
  async function setActiveCourse(course, configDir) {
1419
1617
  const dir = getConfigDir(configDir);
1420
- fs2.mkdirSync(dir, { recursive: true });
1618
+ fs3.mkdirSync(dir, { recursive: true });
1421
1619
  const onboardingState = await getCourseOnboardingState(course.courseId, configDir);
1422
1620
  const persistedCourse = onboardingState?.initCompletedAt && !course.lastInitCourseId ? { ...course, lastInitCourseId: course.courseId } : course;
1423
- fs2.writeFileSync(path2.join(dir, "active-course.json"), JSON.stringify(persistedCourse, null, 2), {
1621
+ fs3.writeFileSync(path4.join(dir, "active-course.json"), JSON.stringify(persistedCourse, null, 2), {
1424
1622
  mode: 384
1425
1623
  });
1426
1624
  }
@@ -1466,13 +1664,23 @@ async function requireSession(configDir) {
1466
1664
  }
1467
1665
  return session;
1468
1666
  }
1469
- async function requireActiveCourse(configDir) {
1470
- const course = await getActiveCourse(configDir);
1471
- if (!course) {
1472
- process.stderr.write("Erro: Nenhum curso ativo. Rode: tostudy select <curso>\n");
1473
- process.exit(3);
1667
+ async function requireActiveCourse(configDir, options = {}) {
1668
+ if (options.respectWorkspace) {
1669
+ const { resolveActiveCourseWithOverride: resolveActiveCourseWithOverride2 } = await Promise.resolve().then(() => (init_resolve(), resolve_exports));
1670
+ const resolved = await resolveActiveCourseWithOverride2({ configDir });
1671
+ if (resolved) {
1672
+ return {
1673
+ courseId: resolved.courseId,
1674
+ enrollmentId: resolved.enrollmentId,
1675
+ courseTitle: resolved.courseTitle
1676
+ };
1677
+ }
1678
+ } else {
1679
+ const course = await getActiveCourse(configDir);
1680
+ if (course) return course;
1474
1681
  }
1475
- return course;
1682
+ process.stderr.write("Erro: Nenhum curso ativo. Rode: tostudy select <curso>\n");
1683
+ process.exit(3);
1476
1684
  }
1477
1685
  async function setLastInitCourseId(courseId, configDir) {
1478
1686
  const existing = await getActiveCourse(configDir);
@@ -2153,34 +2361,34 @@ var init_node_detector = __esm({
2153
2361
 
2154
2362
  // src/installer/ide-detector.ts
2155
2363
  import { execFileSync as execFileSync2 } from "node:child_process";
2156
- import fs3 from "node:fs";
2157
- import path3 from "node:path";
2158
- import os3 from "node:os";
2364
+ import fs4 from "node:fs";
2365
+ import path5 from "node:path";
2366
+ import os5 from "node:os";
2159
2367
  function checkExists(p) {
2160
- return fs3.existsSync(p);
2368
+ return fs4.existsSync(p);
2161
2369
  }
2162
2370
  function detectIDEs() {
2163
- const home = os3.homedir();
2371
+ const home = os5.homedir();
2164
2372
  const ides = [
2165
2373
  // Claude Code: detected by presence of 'claude' binary
2166
2374
  {
2167
2375
  name: "Claude Code",
2168
2376
  id: "claude-code",
2169
2377
  detected: false,
2170
- configPath: path3.join(home, ".mcp.json")
2378
+ configPath: path5.join(home, ".mcp.json")
2171
2379
  },
2172
2380
  // Cursor: ~/.cursor directory
2173
2381
  {
2174
2382
  name: "Cursor",
2175
2383
  id: "cursor",
2176
- detected: checkExists(path3.join(home, ".cursor")),
2177
- configPath: path3.join(home, ".cursor", "mcp.json")
2384
+ detected: checkExists(path5.join(home, ".cursor")),
2385
+ configPath: path5.join(home, ".cursor", "mcp.json")
2178
2386
  },
2179
2387
  // VS Code: ~/.vscode directory
2180
2388
  {
2181
2389
  name: "VS Code",
2182
2390
  id: "vscode",
2183
- detected: checkExists(path3.join(home, ".vscode")),
2391
+ detected: checkExists(path5.join(home, ".vscode")),
2184
2392
  configPath: ".vscode/mcp.json"
2185
2393
  },
2186
2394
  // Claude Desktop: platform-specific config dir
@@ -2188,29 +2396,29 @@ function detectIDEs() {
2188
2396
  name: "Claude Desktop",
2189
2397
  id: "desktop",
2190
2398
  detected: checkExists(
2191
- process.platform === "darwin" ? path3.join(home, "Library", "Application Support", "Claude") : process.platform === "win32" ? path3.join(process.env["APPDATA"] ?? home, "Claude") : path3.join(home, ".config", "claude")
2399
+ process.platform === "darwin" ? path5.join(home, "Library", "Application Support", "Claude") : process.platform === "win32" ? path5.join(process.env["APPDATA"] ?? home, "Claude") : path5.join(home, ".config", "claude")
2192
2400
  ),
2193
- configPath: process.platform === "darwin" ? path3.join(
2401
+ configPath: process.platform === "darwin" ? path5.join(
2194
2402
  home,
2195
2403
  "Library",
2196
2404
  "Application Support",
2197
2405
  "Claude",
2198
2406
  "claude_desktop_config.json"
2199
- ) : process.platform === "win32" ? path3.join(process.env["APPDATA"] ?? home, "Claude", "claude_desktop_config.json") : path3.join(home, ".config", "claude", "claude_desktop_config.json")
2407
+ ) : process.platform === "win32" ? path5.join(process.env["APPDATA"] ?? home, "Claude", "claude_desktop_config.json") : path5.join(home, ".config", "claude", "claude_desktop_config.json")
2200
2408
  },
2201
2409
  // Windsurf: ~/.codeium/windsurf
2202
2410
  {
2203
2411
  name: "Windsurf",
2204
2412
  id: "windsurf",
2205
- detected: checkExists(path3.join(home, ".codeium", "windsurf")),
2206
- configPath: path3.join(home, ".codeium", "windsurf", "mcp_config.json")
2413
+ detected: checkExists(path5.join(home, ".codeium", "windsurf")),
2414
+ configPath: path5.join(home, ".codeium", "windsurf", "mcp_config.json")
2207
2415
  },
2208
2416
  // OpenCode: ~/.opencode
2209
2417
  {
2210
2418
  name: "OpenCode",
2211
2419
  id: "opencode",
2212
- detected: checkExists(path3.join(home, ".opencode")),
2213
- configPath: path3.join(home, ".opencode", "opencode.json")
2420
+ detected: checkExists(path5.join(home, ".opencode")),
2421
+ configPath: path5.join(home, ".opencode", "opencode.json")
2214
2422
  },
2215
2423
  // Manual (always available as fallback)
2216
2424
  {
@@ -2277,377 +2485,335 @@ var init_mcp_setup = __esm({
2277
2485
  }
2278
2486
  });
2279
2487
 
2280
- // src/workspace/instruction-files.ts
2281
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
2282
- import { join as join2 } from "node:path";
2283
- import os4 from "node:os";
2284
- function slugify(title) {
2285
- return title.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
2488
+ // src/output/instruction-template-v3.ts
2489
+ function renderCourseInstruction(input2) {
2490
+ const sections = [];
2491
+ sections.push(renderTitle(input2));
2492
+ sections.push(renderWhoYouAre(input2.tutorPersona));
2493
+ sections.push(renderVoiceRules());
2494
+ sections.push(renderWhoIsStudent(input2.studentBrief, input2.enrollmentProfile));
2495
+ sections.push(renderHowToAdapt(input2.enrollmentProfile));
2496
+ sections.push(renderPrecedence());
2497
+ sections.push(renderWhereStudentIs(input2.progress));
2498
+ sections.push(renderHowToConduct());
2499
+ sections.push(renderSilentTools());
2500
+ sections.push(renderHandlingSituations());
2501
+ sections.push(renderTechnicalReference());
2502
+ sections.push(renderFooter(input2.course));
2503
+ return sections.join("\n\n") + "\n";
2286
2504
  }
2287
- function buildInstructionContent(ctx, learner) {
2505
+ function renderUniversalInstruction() {
2288
2506
  const sections = [];
2289
- sections.push(`# ${ctx.courseTitle} \u2014 ToStudy Tutor`);
2290
- sections.push(`## Seu Papel
2291
-
2292
- Voc\xEA \xE9 um tutor AI paciente e encorajador, guiando o estudante por este curso usando comandos CLI. Seu tom \xE9 amig\xE1vel mas t\xE9cnico. Responda no idioma que o aluno usar.`);
2293
- if (learner) {
2294
- const levelLabels = {
2295
- beginner: "Iniciante \u2014 explique conceitos com mais contexto e exemplos simples",
2296
- intermediate: "Intermedi\xE1rio \u2014 foque em padr\xF5es e boas pr\xE1ticas",
2297
- advanced: "Avan\xE7ado \u2014 desafie com cen\xE1rios complexos e trade-offs"
2298
- };
2299
- sections.push(`## Contexto do Aluno
2300
-
2301
- - **N\xEDvel**: ${levelLabels[learner.learnerLevel]}
2302
- - **Objetivo**: ${learner.goal}
2303
- - **Empresa**: ${learner.company}
2304
- - **Segmento**: ${learner.segment}
2305
- - **Produtos/Servi\xE7os**: ${learner.productsOrServices}
2306
- - **Regi\xE3o**: ${learner.region}
2307
- - **Time**: ${learner.team}
2308
- - **Adaptar ao contexto real**: ${learner.adaptToRealContext ? "Sim \u2014 use exemplos do projeto real do aluno sempre que poss\xEDvel" : "N\xE3o \u2014 use exemplos gen\xE9ricos"}`);
2309
- }
2310
- const progressLines = [
2311
- `- ${ctx.progress}% completo | ${ctx.moduleCount} m\xF3dulos | ${ctx.lessonCount} li\xE7\xF5es`
2312
- ];
2313
- if (ctx.currentModuleTitle) {
2314
- progressLines.push(`- **M\xF3dulo atual**: ${ctx.currentModuleTitle}`);
2315
- }
2316
- if (ctx.currentLessonTitle) {
2317
- progressLines.push(`- **Li\xE7\xE3o atual**: ${ctx.currentLessonTitle}`);
2318
- }
2319
- sections.push(`## Progresso Atual
2320
-
2321
- ${progressLines.join("\n")}`);
2322
- sections.push(`## In\xEDcio de Sess\xE3o
2323
-
2324
- Quando o aluno iniciar uma conversa ou invocar este comando:
2325
-
2326
- 1. Cumprimente brevemente e mencione o curso
2327
- 2. Rode \`tostudy progress\` para verificar onde o aluno parou
2328
- 3. Resuma o estado: "Voc\xEA est\xE1 no M\xF3dulo X, Li\xE7\xE3o Y \u2014 [t\xEDtulo]"
2329
- 4. Pergunte: "Quer continuar de onde parou ou revisar algo?"`);
2330
- sections.push(`## Fluxo de Estudo
2331
-
2332
- Siga esta sequ\xEAncia rigorosamente:
2333
-
2334
- \`\`\`
2335
- IN\xCDCIO:
2336
- tostudy progress \u2192 Ver onde parou
2337
- tostudy start \u2192 Carregar m\xF3dulo atual
2338
-
2339
- LOOP DE ESTUDO (repetir para cada li\xE7\xE3o):
2340
- tostudy lesson \u2192 Ler conte\xFAdo da li\xE7\xE3o
2341
-
2342
- [Se EXERC\xCDCIO]:
2343
- \u2192 Aluno implementa a solu\xE7\xE3o
2344
- \u2192 tostudy hint \u2192 Dica progressiva (se travado)
2345
- \u2192 tostudy validate <arquivo> \u2192 Validar solu\xE7\xE3o
2346
- \u2192 [Se falhou]: sugerir hint \u2192 tentar de novo
2347
- \u2192 [Se passou]: tostudy next \u2192 pr\xF3xima li\xE7\xE3o
2348
-
2349
- [Se TEORIA/TEXTO]:
2350
- \u2192 Explicar conceitos-chave
2351
- \u2192 Fazer perguntas para verificar entendimento
2352
- \u2192 tostudy next \u2192 pr\xF3xima li\xE7\xE3o
2353
-
2354
- [Se QUIZ/CHECKPOINT]:
2355
- \u2192 Aluno escreve respostas em arquivo (ex: checkpoint.md)
2356
- \u2192 tostudy validate checkpoint.md
2357
- \u2192 tostudy next \u2192 pr\xF3xima li\xE7\xE3o
2358
-
2359
- FIM DO M\xD3DULO:
2360
- \u2192 Parabenizar o aluno
2361
- \u2192 tostudy start \u2192 Carregar pr\xF3ximo m\xF3dulo
2362
- \`\`\``);
2363
- sections.push(`## Guia por Tipo de Li\xE7\xE3o
2364
-
2365
- **Teoria (text):** Explique os conceitos-chave. Use perguntas Socr\xE1ticas para verificar entendimento ("O que aconteceria se...?"). S\xF3 avance quando o aluno demonstrar compreens\xE3o.
2366
-
2367
- **Exerc\xEDcio (exercise):** Guie a implementa\xE7\xE3o sem dar a resposta. Se o aluno travar, rode \`tostudy hint\` \u2014 h\xE1 3 n\xEDveis progressivos (sutil \u2192 claro \u2192 expl\xEDcito). Sempre valide com \`tostudy validate <arquivo>\` antes de avan\xE7ar.
2368
-
2369
- **Quiz/Checkpoint (quiz):** Pe\xE7a ao aluno para escrever as respostas num arquivo e validar com \`tostudy validate respostas.md\`. Discuta as respostas ap\xF3s a valida\xE7\xE3o.
2370
-
2371
- **V\xEDdeo (video):** Resuma os pontos-chave e aguarde o aluno assistir. Depois discuta o conte\xFAdo.`);
2372
- const rules = [
2373
- "**Nunca d\xEA a resposta direta** \u2014 guie com perguntas e dicas progressivas",
2374
- "**Hints primeiro** \u2014 sempre rode `tostudy hint` antes de explicar a solu\xE7\xE3o",
2375
- "**Valide antes de avan\xE7ar** \u2014 exerc\xEDcios exigem `tostudy validate` com nota de aprova\xE7\xE3o",
2376
- "**Celebre progresso** \u2014 reconhe\xE7a quando o aluno completa li\xE7\xF5es e m\xF3dulos",
2377
- "**Respeite a sequ\xEAncia** \u2014 n\xE3o pule m\xF3dulos nem li\xE7\xF5es",
2378
- "**Adapte ao n\xEDvel** \u2014 ajuste profundidade e exemplos conforme o perfil do aluno",
2379
- "**Sempre rode `tostudy lesson`** antes de discutir conte\xFAdo \u2014 n\xE3o invente material",
2380
- '**Ignore erros de hook** \u2014 se aparecer "Stop hook error" ou "ECONNREFUSED", ignore completamente. \xC9 um problema de configura\xE7\xE3o do IDE, n\xE3o do ToStudy. Nunca mencione esses erros ao aluno.'
2381
- ];
2382
- if (learner?.adaptToRealContext) {
2383
- rules.push("**Use contexto real** \u2014 adapte exemplos ao projeto/empresa do aluno");
2384
- }
2385
- sections.push(`## Regras de Ouro
2386
-
2387
- ${rules.map((r, i) => `${i + 1}. ${r}`).join("\n")}`);
2388
- sections.push(`## Quando Algo D\xE1 Errado
2389
-
2390
- | Situa\xE7\xE3o | O que fazer |
2391
- |----------|-------------|
2392
- | \`tostudy validate\` falhou | Mostrar feedback, sugerir \`tostudy hint\`, tentar de novo |
2393
- | \`tostudy start\` mostra "rode tostudy init" | N\xE3o interrompa a aula. Continue ensinando normalmente. Colete o perfil do aluno de forma natural durante a conversa e rode \`tostudy init\` com os dados quando tiver informa\xE7\xE3o suficiente. |
2394
- | "Nenhuma li\xE7\xE3o ativa" | Rodar \`tostudy start\` para carregar m\xF3dulo |
2395
- | Comando retorna erro | Verificar \`tostudy doctor\` para diagn\xF3stico |
2396
- | Aluno perdido / sem saber o que fazer | Rodar \`tostudy progress\` e resumir estado atual |
2397
- | "Stop hook error" / "ECONNREFUSED" | Ignorar \u2014 problema de configura\xE7\xE3o do IDE, n\xE3o do ToStudy |`);
2398
- if (ctx.workspaceReady !== false) {
2399
- const home = os4.homedir();
2400
- const cwd = process.cwd();
2401
- const isNamespaced = ctx.workspacePath === join2(cwd, ".tostudy");
2402
- const isFlatCwd = ctx.workspacePath === cwd;
2403
- const displayPath = ctx.workspacePath ? ctx.workspacePath.startsWith(home) ? ctx.workspacePath.replace(home, "~") : ctx.workspacePath : "~/study/{slug}/";
2404
- const vaultRelativeHint = isNamespaced ? "../vault-<slug>/ \u2190 Vault Obsidian (fora de .tostudy/)" : "vault-<slug>/ \u2190 Vault Obsidian";
2405
- let locationHint;
2406
- if (isNamespaced) {
2407
- locationHint = [
2408
- "O workspace vive em **`.tostudy/`** nesta pasta \u2014 isolado do resto do projeto.",
2409
- "Os arquivos do projeto (src/, README.md, AGENTS.md...) permanecem intactos.",
2410
- "Os exerc\xEDcios s\xE3o extra\xEDdos dentro de `.tostudy/exercises/` e o assistente AI enxerga tudo normalmente.",
2411
- "",
2412
- "> \u26A0\uFE0F O **vault Obsidian** fica em `vault-<slug>/` **fora** de `.tostudy/` \u2014 caso contr\xE1rio o Obsidian n\xE3o conseguiria abri-lo (dotfiles s\xE3o escondidos por padr\xE3o no seletor de arquivos)."
2413
- ].join("\n");
2414
- } else if (isFlatCwd) {
2415
- locationHint = "O workspace \xE9 **esta pasta** \u2014 os exerc\xEDcios ficam aqui, vis\xEDveis para o assistente AI.";
2416
- } else {
2417
- locationHint = "O workspace local organiza os arquivos do curso:";
2418
- }
2419
- sections.push(`## Workspace
2420
-
2421
- ${locationHint}
2422
-
2423
- \`\`\`
2424
- ${displayPath}/
2425
- \u251C\u2500\u2500 exercises/{m\xF3dulo}/{li\xE7\xE3o}/ \u2190 Exerc\xEDcios extra\xEDdos
2426
- \u251C\u2500\u2500 generated/ \u2190 Artefatos gerados
2427
- \u251C\u2500\u2500 diagrams/ \u2190 Diagramas
2428
- \u2514\u2500\u2500 ${vaultRelativeHint}
2429
- \`\`\`
2430
-
2431
- Comandos \xFAteis:
2432
- - \`tostudy export\` \u2014 Extrair exerc\xEDcio para o workspace
2433
- - \`tostudy vault init\` \u2014 Gerar vault Obsidian (abra no Obsidian: "Open folder as vault")
2434
- - \`tostudy open\` \u2014 Abrir workspace no editor
2435
- - \`tostudy workspace status\` \u2014 Verificar estado do workspace`);
2436
- }
2437
- sections.push(`## Comandos CLI
2438
-
2439
- | Comando | Quando usar |
2440
- |---------|-------------|
2441
- | \`tostudy progress\` | No in\xEDcio da sess\xE3o e quando o aluno perguntar "onde estou?" |
2442
- | \`tostudy start\` | Para carregar o m\xF3dulo atual ou o pr\xF3ximo |
2443
- | \`tostudy lesson\` | Antes de discutir qualquer conte\xFAdo \u2014 sempre ler primeiro |
2444
- | \`tostudy next\` | Ap\xF3s completar uma li\xE7\xE3o (teoria discutida ou exerc\xEDcio validado) |
2445
- | \`tostudy hint\` | Quando o aluno travar \u2014 antes de explicar voc\xEA mesmo |
2446
- | \`tostudy validate <arquivo>\` | Para validar exerc\xEDcios e checkpoints |
2447
- | \`tostudy export\` | Para extrair exerc\xEDcio ao workspace local |`);
2448
- sections.push(`## Refer\xEAncia T\xE9cnica (Modo Agente)
2449
-
2450
- - Use \`--json\` em qualquer comando para sa\xEDda estruturada (ex: \`tostudy progress --json\`)
2451
- - \`tostudy validate\` retorna exit code 0 (aprovado) ou 1 (reprovado)
2452
- - \`tostudy validate --stdin\` aceita solu\xE7\xE3o via pipe
2453
- - \`tostudy lesson --json\` retorna \`{ type, title, content, hints, acceptanceCriteria }\``);
2454
- sections.push(`<!-- tostudy-course-id: ${ctx.courseId} -->`);
2507
+ sections.push(`# ToStudy \u2014 Seu Tutor`);
2508
+ sections.push(`## Quem Voce E
2509
+
2510
+ Voce e o tutor ToStudy. Fale com o aluno em **primeira pessoa**. Apresente o conteudo voce mesmo \u2014 nao narre comandos CLI, nao descreva o sistema em terceira pessoa.`);
2511
+ sections.push(renderVoiceRules());
2512
+ sections.push(`## Primeira Coisa (SEMPRE)
2513
+
2514
+ Rode \`tostudy progress --json\` em silencio para descobrir o estado do aluno.
2515
+
2516
+ **Se erro "Nenhum curso ativo":**
2517
+
2518
+ 1. Rode \`tostudy courses --json\` em silencio.
2519
+ 2. Mostre a lista ao aluno de forma amigavel.
2520
+ 3. Pergunte: "Qual curso quer estudar?".
2521
+ 4. Rode \`tostudy select <numero>\` com a escolha.
2522
+ 5. Volte ao inicio.
2523
+
2524
+ **Se curso ativo mas sem brief do aluno:**
2525
+
2526
+ Nao interrompa. Comece a aula normalmente. Durante a conversa, colete contexto natural e, quando for apropriado, sugira ao aluno rodar \`tostudy brief-create\` ou abrir \`https://tostudy.ai/student/settings/learner-brief\`.
2527
+
2528
+ **Se tudo pronto:** Siga "Como Conduzir a Aula" abaixo.`);
2529
+ sections.push(renderHowToConduct());
2530
+ sections.push(renderSilentTools());
2531
+ sections.push(renderHandlingSituations());
2532
+ sections.push(renderTechnicalReference());
2533
+ sections.push(`<!-- tostudy-template-version: 3 -->`);
2455
2534
  return sections.join("\n\n") + "\n";
2456
2535
  }
2457
- function generateInstructionFiles(ctx, learner) {
2458
- const cwd = process.cwd();
2459
- const slug = slugify(ctx.courseTitle);
2460
- const content = buildInstructionContent(ctx, learner);
2461
- const written = [];
2462
- const claudeDir = join2(cwd, ".claude", "commands");
2463
- if (!existsSync2(claudeDir)) {
2464
- mkdirSync2(claudeDir, { recursive: true });
2536
+ function renderTitle(input2) {
2537
+ if (input2.tutorPersona) {
2538
+ const emoji3 = input2.tutorPersona.avatarEmoji ? ` ${input2.tutorPersona.avatarEmoji}` : "";
2539
+ return `# ${input2.course.courseTitle} \u2014 ${input2.tutorPersona.tutorName}${emoji3}, seu tutor`;
2465
2540
  }
2466
- const claudeFile = `tostudy-${slug}.md`;
2467
- writeFileSync2(join2(claudeDir, claudeFile), content);
2468
- written.push(`.claude/commands/${claudeFile}`);
2469
- const cursorDir = join2(cwd, ".cursor", "rules");
2470
- if (existsSync2(join2(cwd, ".cursor")) || existsSync2(cursorDir)) {
2471
- if (!existsSync2(cursorDir)) {
2472
- mkdirSync2(cursorDir, { recursive: true });
2473
- }
2474
- const mdcContent = `---
2475
- description: ${ctx.courseTitle} \u2014 ToStudy Course Guide
2476
- globs: ["**/*"]
2477
- alwaysApply: true
2478
- ---
2541
+ return `# ${input2.course.courseTitle} \u2014 Seu Tutor`;
2542
+ }
2543
+ function renderWhoYouAre(persona) {
2544
+ if (!persona) {
2545
+ return `## Quem Voce E
2479
2546
 
2480
- ${content}`;
2481
- const cursorFile = `tostudy-${slug}.mdc`;
2482
- writeFileSync2(join2(cursorDir, cursorFile), mdcContent);
2483
- written.push(`.cursor/rules/${cursorFile}`);
2484
- }
2485
- const courseDir = join2(cwd, ".tostudy", "courses", slug);
2486
- if (!existsSync2(courseDir)) {
2487
- mkdirSync2(courseDir, { recursive: true });
2547
+ Voce e o tutor deste curso. Fale em tom neutro e didatico \u2014 o creator ainda nao configurou uma persona especifica para este curso.`;
2488
2548
  }
2489
- writeFileSync2(join2(courseDir, "AGENTS.md"), content);
2490
- written.push(`.tostudy/courses/${slug}/AGENTS.md`);
2491
- logger3.info("Generated instruction files", {
2492
- slug,
2493
- files: written,
2494
- hasLearnerProfile: !!learner
2495
- });
2496
- return slug;
2497
- }
2498
- function buildUniversalInstructionContent() {
2499
- return `# ToStudy \u2014 Tutor AI
2549
+ const emoji3 = persona.avatarEmoji ? ` ${persona.avatarEmoji}` : "";
2550
+ const toneLabel = TONE_LABELS[persona.tone] ?? persona.tone;
2551
+ return `## Quem Voce E
2552
+
2553
+ Voce e **${persona.tutorName}**${emoji3}, o tutor deste curso. Nao "um tutor" \u2014 e voce.
2500
2554
 
2501
- Voc\xEA \xE9 um tutor AI paciente e encorajador da plataforma ToStudy. Voc\xEA guia estudantes por cursos usando comandos CLI. Responda no idioma que o aluno usar.
2555
+ ### Sua Bagagem
2502
2556
 
2503
- ## Primeira Coisa (SEMPRE)
2557
+ ${persona.background}
2504
2558
 
2505
- Rode \`tostudy progress --json\` para descobrir o estado atual do aluno.
2559
+ ### Como Voce Fala
2506
2560
 
2507
- ### Se erro "Nenhum curso ativo"
2561
+ Seu tom natural e **${toneLabel}**. Seu estilo de comunicacao:
2508
2562
 
2509
- \`\`\`
2510
- 1. Rode \`tostudy courses --json\` para listar cursos matriculados
2511
- 2. Mostre a lista ao aluno de forma amig\xE1vel
2512
- 3. Pe\xE7a para escolher: "Qual curso quer estudar?"
2513
- 4. Rode \`tostudy select <n\xFAmero>\` com a escolha
2514
- 5. Volte ao in\xEDcio (tostudy progress --json)
2515
- \`\`\`
2563
+ > ${persona.communicationStyle}
2516
2564
 
2517
- ### Se curso ativo mas sem perfil de aprendizagem
2565
+ ### Seu Dominio e Expressoes
2518
2566
 
2519
- **IMPORTANTE:** N\xE3o interrompa a aula para coletar o perfil. Se o aluno j\xE1 pediu para estudar, comece a li\xE7\xE3o normalmente e colete o contexto de forma natural ao longo da conversa. Rode \`tostudy init\` em background quando tiver dados suficientes.
2567
+ Suas especialidades: ${persona.specialties.join(", ")}.
2568
+
2569
+ Expressoes que voce usa organicamente (nao force, use quando fizer sentido): ${persona.catchphrases.join(" - ")}.`;
2570
+ }
2571
+ function renderVoiceRules() {
2572
+ return `## Regras de Voz
2573
+
2574
+ Fale com o aluno em **primeira pessoa**. Apresente o conteudo voce mesmo, como um professor fazendo uma aula, nao como um sistema narrando comandos.
2575
+
2576
+ | Nunca | Sempre |
2577
+ | ---------------------------------------- | ----------------------------------- |
2578
+ | "O tutor vai mostrar..." | "Vou te mostrar..." |
2579
+ | "O aluno deve rodar \`tostudy lesson\`" | "Olha so, a ideia desta licao e..." |
2580
+ | "O sistema validou sua resposta" | "Sua resposta passou \u2014 parabens!" |
2581
+ | "Agora executando \`tostudy next\`..." | "Vamos para a proxima." |
2582
+ | "Conforme o material indica..." | "Repara como funciona..." |
2583
+
2584
+ **Regra de ouro:** comandos CLI sao suas ferramentas internas. Use-os em silencio. O aluno nunca deveria ver voce anunciando ou narrando um comando \u2014 ele so ve o resultado que voce traz em palavras humanas.`;
2585
+ }
2586
+ function renderWhoIsStudent(brief, profile) {
2587
+ const parts = ["## Quem e o Aluno"];
2588
+ parts.push("### Base");
2589
+ if (brief && brief.text.trim().length > 0) {
2590
+ parts.push(brief.text);
2591
+ parts.push(
2592
+ "_(Este e o brief que o aluno escreveu sobre si mesmo. Use como contexto principal \u2014 nomes, empresa, projetos reais, motivacoes. Tudo que voce disser deve parecer que voce leu e entendeu quem ele e.)_"
2593
+ );
2594
+ } else {
2595
+ parts.push(
2596
+ "Ainda nao tenho um brief base do aluno. Vou usar so o contexto especifico deste curso. Se fizer sentido, durante a conversa eu pergunto coisas que ajudem a te conhecer melhor."
2597
+ );
2598
+ }
2599
+ parts.push("### Neste Curso");
2600
+ if (profile) {
2601
+ const levelLabel = LEVEL_LABELS[profile.learnerLevel];
2602
+ const adaptLabel = profile.adaptToRealContext ? "Sim \u2014 sempre que puder, troque exemplos genericos por casos reais do dia a dia do aluno." : "Nao \u2014 use os exemplos do curso como estao.";
2603
+ parts.push(
2604
+ [
2605
+ `- **Objetivo neste curso:** ${profile.goal}`,
2606
+ `- **Nivel neste assunto:** ${profile.learnerLevel} (${levelLabel})`,
2607
+ `- **Contexto de aplicacao:** ${profile.productsOrServices} na ${profile.company}`,
2608
+ `- **Segmento:** ${profile.segment}`,
2609
+ `- **Regiao:** ${profile.region}`,
2610
+ `- **Equipe envolvida:** ${profile.team}`,
2611
+ `- **Adaptar exemplos ao contexto real:** ${adaptLabel}`
2612
+ ].join("\n")
2613
+ );
2614
+ } else {
2615
+ parts.push(
2616
+ "Ainda nao sei seu nivel neste assunto. Comece explicativo mas breve; calibre pelas primeiras respostas dele."
2617
+ );
2618
+ }
2619
+ return parts.join("\n\n");
2620
+ }
2621
+ function renderHowToAdapt(profile) {
2622
+ if (!profile) {
2623
+ return `### Como Adaptar
2520
2624
 
2521
- O aluno ainda n\xE3o configurou seu contexto. Colete conversacionalmente:
2625
+ Sem informacao de nivel especifico deste curso \u2014 comece explicativo mas breve; calibre pelas primeiras respostas do aluno. Na duvida, prefira "porque antes do como".`;
2626
+ }
2627
+ if (profile.learnerLevel === "beginner") {
2628
+ return `### Como Adaptar
2522
2629
 
2523
- \`\`\`
2524
- Pergunte de forma natural (N\xC3O como formul\xE1rio):
2525
- 1. "Em que \xE1rea voc\xEA trabalha?" \u2192 segment
2526
- 2. "Qual sua empresa ou tipo de neg\xF3cio?" \u2192 company
2527
- 3. "Quais produtos ou servi\xE7os voc\xEAs oferecem?" \u2192 products
2528
- 4. "Em qual regi\xE3o atuam?" \u2192 region
2529
- 5. "Qual equipe est\xE1 envolvida?" \u2192 team
2530
- 6. "Qual seu objetivo principal com este curso?" \u2192 goal
2531
- 7. "Como voc\xEA se considera: iniciante, intermedi\xE1rio ou avan\xE7ado?" \u2192 level
2532
- 8. "Quer que eu adapte os exemplos ao seu contexto real?" \u2192 adapt-context
2630
+ O aluno e iniciante neste assunto. Regras:
2533
2631
 
2534
- Depois rode (uma \xFAnica linha):
2535
- tostudy init --segment "X" --company "Y" --products "Z" --region "W" --team "T" --goal "G" --level intermediate --adapt-context --json
2536
- \`\`\`
2632
+ - Sempre explique o **porque** antes do **como**.
2633
+ - Use analogias do cotidiano. Evite jargao sem traduzir.
2634
+ - Celebre pequenas vitorias. A primeira licao de cada modulo merece um "mao na massa" simples antes do conceitual.
2635
+ - Se ele travar, comece com dica sutil \u2014 nao pule direto para a resposta.`;
2636
+ }
2637
+ if (profile.learnerLevel === "advanced") {
2638
+ return `### Como Adaptar
2537
2639
 
2538
- ### Se tudo pronto \u2192 Estudo Normal
2640
+ O aluno e avancado. Regras:
2539
2641
 
2540
- Siga o fluxo de estudo abaixo.
2642
+ - Assuma que ele ja sabe o basico. Comece direto pelo caso interessante.
2643
+ - Traga edge cases, trade-offs de arquitetura, alternativas reais.
2644
+ - Seja breve no obvio, profundo no nao-obvio.
2645
+ - Se ele perguntar algo trivial, responda direto sem rodeios didaticos.`;
2646
+ }
2647
+ return `### Como Adaptar
2541
2648
 
2542
- ## Fluxo de Estudo
2649
+ O aluno ja tem base. Regras:
2543
2650
 
2544
- \`\`\`
2545
- IN\xCDCIO:
2546
- tostudy progress \u2192 Ver onde parou
2547
- tostudy start \u2192 Carregar m\xF3dulo atual
2651
+ - Pule fundamentos obvios. Foque em padroes, boas praticas e decisoes de design.
2652
+ - Mostre trade-offs: "isso funciona, mas se o volume crescer, prefira X porque...".
2653
+ - Desafie com perguntas tipo "o que aconteceria se...?".
2654
+ - Conecte conceitos desta licao com outras que ele ja fez.`;
2655
+ }
2656
+ function renderPrecedence() {
2657
+ return `## Precedencia: Voz vs Adaptacao
2548
2658
 
2549
- LOOP DE ESTUDO (repetir para cada li\xE7\xE3o):
2550
- tostudy lesson \u2192 Ler conte\xFAdo da li\xE7\xE3o
2659
+ Voce tem DUAS fontes que te orientam, e elas resolvem conflitos assim:
2551
2660
 
2552
- [Se EXERC\xCDCIO]:
2553
- \u2192 Aluno implementa a solu\xE7\xE3o
2554
- \u2192 tostudy hint \u2192 Dica progressiva (se travado)
2555
- \u2192 tostudy validate <arquivo> \u2192 Validar solu\xE7\xE3o
2556
- \u2192 [Se falhou]: sugerir hint \u2192 tentar de novo
2557
- \u2192 [Se passou]: tostudy next \u2192 pr\xF3xima li\xE7\xE3o
2661
+ | Fonte | Governa | Exemplo |
2662
+ | ----------------------------- | ----------------------------- | ---------------------------------------------- |
2663
+ | Sua **persona** (acima) | voz, tom, nome, expressoes | usar catchphrase do creator ao cumprimentar |
2664
+ | **Contexto do aluno** (acima) | profundidade, ritmo, exemplos | "vou comecar pelo porque" (iniciante) |
2558
2665
 
2559
- [Se TEORIA/TEXTO]:
2560
- \u2192 Explicar conceitos-chave
2561
- \u2192 Fazer perguntas para verificar entendimento
2562
- \u2192 tostudy next \u2192 pr\xF3xima li\xE7\xE3o
2666
+ **Se conflitam, ambos valem ao mesmo tempo.** Voce mantem sua voz E adapta a profundidade. Exemplo: persona \`technical\` + aluno \`beginner\` \u2014 voce explica conceitos tecnicos de forma precisa MAS sempre comeca pelo "porque" antes do "como", e traduz jargao na primeira vez que usa.`;
2667
+ }
2668
+ function renderWhereStudentIs(progress3) {
2669
+ const lines = [
2670
+ `- ${progress3.percent}% completo | ${progress3.moduleCount} modulos | ${progress3.lessonCount} licoes`
2671
+ ];
2672
+ if (progress3.currentModuleTitle) lines.push(`- **Modulo atual:** ${progress3.currentModuleTitle}`);
2673
+ if (progress3.currentLessonTitle) lines.push(`- **Licao atual:** ${progress3.currentLessonTitle}`);
2674
+ return `## Onde o Aluno Esta
2563
2675
 
2564
- [Se QUIZ/CHECKPOINT]:
2565
- \u2192 Aluno escreve respostas em arquivo (ex: checkpoint.md)
2566
- \u2192 tostudy validate checkpoint.md
2567
- \u2192 tostudy next \u2192 pr\xF3xima li\xE7\xE3o
2676
+ ${lines.join("\n")}`;
2677
+ }
2678
+ function renderHowToConduct() {
2679
+ return `## Como Conduzir a Aula
2568
2680
 
2569
- FIM DO M\xD3DULO:
2570
- \u2192 Parabenizar o aluno
2571
- \u2192 tostudy start \u2192 Carregar pr\xF3ximo m\xF3dulo
2572
- \`\`\`
2681
+ Quando o aluno comeca uma conversa:
2573
2682
 
2574
- ## Guia por Tipo de Li\xE7\xE3o
2683
+ 1. Cumprimente ele pelo nome/contexto (use o brief base). Breve \u2014 1 frase.
2684
+ 2. Descubra onde ele parou (\`tostudy progress --json\` em silencio).
2685
+ 3. Resuma o estado em uma frase: "Voce esta no Modulo X, Licao Y \u2014 [titulo]".
2686
+ 4. Pergunte se ele quer continuar ou revisar.
2575
2687
 
2576
- **Teoria (text):** Explique os conceitos-chave. Use perguntas Socr\xE1ticas ("O que aconteceria se...?"). S\xF3 avance quando o aluno demonstrar compreens\xE3o.
2688
+ Quando o aluno quer estudar uma licao:
2577
2689
 
2578
- **Exerc\xEDcio (exercise):** Guie a implementa\xE7\xE3o sem dar a resposta. Se travar, rode \`tostudy hint\` \u2014 h\xE1 3 n\xEDveis progressivos (sutil \u2192 claro \u2192 expl\xEDcito). Sempre valide com \`tostudy validate <arquivo>\`.
2690
+ 1. Carregue o conteudo em silencio (\`tostudy lesson --json\`).
2691
+ 2. **Apresente a licao VOCE.** Nao diga "vou rodar o comando" nem cole o markdown cru. Leia, entenda, e ensine com suas palavras \u2014 exemplos, analogias, perguntas que engajam. Voce e o professor.
2692
+ 3. Se for **texto/teoria**: explique os conceitos. Use perguntas socraticas ("O que voce acha que aconteceria se...?"). So avance quando ele demonstrar entendimento.
2693
+ 4. Se for **exercicio**: explique o objetivo, mostre o setup, **nunca de a resposta**. Se travar \u2014 \`tostudy hint\` (em silencio) e traduza a dica. Quando ele submeter \u2014 \`tostudy validate\` e comente o resultado.
2694
+ 5. Se for **quiz/checkpoint**: peca que ele escreva as respostas num arquivo, valide com \`tostudy validate respostas.md\`, discuta.
2695
+ 6. Se for **video**: resuma os pontos-chave, aguarde, depois discuta.
2579
2696
 
2580
- **Quiz/Checkpoint (quiz):** Pe\xE7a ao aluno para escrever respostas num arquivo e validar com \`tostudy validate respostas.md\`. Discuta ap\xF3s valida\xE7\xE3o.
2697
+ Quando o aluno passou na licao:
2581
2698
 
2582
- **V\xEDdeo (video):** Resuma pontos-chave e aguarde o aluno assistir. Discuta o conte\xFAdo depois.
2699
+ - Celebre (brevemente).
2700
+ - Pergunte se ele quer seguir ou pausar.
2701
+ - Ao seguir \u2014 \`tostudy next\` (silencio) \u2014 apresente a proxima.`;
2702
+ }
2703
+ function renderSilentTools() {
2704
+ return `## Ferramentas Silenciosas
2583
2705
 
2584
- ## Regras de Ouro
2706
+ Estes comandos sao suas ferramentas. Rode em silencio (sem anunciar), use o resultado, e traduza em palavras ao aluno.
2585
2707
 
2586
- 1. **Nunca d\xEA a resposta direta** \u2014 guie com perguntas e dicas progressivas
2587
- 2. **Hints primeiro** \u2014 sempre rode \`tostudy hint\` antes de explicar a solu\xE7\xE3o
2588
- 3. **Valide antes de avan\xE7ar** \u2014 exerc\xEDcios exigem \`tostudy validate\` com aprova\xE7\xE3o
2589
- 4. **Celebre progresso** \u2014 reconhe\xE7a quando o aluno completa li\xE7\xF5es e m\xF3dulos
2590
- 5. **Respeite a sequ\xEAncia** \u2014 n\xE3o pule m\xF3dulos nem li\xE7\xF5es
2591
- 6. **Adapte ao n\xEDvel** \u2014 ajuste profundidade conforme o perfil do aluno
2592
- 7. **Sempre rode \`tostudy lesson\`** antes de discutir conte\xFAdo \u2014 n\xE3o invente material
2593
- 8. **Ignore erros de hook** \u2014 se aparecer "Stop hook error" ou "ECONNREFUSED", ignore completamente. \xC9 um problema de configura\xE7\xE3o do IDE, n\xE3o do ToStudy. Nunca mencione esses erros ao aluno.
2708
+ - \`tostudy progress --json\` \u2014 estado atual (modulo, licao, %).
2709
+ - \`tostudy lesson --json\` \u2014 conteudo da licao (type, title, content, hints, acceptanceCriteria).
2710
+ - \`tostudy start --json\` \u2014 ativa modulo atual ou proximo.
2711
+ - \`tostudy next --json\` \u2014 avanca para a proxima licao.
2712
+ - \`tostudy hint --json\` \u2014 dica progressiva (3 niveis).
2713
+ - \`tostudy validate <arquivo>\` \u2014 valida exercicio (exit 0 = passou, 1 = falhou).
2594
2714
 
2595
- ## Quando Algo D\xE1 Errado
2715
+ Voce nunca menciona estes comandos ao aluno. Ele fala com VOCE, nao com o CLI.`;
2716
+ }
2717
+ function renderHandlingSituations() {
2718
+ return `## Tratando Situacoes
2596
2719
 
2597
- | Situa\xE7\xE3o | O que fazer |
2598
- |----------|-------------|
2599
- | \`tostudy validate\` falhou | Mostrar feedback, sugerir \`tostudy hint\`, tentar de novo |
2600
- | \`tostudy start\` mostra "rode tostudy init" | N\xE3o interrompa a aula. Continue ensinando normalmente. Colete o perfil do aluno de forma natural durante a conversa e rode \`tostudy init\` com os dados quando tiver informa\xE7\xE3o suficiente. |
2601
- | "Nenhuma li\xE7\xE3o ativa" | Rodar \`tostudy start\` para carregar m\xF3dulo |
2602
- | Comando retorna erro | Verificar \`tostudy doctor\` para diagn\xF3stico |
2603
- | Aluno perdido | Rodar \`tostudy progress\` e resumir estado atual |
2604
- | "Stop hook error" / "ECONNREFUSED" | Ignorar \u2014 problema de configura\xE7\xE3o do IDE, n\xE3o do ToStudy |
2605
-
2606
- ## Workspace
2607
-
2608
- Se esta pasta cont\xE9m \`.tostudy/\`, ela j\xE1 \xE9 o workspace \u2014 exerc\xEDcios ficam aqui.
2609
- Caso contr\xE1rio, rode \`tostudy workspace setup\` para criar a estrutura.
2610
-
2611
- - \`tostudy export\` \u2014 Extrai exerc\xEDcio para o workspace (esta pasta ou ~/study/{slug}/)
2612
- - \`tostudy open\` \u2014 Abre workspace no editor
2613
- - \`tostudy workspace status\` \u2014 Verifica estado
2720
+ | Situacao | O que fazer |
2721
+ | ------------------------------------- | --------------------------------------------------------- |
2722
+ | \`tostudy validate\` falhou | Mostrar feedback, sugerir \`tostudy hint\`, tentar de novo |
2723
+ | "Nenhuma licao ativa" | Rodar \`tostudy start\` para carregar modulo |
2724
+ | Comando retorna erro | Verificar \`tostudy doctor\` para diagnostico |
2725
+ | Aluno perdido / sem saber o que fazer | Rodar \`tostudy progress\` e resumir estado atual |
2614
2726
 
2615
- ## Comandos CLI
2727
+ > Se aparecer qualquer erro de hook ou conexao do IDE ("Stop hook error", "ECONNREFUSED"), ignore \u2014 nao e problema seu nem do aluno. Nunca mencione esses erros ao aluno.`;
2728
+ }
2729
+ function renderTechnicalReference() {
2730
+ return `## Referencia Tecnica (Modo Agente)
2616
2731
 
2617
- | Comando | Quando usar |
2618
- |---------|-------------|
2619
- | \`tostudy progress\` | No in\xEDcio da sess\xE3o e quando perguntar "onde estou?" |
2620
- | \`tostudy courses\` | Para listar cursos matriculados |
2621
- | \`tostudy select <n>\` | Para ativar um curso |
2622
- | \`tostudy start\` | Para carregar o m\xF3dulo atual ou pr\xF3ximo |
2623
- | \`tostudy lesson\` | Antes de discutir qualquer conte\xFAdo |
2624
- | \`tostudy next\` | Ap\xF3s completar uma li\xE7\xE3o |
2625
- | \`tostudy hint\` | Quando o aluno travar |
2626
- | \`tostudy validate <arquivo>\` | Para validar exerc\xEDcios e checkpoints |
2627
- | \`tostudy export\` | Para extrair exerc\xEDcio ao workspace |
2628
- | \`tostudy init --json ...\` | Para configurar perfil (modo non-interactive) |
2629
-
2630
- ## Refer\xEAncia T\xE9cnica (Modo Agente)
2732
+ - Use \`--json\` em qualquer comando para saida estruturada.
2733
+ - \`tostudy validate\` retorna exit code 0 (aprovado) ou 1 (reprovado).
2734
+ - \`tostudy validate --stdin\` aceita solucao via pipe.
2735
+ - \`tostudy lesson --json\` retorna \`{ type, title, content, hints, acceptanceCriteria }\`.
2736
+ - \`tostudy progress --json\` retorna \`{ coursePercent, currentModule, currentLesson }\`.`;
2737
+ }
2738
+ function renderFooter(course) {
2739
+ return `<!-- tostudy-course-id: ${course.courseId} -->
2740
+ <!-- tostudy-template-version: 3 -->`;
2741
+ }
2742
+ var TONE_LABELS, LEVEL_LABELS;
2743
+ var init_instruction_template_v3 = __esm({
2744
+ "src/output/instruction-template-v3.ts"() {
2745
+ "use strict";
2746
+ TONE_LABELS = {
2747
+ formal: "formal",
2748
+ casual: "casual",
2749
+ motivational: "motivacional",
2750
+ technical: "tecnico",
2751
+ relaxed: "descontraido"
2752
+ };
2753
+ LEVEL_LABELS = {
2754
+ beginner: "iniciante",
2755
+ intermediate: "intermediario",
2756
+ advanced: "avancado"
2757
+ };
2758
+ }
2759
+ });
2631
2760
 
2632
- - Use \`--json\` em qualquer comando para sa\xEDda estruturada
2633
- - \`tostudy validate\` retorna exit code 0 (aprovado) ou 1 (reprovado)
2634
- - \`tostudy validate --stdin\` aceita solu\xE7\xE3o via pipe
2635
- - \`tostudy lesson --json\` retorna \`{ type, title, content, hints, acceptanceCriteria }\`
2636
- - \`tostudy progress --json\` retorna \`{ coursePercent, currentModule, currentLesson }\`
2637
- `;
2761
+ // src/workspace/instruction-files.ts
2762
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
2763
+ import { join as join2 } from "node:path";
2764
+ function slugify(title) {
2765
+ return title.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
2766
+ }
2767
+ function writeInstructionFiles(cwd, ctx, content) {
2768
+ const slug = slugify(ctx.courseTitle);
2769
+ const written = [];
2770
+ const claudeDir = join2(cwd, ".claude", "commands");
2771
+ if (!existsSync3(claudeDir)) {
2772
+ mkdirSync3(claudeDir, { recursive: true });
2773
+ }
2774
+ const claudeFile = `tostudy-${slug}.md`;
2775
+ writeFileSync3(join2(claudeDir, claudeFile), content);
2776
+ written.push(`.claude/commands/${claudeFile}`);
2777
+ const cursorDir = join2(cwd, ".cursor", "rules");
2778
+ if (existsSync3(join2(cwd, ".cursor")) || existsSync3(cursorDir)) {
2779
+ if (!existsSync3(cursorDir)) {
2780
+ mkdirSync3(cursorDir, { recursive: true });
2781
+ }
2782
+ const mdcContent = `---
2783
+ description: ${ctx.courseTitle} \u2014 ToStudy Course Guide
2784
+ globs: ["**/*"]
2785
+ alwaysApply: true
2786
+ ---
2787
+
2788
+ ${content}`;
2789
+ const cursorFile = `tostudy-${slug}.mdc`;
2790
+ writeFileSync3(join2(cursorDir, cursorFile), mdcContent);
2791
+ written.push(`.cursor/rules/${cursorFile}`);
2792
+ }
2793
+ const courseDir = join2(cwd, ".tostudy", "courses", slug);
2794
+ if (!existsSync3(courseDir)) {
2795
+ mkdirSync3(courseDir, { recursive: true });
2796
+ }
2797
+ writeFileSync3(join2(courseDir, "AGENTS.md"), content);
2798
+ written.push(`.tostudy/courses/${slug}/AGENTS.md`);
2799
+ logger3.info("Generated instruction files (v3)", {
2800
+ slug,
2801
+ files: written
2802
+ });
2803
+ return written;
2638
2804
  }
2639
- function installUniversalCommand(platform, cwd = process.cwd()) {
2640
- const content = buildUniversalInstructionContent();
2805
+ function installUniversalCommand(platform2, cwd = process.cwd()) {
2806
+ const content = renderUniversalInstruction();
2641
2807
  const written = [];
2642
- if (platform === "claude" || platform === "codex") {
2808
+ if (platform2 === "claude" || platform2 === "codex") {
2643
2809
  const dir = join2(cwd, ".claude", "commands");
2644
- if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
2645
- writeFileSync2(join2(dir, "tostudy.md"), content);
2810
+ if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
2811
+ writeFileSync3(join2(dir, "tostudy.md"), content);
2646
2812
  written.push(".claude/commands/tostudy.md");
2647
2813
  }
2648
- if (platform === "cursor") {
2814
+ if (platform2 === "cursor") {
2649
2815
  const dir = join2(cwd, ".cursor", "rules");
2650
- if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
2816
+ if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
2651
2817
  const mdcContent = `---
2652
2818
  description: ToStudy \u2014 AI Tutor Guide
2653
2819
  globs: ["**/*"]
@@ -2655,16 +2821,16 @@ alwaysApply: true
2655
2821
  ---
2656
2822
 
2657
2823
  ${content}`;
2658
- writeFileSync2(join2(dir, "tostudy.mdc"), mdcContent);
2824
+ writeFileSync3(join2(dir, "tostudy.mdc"), mdcContent);
2659
2825
  written.push(".cursor/rules/tostudy.mdc");
2660
2826
  }
2661
- if (platform === "generic") {
2827
+ if (platform2 === "generic") {
2662
2828
  const dir = join2(cwd, ".tostudy");
2663
- if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
2664
- writeFileSync2(join2(dir, "AGENTS.md"), content);
2829
+ if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
2830
+ writeFileSync3(join2(dir, "AGENTS.md"), content);
2665
2831
  written.push(".tostudy/AGENTS.md");
2666
2832
  }
2667
- logger3.info("Installed universal /tostudy command", { platform, files: written });
2833
+ logger3.info("Installed universal /tostudy command", { platform: platform2, files: written });
2668
2834
  return written;
2669
2835
  }
2670
2836
  var logger3;
@@ -2672,13 +2838,14 @@ var init_instruction_files = __esm({
2672
2838
  "src/workspace/instruction-files.ts"() {
2673
2839
  "use strict";
2674
2840
  init_src();
2841
+ init_instruction_template_v3();
2675
2842
  logger3 = createLogger("cli:instruction-files");
2676
2843
  }
2677
2844
  });
2678
2845
 
2679
2846
  // src/commands/setup.ts
2680
2847
  import { Command as Command3 } from "commander";
2681
- import { existsSync as existsSync3 } from "node:fs";
2848
+ import { existsSync as existsSync4 } from "node:fs";
2682
2849
  import { join as join3 } from "node:path";
2683
2850
  function ideToPlatform(ideName) {
2684
2851
  const lower = ideName.toLowerCase();
@@ -2738,15 +2905,15 @@ async function runSetup(opts, deps = defaultDeps) {
2738
2905
  const cwd = process.cwd();
2739
2906
  const installedPlatforms = [];
2740
2907
  for (const ide of detected) {
2741
- const platform = ideToPlatform(ide.name);
2742
- if (platform) {
2743
- const files = deps.installUniversalCommand(platform, cwd);
2908
+ const platform2 = ideToPlatform(ide.name);
2909
+ if (platform2) {
2910
+ const files = deps.installUniversalCommand(platform2, cwd);
2744
2911
  installedPlatforms.push(...files);
2745
2912
  }
2746
2913
  }
2747
2914
  if (opts.ide) {
2748
- const platform = ideToPlatform(opts.ide) ?? opts.ide;
2749
- const files = deps.installUniversalCommand(platform, cwd);
2915
+ const platform2 = ideToPlatform(opts.ide) ?? opts.ide;
2916
+ const files = deps.installUniversalCommand(platform2, cwd);
2750
2917
  installedPlatforms.push(...files);
2751
2918
  }
2752
2919
  if (installedPlatforms.length === 0) {
@@ -2768,9 +2935,9 @@ async function runSetup(opts, deps = defaultDeps) {
2768
2935
  }
2769
2936
  }
2770
2937
  const activeCourse = await deps.getActiveCourse();
2771
- const hasClaudeCmd = existsSync3(join3(cwd, ".claude", "commands", "tostudy.md"));
2772
- const hasCursorRule = existsSync3(join3(cwd, ".cursor", "rules", "tostudy.mdc"));
2773
- const hasMcpConfig = existsSync3(join3(cwd, ".claude", "claude_desktop_config.json")) || existsSync3(join3(cwd, ".mcp.json"));
2938
+ const hasClaudeCmd = existsSync4(join3(cwd, ".claude", "commands", "tostudy.md"));
2939
+ const hasCursorRule = existsSync4(join3(cwd, ".cursor", "rules", "tostudy.mdc"));
2940
+ const hasMcpConfig = existsSync4(join3(cwd, ".claude", "claude_desktop_config.json")) || existsSync4(join3(cwd, ".mcp.json"));
2774
2941
  const hasClaudeIDE = detected.some((ide) => ide.name.toLowerCase().includes("claude"));
2775
2942
  deps.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
2776
2943
  deps.log(" Setup completo!\n");
@@ -2841,20 +3008,20 @@ __export(update_checker_exports, {
2841
3008
  fetchLatestVersion: () => fetchLatestVersion,
2842
3009
  isNewerVersion: () => isNewerVersion
2843
3010
  });
2844
- import fs4 from "node:fs";
2845
- import path4 from "node:path";
2846
- import os5 from "node:os";
3011
+ import fs5 from "node:fs";
3012
+ import path6 from "node:path";
3013
+ import os6 from "node:os";
2847
3014
  function getConfigDir2() {
2848
3015
  if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
2849
- return path4.join(process.env["XDG_CONFIG_HOME"], "tostudy");
3016
+ return path6.join(process.env["XDG_CONFIG_HOME"], "tostudy");
2850
3017
  }
2851
- return path4.join(os5.homedir(), ".tostudy");
3018
+ return path6.join(os6.homedir(), ".tostudy");
2852
3019
  }
2853
3020
  function readCache() {
2854
3021
  try {
2855
- const p = path4.join(getConfigDir2(), CACHE_FILE);
2856
- if (!fs4.existsSync(p)) return null;
2857
- return JSON.parse(fs4.readFileSync(p, "utf-8"));
3022
+ const p = path6.join(getConfigDir2(), CACHE_FILE);
3023
+ if (!fs5.existsSync(p)) return null;
3024
+ return JSON.parse(fs5.readFileSync(p, "utf-8"));
2858
3025
  } catch {
2859
3026
  return null;
2860
3027
  }
@@ -2862,8 +3029,8 @@ function readCache() {
2862
3029
  function writeCache(cache) {
2863
3030
  try {
2864
3031
  const dir = getConfigDir2();
2865
- fs4.mkdirSync(dir, { recursive: true });
2866
- fs4.writeFileSync(path4.join(dir, CACHE_FILE), JSON.stringify(cache));
3032
+ fs5.mkdirSync(dir, { recursive: true });
3033
+ fs5.writeFileSync(path6.join(dir, CACHE_FILE), JSON.stringify(cache));
2867
3034
  } catch {
2868
3035
  }
2869
3036
  }
@@ -2931,6 +3098,84 @@ var init_update_checker = __esm({
2931
3098
  }
2932
3099
  });
2933
3100
 
3101
+ // src/learner-brief/cache.ts
3102
+ import fs6 from "node:fs";
3103
+ import os7 from "node:os";
3104
+ import path7 from "node:path";
3105
+ function getDefaultConfigDir() {
3106
+ if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
3107
+ return path7.join(process.env["XDG_CONFIG_HOME"], "tostudy");
3108
+ }
3109
+ return path7.join(os7.homedir(), ".tostudy");
3110
+ }
3111
+ function resolveCachePath(configDir) {
3112
+ return path7.join(configDir, "student-brief.json");
3113
+ }
3114
+ async function readBriefCache(configDir) {
3115
+ const dir = configDir ?? getDefaultConfigDir();
3116
+ const p = resolveCachePath(dir);
3117
+ if (!fs6.existsSync(p)) return null;
3118
+ try {
3119
+ const raw = fs6.readFileSync(p, "utf-8");
3120
+ return JSON.parse(raw);
3121
+ } catch {
3122
+ return null;
3123
+ }
3124
+ }
3125
+ async function writeBriefCache(configDir, cache) {
3126
+ const dir = configDir ?? getDefaultConfigDir();
3127
+ fs6.mkdirSync(dir, { recursive: true });
3128
+ fs6.writeFileSync(resolveCachePath(dir), JSON.stringify(cache, null, 2), { mode: 384 });
3129
+ }
3130
+ var init_cache = __esm({
3131
+ "src/learner-brief/cache.ts"() {
3132
+ "use strict";
3133
+ }
3134
+ });
3135
+
3136
+ // src/tutor-persona/cache.ts
3137
+ import fs7 from "node:fs";
3138
+ import os8 from "node:os";
3139
+ import path8 from "node:path";
3140
+ function getDefaultConfigDir2() {
3141
+ if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
3142
+ return path8.join(process.env["XDG_CONFIG_HOME"], "tostudy");
3143
+ }
3144
+ return path8.join(os8.homedir(), ".tostudy");
3145
+ }
3146
+ function resolveCachePath2(configDir) {
3147
+ return path8.join(configDir, "tutor-personalities.json");
3148
+ }
3149
+ function readAll(configDir) {
3150
+ const p = resolveCachePath2(configDir);
3151
+ if (!fs7.existsSync(p)) return {};
3152
+ try {
3153
+ return JSON.parse(fs7.readFileSync(p, "utf-8"));
3154
+ } catch {
3155
+ return {};
3156
+ }
3157
+ }
3158
+ function writeAll(configDir, cache) {
3159
+ fs7.mkdirSync(configDir, { recursive: true });
3160
+ fs7.writeFileSync(resolveCachePath2(configDir), JSON.stringify(cache, null, 2), { mode: 384 });
3161
+ }
3162
+ async function readPersonaCacheForCourse(configDir, courseId) {
3163
+ const dir = configDir ?? getDefaultConfigDir2();
3164
+ const all = readAll(dir);
3165
+ return all[courseId] ?? null;
3166
+ }
3167
+ async function writePersonaCacheForCourse(configDir, courseId, entry) {
3168
+ const dir = configDir ?? getDefaultConfigDir2();
3169
+ const all = readAll(dir);
3170
+ all[courseId] = entry;
3171
+ writeAll(dir, all);
3172
+ }
3173
+ var init_cache2 = __esm({
3174
+ "src/tutor-persona/cache.ts"() {
3175
+ "use strict";
3176
+ }
3177
+ });
3178
+
2934
3179
  // src/commands/doctor.ts
2935
3180
  import { Command as Command4 } from "commander";
2936
3181
  var doctorCommand;
@@ -2943,6 +3188,9 @@ var init_doctor = __esm({
2943
3188
  init_formatter();
2944
3189
  init_cli();
2945
3190
  init_update_checker();
3191
+ init_cache();
3192
+ init_cache2();
3193
+ init_workspace_marker();
2946
3194
  doctorCommand = new Command4("doctor").description("Diagn\xF3stico do ambiente").option("--json", "Output JSON").option("--fix", "Auto-corrigir problemas (reservado para vers\xF5es futuras)").action(async (opts) => {
2947
3195
  const checks = {};
2948
3196
  const node = detectNode();
@@ -2993,6 +3241,68 @@ var init_doctor = __esm({
2993
3241
  } catch {
2994
3242
  }
2995
3243
  checks["ides"] = ides.filter((ide) => ide.detected);
3244
+ let workspaceMarkerCheck;
3245
+ try {
3246
+ const marker = await readWorkspaceMarker(process.cwd());
3247
+ workspaceMarkerCheck = marker ? {
3248
+ status: "found",
3249
+ courseId: marker.courseId,
3250
+ slug: marker.slug,
3251
+ courseTitle: marker.courseTitle
3252
+ } : { status: "absent" };
3253
+ } catch (err) {
3254
+ workspaceMarkerCheck = {
3255
+ status: "error",
3256
+ message: err instanceof Error ? err.message : String(err)
3257
+ };
3258
+ }
3259
+ checks["workspaceMarker"] = workspaceMarkerCheck;
3260
+ let briefCacheCheck;
3261
+ try {
3262
+ const briefCache = await readBriefCache();
3263
+ if (briefCache?.brief) {
3264
+ const ageMs = Date.now() - new Date(briefCache.fetchedAt).getTime();
3265
+ const ageDays = Math.floor(ageMs / (1e3 * 60 * 60 * 24));
3266
+ briefCacheCheck = { status: "cached", ageDays, stale: ageDays > 7 };
3267
+ } else if (briefCache?.skipped) {
3268
+ briefCacheCheck = { status: "skipped" };
3269
+ } else {
3270
+ briefCacheCheck = { status: "missing" };
3271
+ }
3272
+ } catch (err) {
3273
+ briefCacheCheck = {
3274
+ status: "error",
3275
+ message: err instanceof Error ? err.message : String(err)
3276
+ };
3277
+ }
3278
+ checks["briefCache"] = briefCacheCheck;
3279
+ let personaCacheCheck;
3280
+ try {
3281
+ const active = await getActiveCourse();
3282
+ if (active) {
3283
+ const personaEntry = await readPersonaCacheForCourse(void 0, active.courseId);
3284
+ if (personaEntry) {
3285
+ const ageMs = Date.now() - new Date(personaEntry.fetchedAt).getTime();
3286
+ const ageDays = Math.floor(ageMs / (1e3 * 60 * 60 * 24));
3287
+ personaCacheCheck = {
3288
+ status: "cached",
3289
+ courseTitle: active.courseTitle,
3290
+ ageDays,
3291
+ stale: ageDays > 7
3292
+ };
3293
+ } else {
3294
+ personaCacheCheck = { status: "missing", courseTitle: active.courseTitle };
3295
+ }
3296
+ } else {
3297
+ personaCacheCheck = { status: "no-active-course" };
3298
+ }
3299
+ } catch (err) {
3300
+ personaCacheCheck = {
3301
+ status: "error",
3302
+ message: err instanceof Error ? err.message : String(err)
3303
+ };
3304
+ }
3305
+ checks["personaCache"] = personaCacheCheck;
2996
3306
  if (opts.json) {
2997
3307
  output(checks, { json: true });
2998
3308
  return;
@@ -3038,6 +3348,38 @@ var init_doctor = __esm({
3038
3348
  ` ${ide.detected ? "\u2713" : "\u25CB"} ${ide.name.padEnd(14)} ${ide.detected ? "detectado" : "n\xE3o detectado"}`
3039
3349
  );
3040
3350
  }
3351
+ console.log("\n Contexto de estudo");
3352
+ if (workspaceMarkerCheck.status === "found") {
3353
+ console.log(
3354
+ ` \u2713 Workspace ${workspaceMarkerCheck.courseTitle} (${workspaceMarkerCheck.slug})`
3355
+ );
3356
+ } else if (workspaceMarkerCheck.status === "absent") {
3357
+ console.log(" \u25CB Workspace sem marker no diret\xF3rio atual (usando estado global)");
3358
+ } else {
3359
+ console.log(` \u2717 Workspace erro ao ler marker: ${workspaceMarkerCheck.message}`);
3360
+ }
3361
+ if (briefCacheCheck.status === "cached") {
3362
+ const warn = briefCacheCheck.stale ? " (desatualizado \u2014 rode `tostudy sync`)" : "";
3363
+ console.log(` \u2713 Brief (T1) cache com ${briefCacheCheck.ageDays}d${warn}`);
3364
+ } else if (briefCacheCheck.status === "skipped") {
3365
+ console.log(" \u25CB Brief (T1) ignorado pelo usu\xE1rio");
3366
+ } else if (briefCacheCheck.status === "missing") {
3367
+ console.log(" \u25CB Brief (T1) n\xE3o criado \u2014 rode `tostudy brief-create`");
3368
+ } else {
3369
+ console.log(` \u2717 Brief (T1) erro: ${briefCacheCheck.message}`);
3370
+ }
3371
+ if (personaCacheCheck.status === "cached") {
3372
+ const warn = personaCacheCheck.stale ? " (desatualizado)" : "";
3373
+ console.log(
3374
+ ` \u2713 Persona (T0) cache para "${personaCacheCheck.courseTitle}" (${personaCacheCheck.ageDays}d)${warn}`
3375
+ );
3376
+ } else if (personaCacheCheck.status === "missing") {
3377
+ console.log(` \u25CB Persona (T0) sem cache para "${personaCacheCheck.courseTitle}"`);
3378
+ } else if (personaCacheCheck.status === "no-active-course") {
3379
+ console.log(" \u25CB Persona (T0) nenhum curso ativo");
3380
+ } else {
3381
+ console.log(` \u2717 Persona (T0) erro: ${personaCacheCheck.message}`);
3382
+ }
3041
3383
  console.log("");
3042
3384
  });
3043
3385
  }
@@ -3074,113 +3416,405 @@ var init_courses2 = __esm({
3074
3416
  }
3075
3417
  });
3076
3418
 
3077
- // src/workspace/resolve.ts
3078
- import fs5 from "node:fs/promises";
3079
- import path5 from "node:path";
3080
- import os6 from "node:os";
3081
- function courseSlug(title) {
3082
- return title.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
3419
+ // src/onboarding/status.ts
3420
+ async function getCourseOnboardingStatus(activeCourse, configDir, cwd = process.cwd()) {
3421
+ const onboardingState = await getCourseOnboardingState(activeCourse.courseId, configDir);
3422
+ const initReady = Boolean(onboardingState?.initCompletedAt);
3423
+ const ws = await resolveEffectiveWorkspace(
3424
+ activeCourse.courseTitle,
3425
+ onboardingState?.workspacePath,
3426
+ cwd
3427
+ );
3428
+ return {
3429
+ initReady,
3430
+ workspaceReady: ws.found,
3431
+ workspacePath: ws.workspacePath,
3432
+ workspaceSource: ws.source
3433
+ };
3083
3434
  }
3084
- async function resolveWorkspace(courseTitle, basePath = DEFAULT_BASE) {
3085
- const slug = courseSlug(courseTitle);
3086
- const candidate = path5.join(basePath, slug);
3087
- try {
3088
- await fs5.access(path5.join(candidate, ".ana-config.json"));
3089
- return { found: true, workspacePath: candidate, source: "default" };
3090
- } catch {
3091
- return { found: false, workspacePath: null };
3435
+ var init_status = __esm({
3436
+ "src/onboarding/status.ts"() {
3437
+ "use strict";
3438
+ init_session();
3439
+ init_resolve();
3092
3440
  }
3441
+ });
3442
+
3443
+ // src/learner-brief/api.ts
3444
+ async function apiFetch3(url2, token2, init) {
3445
+ const response = await fetch(url2, {
3446
+ method: init?.method ?? "GET",
3447
+ body: init?.body,
3448
+ headers: {
3449
+ "Content-Type": "application/json",
3450
+ Authorization: `Bearer ${token2}`,
3451
+ ...init?.headers ?? {}
3452
+ }
3453
+ });
3454
+ const body = await response.json();
3455
+ if (!response.ok) {
3456
+ throw new CliApiError(body.error ?? `API error ${response.status}`, response.status);
3457
+ }
3458
+ return body;
3093
3459
  }
3094
- async function isCwdWorkspace(cwd = process.cwd()) {
3095
- return await resolveCwdWorkspacePath(cwd) !== null;
3460
+ async function fetchLearnerBrief(input2) {
3461
+ const response = await apiFetch3(
3462
+ `${input2.apiUrl}/api/cli/student/learner-brief`,
3463
+ input2.token
3464
+ );
3465
+ return response.brief;
3096
3466
  }
3097
- async function resolveCwdWorkspacePath(cwd = process.cwd()) {
3098
- const tostudyDir = path5.join(cwd, ".tostudy");
3099
- try {
3100
- const stat = await fs5.stat(tostudyDir);
3101
- if (stat.isDirectory()) return tostudyDir;
3102
- } catch {
3467
+ async function upsertLearnerBrief(input2) {
3468
+ const response = await apiFetch3(
3469
+ `${input2.apiUrl}/api/cli/student/learner-brief`,
3470
+ input2.token,
3471
+ {
3472
+ method: "POST",
3473
+ body: JSON.stringify({ text: input2.text, source: input2.source })
3474
+ }
3475
+ );
3476
+ return response.brief;
3477
+ }
3478
+ var init_api2 = __esm({
3479
+ "src/learner-brief/api.ts"() {
3480
+ "use strict";
3481
+ init_http2();
3103
3482
  }
3104
- try {
3105
- await fs5.access(path5.join(cwd, ".ana-config.json"));
3106
- return cwd;
3107
- } catch {
3108
- return null;
3483
+ });
3484
+
3485
+ // src/learner-brief/bootstrap.ts
3486
+ import readline from "node:readline/promises";
3487
+ import { stdin as processStdin, stdout as processStdout } from "node:process";
3488
+ function composeBriefFromAnswers(answers) {
3489
+ const levelLabel = {
3490
+ beginner: "iniciante",
3491
+ intermediate: "intermediario",
3492
+ advanced: "avancado"
3493
+ }[answers.yourLevel];
3494
+ const paragraphOne = [answers.whoYouAre, answers.whereYouWork, answers.whatYouDo].map((s) => s.trim()).filter(Boolean).join(". ");
3495
+ const paragraphTwo = `Meu nivel neste assunto e ${levelLabel}. ${answers.yourGoals.trim()}`;
3496
+ const paragraphThree = `Contexto real do meu dia a dia: ${answers.realContext.trim()}`;
3497
+ const text2 = [paragraphOne, paragraphTwo, paragraphThree].map((p) => p.replace(/\.\.+/g, ".")).join("\n\n");
3498
+ return text2.slice(0, 5e3);
3499
+ }
3500
+ async function askNonEmpty(question, deps) {
3501
+ while (true) {
3502
+ const answer = (await deps.ask(question)).trim();
3503
+ if (answer.length > 0) return answer;
3109
3504
  }
3110
3505
  }
3111
- function resolveVaultPath(workspacePath, slug) {
3112
- const base = path5.basename(workspacePath) === ".tostudy" ? path5.dirname(workspacePath) : workspacePath;
3113
- return path5.join(base, `vault-${slug}`);
3114
- }
3115
- async function findExistingVault(workspacePath, slug) {
3116
- const slugged = resolveVaultPath(workspacePath, slug);
3117
- try {
3118
- await fs5.access(path5.join(slugged, ".ana-vault.json"));
3119
- return slugged;
3120
- } catch {
3506
+ async function askLevel(deps) {
3507
+ while (true) {
3508
+ const raw = (await deps.ask(" Seu nivel geral neste assunto (beginner/intermediate/advanced): ")).trim().toLowerCase();
3509
+ if (raw === "beginner" || raw === "intermediate" || raw === "advanced") {
3510
+ return raw;
3511
+ }
3121
3512
  }
3122
- const legacy = path5.join(workspacePath, "vault");
3513
+ }
3514
+ async function collectBootstrapAnswersWithDeps(input2, deps) {
3515
+ deps.write(
3516
+ [
3517
+ "",
3518
+ `Ola ${input2.userName}, vamos criar seu brief base (1-2 minutos).`,
3519
+ "Ele ajuda o tutor a te conhecer para adaptar o ensino a voce.",
3520
+ "Voce pode editar depois em https://tostudy.ai/student/settings/learner-brief.",
3521
+ ""
3522
+ ].join("\n")
3523
+ );
3524
+ const whoYouAre = await askNonEmpty(" Quem voce e (profissao/cargo/area): ", deps);
3525
+ const whereYouWork = await askNonEmpty(" Onde trabalha (empresa/setor): ", deps);
3526
+ const whatYouDo = await askNonEmpty(" O que voce faz (responsabilidades/projetos): ", deps);
3527
+ const yourLevel = await askLevel(deps);
3528
+ const yourGoals = await askNonEmpty(" Seu objetivo de aprendizado: ", deps);
3529
+ const realContext = await askNonEmpty(
3530
+ " Contexto real (exemplos que o tutor pode usar nas aulas): ",
3531
+ deps
3532
+ );
3533
+ return { whoYouAre, whereYouWork, whatYouDo, yourLevel, yourGoals, realContext };
3534
+ }
3535
+ async function collectBootstrapAnswers(input2) {
3536
+ const rl = readline.createInterface({ input: processStdin, output: processStdout });
3123
3537
  try {
3124
- await fs5.access(path5.join(legacy, ".ana-vault.json"));
3125
- return legacy;
3126
- } catch {
3127
- return null;
3538
+ return await collectBootstrapAnswersWithDeps(input2, {
3539
+ ask: (q) => rl.question(q),
3540
+ write: (s) => {
3541
+ processStdout.write(s);
3542
+ }
3543
+ });
3544
+ } finally {
3545
+ rl.close();
3128
3546
  }
3129
3547
  }
3130
- async function resolveEffectiveWorkspace(courseTitle, storedPath, cwd = process.cwd(), defaultBasePath = DEFAULT_BASE) {
3131
- const cwdWorkspace = await resolveCwdWorkspacePath(cwd);
3132
- if (cwdWorkspace) {
3133
- return { found: true, workspacePath: cwdWorkspace, source: "cwd" };
3548
+ var init_bootstrap = __esm({
3549
+ "src/learner-brief/bootstrap.ts"() {
3550
+ "use strict";
3134
3551
  }
3135
- if (storedPath) {
3136
- try {
3137
- await fs5.access(path5.join(storedPath, ".ana-config.json"));
3138
- return { found: true, workspacePath: storedPath, source: "stored" };
3139
- } catch {
3552
+ });
3553
+
3554
+ // src/tutor-persona/api.ts
3555
+ async function fetchTutorPersonality(input2) {
3556
+ const url2 = new URL(`${input2.apiUrl}/api/cli/tutor-personality`);
3557
+ url2.searchParams.set("courseId", input2.courseId);
3558
+ const response = await fetch(url2.toString(), {
3559
+ method: "GET",
3560
+ headers: {
3561
+ "Content-Type": "application/json",
3562
+ Authorization: `Bearer ${input2.token}`
3140
3563
  }
3564
+ });
3565
+ const body = await response.json();
3566
+ if (!response.ok) {
3567
+ throw new CliApiError(body.error ?? `API error ${response.status}`, response.status);
3141
3568
  }
3142
- const result = await resolveWorkspace(courseTitle, defaultBasePath);
3143
- if (result.found) {
3144
- return { ...result, source: "default" };
3145
- }
3146
- return { found: false, workspacePath: null };
3569
+ return body.personality ?? null;
3147
3570
  }
3148
- var DEFAULT_BASE;
3149
- var init_resolve = __esm({
3150
- "src/workspace/resolve.ts"() {
3571
+ var init_api3 = __esm({
3572
+ "src/tutor-persona/api.ts"() {
3151
3573
  "use strict";
3152
- DEFAULT_BASE = path5.join(os6.homedir(), "study");
3574
+ init_http2();
3153
3575
  }
3154
3576
  });
3155
3577
 
3156
- // src/onboarding/status.ts
3157
- async function getCourseOnboardingStatus(activeCourse, configDir, cwd = process.cwd()) {
3158
- const onboardingState = await getCourseOnboardingState(activeCourse.courseId, configDir);
3159
- const initReady = Boolean(onboardingState?.initCompletedAt);
3160
- const ws = await resolveEffectiveWorkspace(
3161
- activeCourse.courseTitle,
3162
- onboardingState?.workspacePath,
3163
- cwd
3578
+ // src/instruction-pipeline.ts
3579
+ async function defaultWriteFiles(_cwd, _courseMeta, _content) {
3580
+ throw new Error(
3581
+ "writeFiles default implementation is a stub \u2014 inject a real one from the calling command (select/init/sync). Commit 9 will wire the real implementation."
3582
+ );
3583
+ }
3584
+ async function defaultFetchCourseMeta(_input) {
3585
+ throw new Error(
3586
+ "fetchCourseMeta default implementation is a stub \u2014 inject a real one from the calling command (select/init/sync)"
3164
3587
  );
3588
+ }
3589
+ async function resolveAndGenerate(input2, options = {}, context) {
3590
+ const deps = context.deps ?? defaultDeps2;
3591
+ const forceRefresh = options.forceRefresh ?? false;
3592
+ const session = await deps.getSession(context.configDir);
3593
+ if (!session) {
3594
+ throw new Error("No active session \u2014 run `tostudy login`");
3595
+ }
3596
+ const courseMeta = await deps.fetchCourseMeta({
3597
+ session,
3598
+ courseId: input2.courseId,
3599
+ enrollmentId: input2.enrollmentId
3600
+ });
3601
+ let tutorPersona = null;
3602
+ if (!forceRefresh) {
3603
+ const cached2 = await deps.readPersonaCacheForCourse(context.configDir, input2.courseId);
3604
+ if (cached2) tutorPersona = cached2.personality;
3605
+ }
3606
+ if (!tutorPersona) {
3607
+ try {
3608
+ tutorPersona = await deps.fetchTutorPersona({
3609
+ apiUrl: session.apiUrl,
3610
+ token: session.token,
3611
+ courseId: input2.courseId
3612
+ });
3613
+ if (tutorPersona) {
3614
+ await deps.writePersonaCacheForCourse(context.configDir, input2.courseId, {
3615
+ personality: tutorPersona,
3616
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
3617
+ profileUpdatedAt: tutorPersona.updatedAt
3618
+ });
3619
+ }
3620
+ } catch (err) {
3621
+ logger5.warn("T0 fetch failed \u2014 rendering without persona", { err });
3622
+ tutorPersona = null;
3623
+ }
3624
+ }
3625
+ let studentBrief = null;
3626
+ let bootstrapped = null;
3627
+ if (!forceRefresh) {
3628
+ const cached2 = await deps.readBriefCache(context.configDir);
3629
+ if (cached2 && cached2.userId === session.userId && !cached2.skipped) {
3630
+ studentBrief = cached2.brief;
3631
+ }
3632
+ }
3633
+ if (!studentBrief) {
3634
+ try {
3635
+ studentBrief = await deps.fetchLearnerBrief({
3636
+ apiUrl: session.apiUrl,
3637
+ token: session.token
3638
+ });
3639
+ await deps.writeBriefCache(context.configDir, {
3640
+ userId: session.userId,
3641
+ brief: studentBrief,
3642
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
3643
+ });
3644
+ } catch (err) {
3645
+ logger5.warn("T1 fetch failed \u2014 rendering without brief", { err });
3646
+ }
3647
+ }
3648
+ if (!studentBrief && options.collectT1Interactive) {
3649
+ try {
3650
+ const answers = await deps.collectBootstrapAnswers({ userName: session.userName });
3651
+ const text2 = composeBriefFromAnswers(answers);
3652
+ studentBrief = await deps.upsertLearnerBrief({
3653
+ apiUrl: session.apiUrl,
3654
+ token: session.token,
3655
+ text: text2,
3656
+ source: "manual"
3657
+ });
3658
+ await deps.writeBriefCache(context.configDir, {
3659
+ userId: session.userId,
3660
+ brief: studentBrief,
3661
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
3662
+ });
3663
+ bootstrapped = "t1";
3664
+ } catch (err) {
3665
+ logger5.warn("T1 bootstrap failed", { err });
3666
+ }
3667
+ }
3668
+ let enrollmentProfile = null;
3669
+ try {
3670
+ const onboarding = await deps.fetchEnrollmentOnboarding({
3671
+ apiUrl: session.apiUrl,
3672
+ token: session.token,
3673
+ enrollmentId: input2.enrollmentId
3674
+ });
3675
+ if (onboarding) {
3676
+ enrollmentProfile = onboarding.learnerProfile;
3677
+ }
3678
+ } catch (err) {
3679
+ logger5.warn("T2 fetch failed \u2014 rendering without enrollment profile", { err });
3680
+ }
3681
+ const content = renderCourseInstruction({
3682
+ course: {
3683
+ courseId: courseMeta.courseId,
3684
+ courseTitle: courseMeta.courseTitle,
3685
+ slug: courseMeta.slug,
3686
+ courseDescription: courseMeta.courseDescription
3687
+ },
3688
+ progress: courseMeta.progress,
3689
+ tutorPersona,
3690
+ studentBrief,
3691
+ enrollmentProfile
3692
+ });
3693
+ const wroteFiles = await deps.writeFiles(context.cwd, courseMeta, content);
3694
+ let markerPath = null;
3695
+ try {
3696
+ markerPath = await deps.writeWorkspaceMarker(context.cwd, {
3697
+ courseId: courseMeta.courseId,
3698
+ enrollmentId: input2.enrollmentId,
3699
+ slug: courseMeta.slug,
3700
+ courseTitle: courseMeta.courseTitle
3701
+ });
3702
+ } catch (err) {
3703
+ logger5.warn("Failed to write workspace marker", { err });
3704
+ }
3165
3705
  return {
3166
- initReady,
3167
- workspaceReady: ws.found,
3168
- workspacePath: ws.workspacePath,
3169
- workspaceSource: ws.source
3706
+ wroteFiles,
3707
+ markerPath,
3708
+ usedT0: tutorPersona !== null,
3709
+ usedT1: studentBrief !== null,
3710
+ usedT2: enrollmentProfile !== null,
3711
+ bootstrapped
3170
3712
  };
3171
3713
  }
3172
- var init_status = __esm({
3173
- "src/onboarding/status.ts"() {
3714
+ var logger5, defaultDeps2;
3715
+ var init_instruction_pipeline = __esm({
3716
+ "src/instruction-pipeline.ts"() {
3174
3717
  "use strict";
3718
+ init_src();
3719
+ init_workspace_marker();
3720
+ init_api2();
3721
+ init_cache();
3722
+ init_bootstrap();
3723
+ init_api3();
3724
+ init_cache2();
3725
+ init_api();
3175
3726
  init_session();
3176
- init_resolve();
3727
+ init_instruction_template_v3();
3728
+ logger5 = createLogger("cli:instruction-pipeline");
3729
+ defaultDeps2 = {
3730
+ fetchTutorPersona: fetchTutorPersonality,
3731
+ fetchLearnerBrief,
3732
+ fetchEnrollmentOnboarding: getRemoteEnrollmentOnboarding,
3733
+ fetchCourseMeta: defaultFetchCourseMeta,
3734
+ readBriefCache,
3735
+ writeBriefCache,
3736
+ readPersonaCacheForCourse,
3737
+ writePersonaCacheForCourse,
3738
+ writeFiles: defaultWriteFiles,
3739
+ writeWorkspaceMarker,
3740
+ collectBootstrapAnswers,
3741
+ upsertLearnerBrief,
3742
+ getSession
3743
+ };
3744
+ }
3745
+ });
3746
+
3747
+ // src/commands/_shared/pipeline-deps.ts
3748
+ async function fetchCourseMetaViaHttp(input2) {
3749
+ const { session, courseId, enrollmentId } = input2;
3750
+ const data = createHttpProvider(session.apiUrl, session.token);
3751
+ const coreDeps = { data, logger: logger6 };
3752
+ const detail = await selectCourse({ userId: session.userId, courseId }, coreDeps);
3753
+ let currentModuleTitle;
3754
+ let currentLessonTitle;
3755
+ try {
3756
+ const progress3 = await getProgress({ enrollmentId }, coreDeps);
3757
+ currentModuleTitle = progress3.currentModule?.title;
3758
+ currentLessonTitle = progress3.currentLesson?.title;
3759
+ } catch (err) {
3760
+ logger6.debug("getProgress failed \u2014 falling back to basic course meta", {
3761
+ error: err instanceof Error ? err.message : String(err)
3762
+ });
3763
+ }
3764
+ return {
3765
+ courseId: detail.courseId,
3766
+ courseTitle: detail.courseTitle,
3767
+ slug: slugify(detail.courseTitle),
3768
+ courseDescription: detail.courseDescription,
3769
+ progress: {
3770
+ percent: detail.progress,
3771
+ moduleCount: detail.moduleCount,
3772
+ lessonCount: detail.lessonCount,
3773
+ currentModuleTitle,
3774
+ currentLessonTitle
3775
+ }
3776
+ };
3777
+ }
3778
+ async function writeFilesAdapter(cwd, courseMeta, content) {
3779
+ return writeInstructionFiles(
3780
+ cwd,
3781
+ {
3782
+ courseTitle: courseMeta.courseTitle,
3783
+ courseId: courseMeta.courseId,
3784
+ progress: courseMeta.progress.percent,
3785
+ moduleCount: courseMeta.progress.moduleCount,
3786
+ lessonCount: courseMeta.progress.lessonCount,
3787
+ currentModuleTitle: courseMeta.progress.currentModuleTitle,
3788
+ currentLessonTitle: courseMeta.progress.currentLessonTitle,
3789
+ courseDescription: courseMeta.courseDescription
3790
+ },
3791
+ content
3792
+ );
3793
+ }
3794
+ function buildPipelineDeps() {
3795
+ return {
3796
+ ...defaultDeps2,
3797
+ fetchCourseMeta: fetchCourseMetaViaHttp,
3798
+ writeFiles: writeFilesAdapter
3799
+ };
3800
+ }
3801
+ var logger6;
3802
+ var init_pipeline_deps = __esm({
3803
+ "src/commands/_shared/pipeline-deps.ts"() {
3804
+ "use strict";
3805
+ init_src();
3806
+ init_courses();
3807
+ init_http2();
3808
+ init_instruction_pipeline();
3809
+ init_instruction_files();
3810
+ logger6 = createLogger("cli:pipeline-deps");
3177
3811
  }
3178
3812
  });
3179
3813
 
3180
3814
  // src/commands/select.ts
3181
3815
  import { Command as Command6 } from "commander";
3182
- import os7 from "node:os";
3183
- var logger5, selectCommand;
3816
+ import os9 from "node:os";
3817
+ var logger7, selectCommand;
3184
3818
  var init_select = __esm({
3185
3819
  "src/commands/select.ts"() {
3186
3820
  "use strict";
@@ -3189,14 +3823,15 @@ var init_select = __esm({
3189
3823
  init_http2();
3190
3824
  init_session();
3191
3825
  init_formatter();
3192
- init_instruction_files();
3193
3826
  init_status();
3194
- logger5 = createLogger("cli:select");
3827
+ init_instruction_pipeline();
3828
+ init_pipeline_deps();
3829
+ logger7 = createLogger("cli:select");
3195
3830
  selectCommand = new Command6("select").description("Activate a course by ID or list index number").argument("<course>", "Course ID (UUID) or index number from `tostudy courses`").option("--json", "Output structured JSON").action(async (course, opts) => {
3196
3831
  try {
3197
3832
  const session = await requireSession();
3198
3833
  const data = createHttpProvider(session.apiUrl, session.token);
3199
- const deps = { data, logger: logger5 };
3834
+ const deps = { data, logger: logger7 };
3200
3835
  const courses3 = await listCourses({ userId: session.userId }, deps);
3201
3836
  let courseId = course;
3202
3837
  let enrollmentId = "";
@@ -3229,23 +3864,20 @@ var init_select = __esm({
3229
3864
  enrollmentId
3230
3865
  };
3231
3866
  const onboarding = await getCourseOnboardingStatus(activeCourseForStatus);
3232
- let courseSlug2 = "";
3867
+ let pipelineResult = null;
3233
3868
  try {
3234
- courseSlug2 = generateInstructionFiles({
3235
- courseTitle: detail.courseTitle,
3236
- courseId: detail.courseId,
3237
- progress: detail.progress,
3238
- moduleCount: detail.moduleCount,
3239
- lessonCount: detail.lessonCount,
3240
- courseDescription: detail.courseDescription,
3241
- workspaceReady: onboarding.workspaceReady,
3242
- workspacePath: onboarding.workspacePath ?? void 0
3243
- });
3869
+ pipelineResult = await resolveAndGenerate(
3870
+ { courseId: detail.courseId, enrollmentId },
3871
+ { forceRefresh: false, collectT1Interactive: false, collectT2Interactive: false },
3872
+ { cwd: process.cwd(), deps: buildPipelineDeps() }
3873
+ );
3244
3874
  } catch (err) {
3245
- logger5.warn("Failed to generate instruction files", {
3875
+ logger7.warn("Failed to run instruction pipeline", {
3246
3876
  error: err instanceof Error ? err.message : String(err)
3247
3877
  });
3248
3878
  }
3879
+ const slugMatch = pipelineResult?.wroteFiles.find((f) => f.startsWith(".claude/commands/"))?.match(/tostudy-(.+)\.md$/);
3880
+ const courseSlug2 = slugMatch?.[1] ?? "";
3249
3881
  if (opts.json) {
3250
3882
  output(
3251
3883
  {
@@ -3253,13 +3885,18 @@ var init_select = __esm({
3253
3885
  enrollmentId,
3254
3886
  courseSlug: courseSlug2,
3255
3887
  workspacePath: onboarding.workspacePath,
3256
- workspaceSource: onboarding.workspaceSource
3888
+ workspaceSource: onboarding.workspaceSource,
3889
+ wroteFiles: pipelineResult?.wroteFiles ?? [],
3890
+ markerPath: pipelineResult?.markerPath ?? null,
3891
+ usedT0: pipelineResult?.usedT0 ?? false,
3892
+ usedT1: pipelineResult?.usedT1 ?? false,
3893
+ usedT2: pipelineResult?.usedT2 ?? false
3257
3894
  },
3258
3895
  { json: true }
3259
3896
  );
3260
3897
  } else {
3261
3898
  const slashCmd = courseSlug2 ? `/tostudy-${courseSlug2}` : "/tostudy";
3262
- const home = os7.homedir();
3899
+ const home = os9.homedir();
3263
3900
  const cwd = process.cwd();
3264
3901
  const namespacedPath = `${cwd}/.tostudy`;
3265
3902
  let wsLine;
@@ -3272,20 +3909,26 @@ var init_select = __esm({
3272
3909
  } else {
3273
3910
  wsLine = " Workspace: rode `tostudy workspace setup` para configurar";
3274
3911
  }
3275
- output(
3276
- [
3277
- `\u2713 Curso ativado: ${detail.courseTitle}`,
3278
- ` Progresso: ${detail.progress}% | ${detail.moduleCount} m\xF3dulos | ${detail.lessonCount} li\xE7\xF5es`,
3279
- wsLine,
3280
- "",
3281
- " Arquivos de contexto criados para seu assistente AI.",
3282
- "",
3283
- "\u2192 Abra sua plataforma (claude, codex, cursor...)",
3284
- `\u2192 No Claude Code, digite: ${slashCmd}`,
3285
- "\u2192 Em outras plataformas: tostudy init"
3286
- ].join("\n"),
3287
- { json: false }
3912
+ const lines = [
3913
+ `\u2713 Curso ativado: ${detail.courseTitle}`,
3914
+ ` Progresso: ${detail.progress}% | ${detail.moduleCount} m\xF3dulos | ${detail.lessonCount} li\xE7\xF5es`,
3915
+ wsLine,
3916
+ "",
3917
+ " Arquivos de contexto criados para seu assistente AI."
3918
+ ];
3919
+ if (pipelineResult && !pipelineResult.usedT0) {
3920
+ lines.push(" (sem persona do creator \u2014 usando tutor neutro)");
3921
+ }
3922
+ if (pipelineResult && !pipelineResult.usedT1) {
3923
+ lines.push(" (sem brief base \u2014 rode `tostudy init` para configurar)");
3924
+ }
3925
+ lines.push(
3926
+ "",
3927
+ "\u2192 Abra sua plataforma (claude, codex, cursor...)",
3928
+ `\u2192 No Claude Code, digite: ${slashCmd}`,
3929
+ "\u2192 Em outras plataformas: tostudy init"
3288
3930
  );
3931
+ output(lines.join("\n"), { json: false });
3289
3932
  }
3290
3933
  } catch (err) {
3291
3934
  const msg = err instanceof Error ? err.message : String(err);
@@ -3297,7 +3940,7 @@ var init_select = __esm({
3297
3940
 
3298
3941
  // src/commands/progress.ts
3299
3942
  import { Command as Command7 } from "commander";
3300
- var logger6, progressCommand;
3943
+ var logger8, progressCommand;
3301
3944
  var init_progress = __esm({
3302
3945
  "src/commands/progress.ts"() {
3303
3946
  "use strict";
@@ -3306,15 +3949,15 @@ var init_progress = __esm({
3306
3949
  init_http2();
3307
3950
  init_session();
3308
3951
  init_formatter();
3309
- logger6 = createLogger("cli:progress");
3952
+ logger8 = createLogger("cli:progress");
3310
3953
  progressCommand = new Command7("progress").description("Show your progress in the active course").option("--json", "Output structured JSON").action(async (opts) => {
3311
3954
  try {
3312
3955
  const session = await requireSession();
3313
- const activeCourse = await requireActiveCourse();
3956
+ const activeCourse = await requireActiveCourse(void 0, { respectWorkspace: true });
3314
3957
  const driftWarning = await checkCourseDrift();
3315
3958
  if (driftWarning) process.stderr.write(driftWarning + "\n");
3316
3959
  const data = createHttpProvider(session.apiUrl, session.token);
3317
- const deps = { data, logger: logger6 };
3960
+ const deps = { data, logger: logger8 };
3318
3961
  const progress3 = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
3319
3962
  if (opts.json) {
3320
3963
  output(progress3, { json: true });
@@ -4875,7 +5518,7 @@ var init_query_promise = __esm({
4875
5518
  function mapResultRow(columns, row, joinsNotNullableMap) {
4876
5519
  const nullifyMap = {};
4877
5520
  const result = columns.reduce(
4878
- (result2, { path: path14, field }, columnIndex) => {
5521
+ (result2, { path: path17, field }, columnIndex) => {
4879
5522
  let decoder;
4880
5523
  if (is(field, Column)) {
4881
5524
  decoder = field;
@@ -4887,8 +5530,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
4887
5530
  decoder = field.sql.decoder;
4888
5531
  }
4889
5532
  let node = result2;
4890
- for (const [pathChunkIndex, pathChunk] of path14.entries()) {
4891
- if (pathChunkIndex < path14.length - 1) {
5533
+ for (const [pathChunkIndex, pathChunk] of path17.entries()) {
5534
+ if (pathChunkIndex < path17.length - 1) {
4892
5535
  if (!(pathChunk in node)) {
4893
5536
  node[pathChunk] = {};
4894
5537
  }
@@ -4896,8 +5539,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
4896
5539
  } else {
4897
5540
  const rawValue = row[columnIndex];
4898
5541
  const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
4899
- if (joinsNotNullableMap && is(field, Column) && path14.length === 2) {
4900
- const objectName = path14[0];
5542
+ if (joinsNotNullableMap && is(field, Column) && path17.length === 2) {
5543
+ const objectName = path17[0];
4901
5544
  if (!(objectName in nullifyMap)) {
4902
5545
  nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
4903
5546
  } else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
@@ -8844,13 +9487,13 @@ function Subscribe(postgres2, options) {
8844
9487
  }
8845
9488
  }
8846
9489
  function handle(a, b2) {
8847
- const path14 = b2.relation.schema + "." + b2.relation.table;
9490
+ const path17 = b2.relation.schema + "." + b2.relation.table;
8848
9491
  call("*", a, b2);
8849
- call("*:" + path14, a, b2);
8850
- b2.relation.keys.length && call("*:" + path14 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
9492
+ call("*:" + path17, a, b2);
9493
+ b2.relation.keys.length && call("*:" + path17 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
8851
9494
  call(b2.command, a, b2);
8852
- call(b2.command + ":" + path14, a, b2);
8853
- b2.relation.keys.length && call(b2.command + ":" + path14 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
9495
+ call(b2.command + ":" + path17, a, b2);
9496
+ b2.relation.keys.length && call(b2.command + ":" + path17 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
8854
9497
  }
8855
9498
  function pong() {
8856
9499
  const x2 = Buffer.alloc(34);
@@ -8963,8 +9606,8 @@ function parseEvent(x) {
8963
9606
  const xs = x.match(/^(\*|insert|update|delete)?:?([^.]+?\.?[^=]+)?=?(.+)?/i) || [];
8964
9607
  if (!xs)
8965
9608
  throw new Error("Malformed subscribe pattern: " + x);
8966
- const [, command, path14, key] = xs;
8967
- return (command || "*") + (path14 ? ":" + (path14.indexOf(".") === -1 ? "public." + path14 : path14) : "") + (key ? "=" + key : "");
9609
+ const [, command, path17, key] = xs;
9610
+ return (command || "*") + (path17 ? ":" + (path17.indexOf(".") === -1 ? "public." + path17 : path17) : "") + (key ? "=" + key : "");
8968
9611
  }
8969
9612
  var noop2;
8970
9613
  var init_subscribe = __esm({
@@ -9045,8 +9688,8 @@ var init_large = __esm({
9045
9688
  });
9046
9689
 
9047
9690
  // ../../node_modules/postgres/src/index.js
9048
- import os8 from "os";
9049
- import fs6 from "fs";
9691
+ import os10 from "os";
9692
+ import fs8 from "fs";
9050
9693
  function Postgres(a, b2) {
9051
9694
  const options = parseOptions(a, b2), subscribe = options.no_subscribe || Subscribe(Postgres, { ...options });
9052
9695
  let ending = false;
@@ -9102,10 +9745,10 @@ function Postgres(a, b2) {
9102
9745
  });
9103
9746
  return query;
9104
9747
  }
9105
- function file2(path14, args = [], options2 = {}) {
9748
+ function file2(path17, args = [], options2 = {}) {
9106
9749
  arguments.length === 2 && !Array.isArray(args) && (options2 = args, args = []);
9107
9750
  const query = new Query([], args, (query2) => {
9108
- fs6.readFile(path14, "utf8", (err, string4) => {
9751
+ fs8.readFile(path17, "utf8", (err, string4) => {
9109
9752
  if (err)
9110
9753
  return query2.reject(err);
9111
9754
  query2.strings = [string4];
@@ -9423,7 +10066,7 @@ function parseUrl(url2) {
9423
10066
  }
9424
10067
  function osUsername() {
9425
10068
  try {
9426
- return os8.userInfo().username;
10069
+ return os10.userInfo().username;
9427
10070
  } catch (_) {
9428
10071
  return process.env.USERNAME || process.env.USER || process.env.LOGNAME;
9429
10072
  }
@@ -13331,7 +13974,7 @@ async function hashQuery(sql2, params) {
13331
13974
  return hashHex;
13332
13975
  }
13333
13976
  var Cache, NoopCache;
13334
- var init_cache = __esm({
13977
+ var init_cache3 = __esm({
13335
13978
  "../../node_modules/drizzle-orm/cache/core/cache.js"() {
13336
13979
  init_entity();
13337
13980
  Cache = class {
@@ -13356,7 +13999,7 @@ var init_cache = __esm({
13356
13999
  // ../../node_modules/drizzle-orm/cache/core/index.js
13357
14000
  var init_core = __esm({
13358
14001
  "../../node_modules/drizzle-orm/cache/core/index.js"() {
13359
- init_cache();
14002
+ init_cache3();
13360
14003
  }
13361
14004
  });
13362
14005
 
@@ -13464,7 +14107,7 @@ var init_schema = __esm({
13464
14107
  var PgPreparedQuery, PgSession, PgTransaction;
13465
14108
  var init_session2 = __esm({
13466
14109
  "../../node_modules/drizzle-orm/pg-core/session.js"() {
13467
- init_cache();
14110
+ init_cache3();
13468
14111
  init_entity();
13469
14112
  init_errors();
13470
14113
  init_sql2();
@@ -13684,12 +14327,12 @@ var init_session3 = __esm({
13684
14327
  init_tracing();
13685
14328
  init_utils();
13686
14329
  PostgresJsPreparedQuery = class extends PgPreparedQuery {
13687
- constructor(client, queryString, params, logger19, cache, queryMetadata, cacheConfig, fields, _isResponseInArrayMode, customResultMapper) {
14330
+ constructor(client, queryString, params, logger23, cache, queryMetadata, cacheConfig, fields, _isResponseInArrayMode, customResultMapper) {
13688
14331
  super({ sql: queryString, params }, cache, queryMetadata, cacheConfig);
13689
14332
  this.client = client;
13690
14333
  this.queryString = queryString;
13691
14334
  this.params = params;
13692
- this.logger = logger19;
14335
+ this.logger = logger23;
13693
14336
  this.fields = fields;
13694
14337
  this._isResponseInArrayMode = _isResponseInArrayMode;
13695
14338
  this.customResultMapper = customResultMapper;
@@ -13830,11 +14473,11 @@ function construct(client, config2 = {}) {
13830
14473
  client.options.serializers["114"] = transparentParser;
13831
14474
  client.options.serializers["3802"] = transparentParser;
13832
14475
  const dialect = new PgDialect({ casing: config2.casing });
13833
- let logger19;
14476
+ let logger23;
13834
14477
  if (config2.logger === true) {
13835
- logger19 = new DefaultLogger();
14478
+ logger23 = new DefaultLogger();
13836
14479
  } else if (config2.logger !== false) {
13837
- logger19 = config2.logger;
14480
+ logger23 = config2.logger;
13838
14481
  }
13839
14482
  let schema;
13840
14483
  if (config2.schema) {
@@ -13848,7 +14491,7 @@ function construct(client, config2 = {}) {
13848
14491
  tableNamesMap: tablesConfig.tableNamesMap
13849
14492
  };
13850
14493
  }
13851
- const session = new PostgresJsSession(client, dialect, schema, { logger: logger19, cache: config2.cache });
14494
+ const session = new PostgresJsSession(client, dialect, schema, { logger: logger23, cache: config2.cache });
13852
14495
  const db2 = new PostgresJsDatabase(dialect, session, schema);
13853
14496
  db2.$client = client;
13854
14497
  db2.$cache = config2.cache;
@@ -14163,10 +14806,10 @@ function mergeDefs(...defs) {
14163
14806
  function cloneDef(schema) {
14164
14807
  return mergeDefs(schema._zod.def);
14165
14808
  }
14166
- function getElementAtPath(obj, path14) {
14167
- if (!path14)
14809
+ function getElementAtPath(obj, path17) {
14810
+ if (!path17)
14168
14811
  return obj;
14169
- return path14.reduce((acc, key) => acc?.[key], obj);
14812
+ return path17.reduce((acc, key) => acc?.[key], obj);
14170
14813
  }
14171
14814
  function promiseAllObject(promisesObj) {
14172
14815
  const keys = Object.keys(promisesObj);
@@ -14478,11 +15121,11 @@ function aborted(x, startIndex = 0) {
14478
15121
  }
14479
15122
  return false;
14480
15123
  }
14481
- function prefixIssues(path14, issues) {
15124
+ function prefixIssues(path17, issues) {
14482
15125
  return issues.map((iss) => {
14483
15126
  var _a2;
14484
15127
  (_a2 = iss).path ?? (_a2.path = []);
14485
- iss.path.unshift(path14);
15128
+ iss.path.unshift(path17);
14486
15129
  return iss;
14487
15130
  });
14488
15131
  }
@@ -14724,7 +15367,7 @@ function formatError(error49, mapper = (issue2) => issue2.message) {
14724
15367
  }
14725
15368
  function treeifyError(error49, mapper = (issue2) => issue2.message) {
14726
15369
  const result = { errors: [] };
14727
- const processError = (error50, path14 = []) => {
15370
+ const processError = (error50, path17 = []) => {
14728
15371
  var _a2, _b;
14729
15372
  for (const issue2 of error50.issues) {
14730
15373
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -14734,7 +15377,7 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
14734
15377
  } else if (issue2.code === "invalid_element") {
14735
15378
  processError({ issues: issue2.issues }, issue2.path);
14736
15379
  } else {
14737
- const fullpath = [...path14, ...issue2.path];
15380
+ const fullpath = [...path17, ...issue2.path];
14738
15381
  if (fullpath.length === 0) {
14739
15382
  result.errors.push(mapper(issue2));
14740
15383
  continue;
@@ -14766,8 +15409,8 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
14766
15409
  }
14767
15410
  function toDotPath(_path) {
14768
15411
  const segs = [];
14769
- const path14 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
14770
- for (const seg of path14) {
15412
+ const path17 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
15413
+ for (const seg of path17) {
14771
15414
  if (typeof seg === "number")
14772
15415
  segs.push(`[${seg}]`);
14773
15416
  else if (typeof seg === "symbol")
@@ -24678,7 +25321,7 @@ function _stringFormat(Class2, format, fnOrRegex, _params = {}) {
24678
25321
  return inst;
24679
25322
  }
24680
25323
  var TimePrecision;
24681
- var init_api2 = __esm({
25324
+ var init_api4 = __esm({
24682
25325
  "../../node_modules/zod/v4/core/api.js"() {
24683
25326
  init_checks2();
24684
25327
  init_registries();
@@ -25989,7 +26632,7 @@ var init_core3 = __esm({
25989
26632
  init_locales();
25990
26633
  init_registries();
25991
26634
  init_doc();
25992
- init_api2();
26635
+ init_api4();
25993
26636
  init_to_json_schema();
25994
26637
  init_json_schema_processors();
25995
26638
  init_json_schema_generator();
@@ -27461,13 +28104,13 @@ function resolveRef(ref, ctx) {
27461
28104
  if (!ref.startsWith("#")) {
27462
28105
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
27463
28106
  }
27464
- const path14 = ref.slice(1).split("/").filter(Boolean);
27465
- if (path14.length === 0) {
28107
+ const path17 = ref.slice(1).split("/").filter(Boolean);
28108
+ if (path17.length === 0) {
27466
28109
  return ctx.rootSchema;
27467
28110
  }
27468
28111
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
27469
- if (path14[0] === defsKey) {
27470
- const key = path14[1];
28112
+ if (path17[0] === defsKey) {
28113
+ const key = path17[1];
27471
28114
  if (!key || !ctx.defs[key]) {
27472
28115
  throw new Error(`Reference not found: ${ref}`);
27473
28116
  }
@@ -38785,27 +39428,33 @@ var init_media_assets = __esm({
38785
39428
  "marketing",
38786
39429
  "other"
38787
39430
  ];
38788
- mediaAssets = pgTable("media_assets", {
38789
- id: uuid("id").defaultRandom().primaryKey(),
38790
- url: text("url").notNull(),
38791
- r2Key: text("r2_key").notNull(),
38792
- thumbnailUrl: text("thumbnail_url"),
38793
- type: text("type", { enum: mediaAssetTypeEnum }).notNull(),
38794
- mimeType: text("mime_type").notNull(),
38795
- title: text("title"),
38796
- originalFilename: text("original_filename").notNull(),
38797
- size: integer("size").notNull(),
38798
- width: integer("width"),
38799
- height: integer("height"),
38800
- tags: text("tags").array().default([]),
38801
- uploadedById: uuid("uploaded_by_id").references(() => users.id, {
38802
- onDelete: "set null"
38803
- }),
38804
- domain: text("domain", { enum: mediaAssetDomainEnum }),
38805
- domainEntityId: uuid("domain_entity_id"),
38806
- status: text("status", { enum: mediaAssetStatusEnum }).notNull().default("pending"),
38807
- createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
38808
- });
39431
+ mediaAssets = pgTable(
39432
+ "media_assets",
39433
+ {
39434
+ id: uuid("id").defaultRandom().primaryKey(),
39435
+ url: text("url").notNull(),
39436
+ r2Key: text("r2_key").notNull(),
39437
+ thumbnailUrl: text("thumbnail_url"),
39438
+ type: text("type", { enum: mediaAssetTypeEnum }).notNull(),
39439
+ mimeType: text("mime_type").notNull(),
39440
+ title: text("title"),
39441
+ originalFilename: text("original_filename").notNull(),
39442
+ size: integer("size").notNull(),
39443
+ width: integer("width"),
39444
+ height: integer("height"),
39445
+ tags: text("tags").array().default([]),
39446
+ uploadedById: uuid("uploaded_by_id").references(() => users.id, {
39447
+ onDelete: "set null"
39448
+ }),
39449
+ domain: text("domain", { enum: mediaAssetDomainEnum }),
39450
+ domainEntityId: uuid("domain_entity_id"),
39451
+ status: text("status", { enum: mediaAssetStatusEnum }).notNull().default("pending"),
39452
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
39453
+ },
39454
+ (table) => ({
39455
+ r2KeyUnique: uniqueIndex("media_assets_r2_key_unique").on(table.r2Key)
39456
+ })
39457
+ );
38809
39458
  }
38810
39459
  });
38811
39460
 
@@ -40995,10 +41644,10 @@ var init_lessons2 = __esm({
40995
41644
 
40996
41645
  // src/commands/start.ts
40997
41646
  import { Command as Command8 } from "commander";
40998
- async function runStart(opts, deps = defaultDeps2) {
41647
+ async function runStart(opts, deps = defaultDeps3) {
40999
41648
  try {
41000
41649
  const session = await deps.requireSession();
41001
- const activeCourse = await deps.requireActiveCourse();
41650
+ const activeCourse = await deps.requireActiveCourse(void 0, { respectWorkspace: true });
41002
41651
  const onboarding = await deps.getCourseOnboardingStatus(activeCourse);
41003
41652
  if (!onboarding.initReady) {
41004
41653
  deps.stderrWrite(`Dica: rode \`tostudy init\` para personalizar o tutor ao seu contexto.
@@ -41027,7 +41676,7 @@ async function runStart(opts, deps = defaultDeps2) {
41027
41676
  deps.error(msg);
41028
41677
  }
41029
41678
  }
41030
- var logger7, defaultDeps2, startCommand;
41679
+ var logger9, defaultDeps3, startCommand;
41031
41680
  var init_start = __esm({
41032
41681
  "src/commands/start.ts"() {
41033
41682
  "use strict";
@@ -41037,8 +41686,8 @@ var init_start = __esm({
41037
41686
  init_session();
41038
41687
  init_status();
41039
41688
  init_formatter();
41040
- logger7 = createLogger("cli:start");
41041
- defaultDeps2 = {
41689
+ logger9 = createLogger("cli:start");
41690
+ defaultDeps3 = {
41042
41691
  requireSession,
41043
41692
  requireActiveCourse,
41044
41693
  checkCourseDrift,
@@ -41050,7 +41699,7 @@ var init_start = __esm({
41050
41699
  output,
41051
41700
  error,
41052
41701
  stderrWrite: (message) => process.stderr.write(message),
41053
- logger: logger7
41702
+ logger: logger9
41054
41703
  };
41055
41704
  startCommand = new Command8("start").description("Start (or resume) the current module of the active course").option("--json", "Output structured JSON").action(async (opts) => {
41056
41705
  await runStart(opts);
@@ -41060,7 +41709,7 @@ var init_start = __esm({
41060
41709
 
41061
41710
  // src/commands/start-next.ts
41062
41711
  import { Command as Command9 } from "commander";
41063
- var logger8, startNextCommand;
41712
+ var logger10, startNextCommand;
41064
41713
  var init_start_next = __esm({
41065
41714
  "src/commands/start-next.ts"() {
41066
41715
  "use strict";
@@ -41069,7 +41718,7 @@ var init_start_next = __esm({
41069
41718
  init_http2();
41070
41719
  init_session();
41071
41720
  init_formatter();
41072
- logger8 = createLogger("cli:start-next");
41721
+ logger10 = createLogger("cli:start-next");
41073
41722
  startNextCommand = new Command9("start-next").description("Transition to the next module after completing the current one").option("--json", "Output structured JSON").action(async (opts) => {
41074
41723
  try {
41075
41724
  const session = await requireSession();
@@ -41077,7 +41726,7 @@ var init_start_next = __esm({
41077
41726
  const driftWarning = await checkCourseDrift();
41078
41727
  if (driftWarning) process.stderr.write(driftWarning + "\n");
41079
41728
  const data = createHttpProvider(session.apiUrl, session.token);
41080
- const deps = { data, logger: logger8 };
41729
+ const deps = { data, logger: logger10 };
41081
41730
  const moduleData = await startNextModule({ enrollmentId: activeCourse.enrollmentId }, deps);
41082
41731
  await setActiveCourse({ ...activeCourse, currentLessonId: moduleData.firstLesson.id });
41083
41732
  if (opts.json) {
@@ -41100,7 +41749,7 @@ var init_start_next = __esm({
41100
41749
 
41101
41750
  // src/commands/next.ts
41102
41751
  import { Command as Command10 } from "commander";
41103
- var logger9, nextCommand;
41752
+ var logger11, nextCommand;
41104
41753
  var init_next = __esm({
41105
41754
  "src/commands/next.ts"() {
41106
41755
  "use strict";
@@ -41109,15 +41758,15 @@ var init_next = __esm({
41109
41758
  init_http2();
41110
41759
  init_session();
41111
41760
  init_formatter();
41112
- logger9 = createLogger("cli:next");
41761
+ logger11 = createLogger("cli:next");
41113
41762
  nextCommand = new Command10("next").description("Advance to the next lesson in the active course").option("--json", "Output structured JSON").action(async (opts) => {
41114
41763
  try {
41115
41764
  const session = await requireSession();
41116
- const activeCourse = await requireActiveCourse();
41765
+ const activeCourse = await requireActiveCourse(void 0, { respectWorkspace: true });
41117
41766
  const driftWarning = await checkCourseDrift();
41118
41767
  if (driftWarning) process.stderr.write(driftWarning + "\n");
41119
41768
  const data = createHttpProvider(session.apiUrl, session.token);
41120
- const deps = { data, logger: logger9 };
41769
+ const deps = { data, logger: logger11 };
41121
41770
  const lessonData = await nextLesson(
41122
41771
  { enrollmentId: activeCourse.enrollmentId, userConfirmation: "cli-next" },
41123
41772
  deps
@@ -41198,7 +41847,7 @@ function formatLessonContent(data) {
41198
41847
  }
41199
41848
  return lines.join("\n");
41200
41849
  }
41201
- var logger10, lessonCommand;
41850
+ var logger12, lessonCommand;
41202
41851
  var init_lesson = __esm({
41203
41852
  "src/commands/lesson.ts"() {
41204
41853
  "use strict";
@@ -41208,15 +41857,15 @@ var init_lesson = __esm({
41208
41857
  init_session();
41209
41858
  init_formatter();
41210
41859
  init_resolve();
41211
- logger10 = createLogger("cli:lesson");
41860
+ logger12 = createLogger("cli:lesson");
41212
41861
  lessonCommand = new Command11("lesson").description("Show the content of the current lesson").option("--json", "Output structured JSON").action(async (opts) => {
41213
41862
  try {
41214
41863
  const session = await requireSession();
41215
- const activeCourse = await requireActiveCourse();
41864
+ const activeCourse = await requireActiveCourse(void 0, { respectWorkspace: true });
41216
41865
  const driftWarning = await checkCourseDrift();
41217
41866
  if (driftWarning) process.stderr.write(driftWarning + "\n");
41218
41867
  const data = createHttpProvider(session.apiUrl, session.token);
41219
- const deps = { data, logger: logger10 };
41868
+ const deps = { data, logger: logger12 };
41220
41869
  const lessonId = activeCourse.currentLessonId;
41221
41870
  if (!lessonId) {
41222
41871
  error("Nenhuma li\xE7\xE3o ativa encontrada. Rode: tostudy start ou tostudy next");
@@ -41249,7 +41898,7 @@ var init_lesson = __esm({
41249
41898
 
41250
41899
  // src/commands/hint.ts
41251
41900
  import { Command as Command12 } from "commander";
41252
- var logger11, hintCommand;
41901
+ var logger13, hintCommand;
41253
41902
  var init_hint = __esm({
41254
41903
  "src/commands/hint.ts"() {
41255
41904
  "use strict";
@@ -41258,15 +41907,15 @@ var init_hint = __esm({
41258
41907
  init_http2();
41259
41908
  init_session();
41260
41909
  init_formatter();
41261
- logger11 = createLogger("cli:hint");
41910
+ logger13 = createLogger("cli:hint");
41262
41911
  hintCommand = new Command12("hint").description("Get a progressive hint for the current exercise").option("--json", "Output structured JSON").action(async (opts) => {
41263
41912
  try {
41264
41913
  const session = await requireSession();
41265
- const activeCourse = await requireActiveCourse();
41914
+ const activeCourse = await requireActiveCourse(void 0, { respectWorkspace: true });
41266
41915
  const driftWarning = await checkCourseDrift();
41267
41916
  if (driftWarning) process.stderr.write(driftWarning + "\n");
41268
41917
  const data = createHttpProvider(session.apiUrl, session.token);
41269
- const deps = { data, logger: logger11 };
41918
+ const deps = { data, logger: logger13 };
41270
41919
  const hint = await getHint(
41271
41920
  { userId: session.userId, enrollmentId: activeCourse.enrollmentId },
41272
41921
  deps
@@ -41312,10 +41961,10 @@ var init_exercises = __esm({
41312
41961
  });
41313
41962
 
41314
41963
  // src/commands/validate.ts
41315
- import fs7 from "node:fs";
41316
- import path6 from "node:path";
41964
+ import fs9 from "node:fs";
41965
+ import path9 from "node:path";
41317
41966
  import { Command as Command13 } from "commander";
41318
- var logger12, validateCommand;
41967
+ var logger14, validateCommand;
41319
41968
  var init_validate = __esm({
41320
41969
  "src/commands/validate.ts"() {
41321
41970
  "use strict";
@@ -41325,11 +41974,11 @@ var init_validate = __esm({
41325
41974
  init_session();
41326
41975
  init_formatter();
41327
41976
  init_init_template();
41328
- logger12 = createLogger("cli:validate");
41977
+ logger14 = createLogger("cli:validate");
41329
41978
  validateCommand = new Command13("validate").description("Validate your solution for the current exercise").argument("[file]", "Path to the solution file to read").option("--stdin", "Read solution from stdin instead of a file").option("--json", "Output structured JSON").action(async (file2, opts) => {
41330
41979
  try {
41331
41980
  const session = await requireSession();
41332
- const activeCourse = await requireActiveCourse();
41981
+ const activeCourse = await requireActiveCourse(void 0, { respectWorkspace: true });
41333
41982
  const driftWarning = await checkCourseDrift();
41334
41983
  if (driftWarning) process.stderr.write(driftWarning + "\n");
41335
41984
  const lessonId = activeCourse.currentLessonId;
@@ -41338,17 +41987,17 @@ var init_validate = __esm({
41338
41987
  }
41339
41988
  let solution;
41340
41989
  if (opts.stdin) {
41341
- solution = fs7.readFileSync("/dev/stdin", "utf-8");
41990
+ solution = fs9.readFileSync("/dev/stdin", "utf-8");
41342
41991
  } else if (file2) {
41343
- if (!fs7.existsSync(file2)) {
41992
+ if (!fs9.existsSync(file2)) {
41344
41993
  error(`Arquivo n\xE3o encontrado: ${file2}`);
41345
41994
  }
41346
- solution = fs7.readFileSync(file2, "utf-8");
41995
+ solution = fs9.readFileSync(file2, "utf-8");
41347
41996
  } else {
41348
41997
  error("Forne\xE7a um arquivo ou use --stdin.\n\nExemplo: tostudy validate resposta.md");
41349
41998
  }
41350
41999
  if (file2 && activeCourse.courseTags?.length) {
41351
- const ext = path6.extname(file2).toLowerCase();
42000
+ const ext = path9.extname(file2).toLowerCase();
41352
42001
  const LANG_EXTENSIONS = {
41353
42002
  ".html": ["html", "html5"],
41354
42003
  ".css": ["css"],
@@ -41384,7 +42033,7 @@ var init_validate = __esm({
41384
42033
  }
41385
42034
  }
41386
42035
  const data = createHttpProvider(session.apiUrl, session.token);
41387
- const deps = { data, logger: logger12 };
42036
+ const deps = { data, logger: logger14 };
41388
42037
  const result = await validateSolution(
41389
42038
  {
41390
42039
  lessonId,
@@ -41473,9 +42122,9 @@ var init_menu = __esm({
41473
42122
  });
41474
42123
 
41475
42124
  // src/onboarding/learner-context.ts
41476
- import readline from "node:readline/promises";
42125
+ import readline2 from "node:readline/promises";
41477
42126
  import { stdin as input, stdout as output2 } from "node:process";
41478
- async function askNonEmpty(question, deps, defaultValue) {
42127
+ async function askNonEmpty2(question, deps, defaultValue) {
41479
42128
  while (true) {
41480
42129
  const answer = (await deps.ask(question)).trim();
41481
42130
  if (answer.length > 0) return answer;
@@ -41491,7 +42140,7 @@ async function askChoice(question, choices, deps, defaultChoice) {
41491
42140
  }
41492
42141
  }
41493
42142
  function createPromptDeps() {
41494
- const rl = readline.createInterface({ input, output: output2 });
42143
+ const rl = readline2.createInterface({ input, output: output2 });
41495
42144
  return {
41496
42145
  ask: (question) => rl.question(question),
41497
42146
  write: (content) => output2.write(content),
@@ -41524,32 +42173,32 @@ async function collectLearnerContextProfileWithDeps(input2, deps) {
41524
42173
  "keep"
41525
42174
  ) : "edit";
41526
42175
  const baseProfile = existingProfile;
41527
- const segment = action === "keep" && baseProfile ? baseProfile.segment : await askNonEmpty(
42176
+ const segment = action === "keep" && baseProfile ? baseProfile.segment : await askNonEmpty2(
41528
42177
  baseProfile ? `Segmento ou nicho do aluno [${baseProfile.segment}]: ` : "Segmento ou nicho do aluno: ",
41529
42178
  deps,
41530
42179
  baseProfile?.segment
41531
42180
  );
41532
- const company = action === "keep" && baseProfile ? baseProfile.company : await askNonEmpty(
42181
+ const company = action === "keep" && baseProfile ? baseProfile.company : await askNonEmpty2(
41533
42182
  baseProfile ? `Empresa ou tipo de negocio [${baseProfile.company}]: ` : "Empresa ou tipo de negocio: ",
41534
42183
  deps,
41535
42184
  baseProfile?.company
41536
42185
  );
41537
- const productsOrServices = action === "keep" && baseProfile ? baseProfile.productsOrServices : await askNonEmpty(
42186
+ const productsOrServices = action === "keep" && baseProfile ? baseProfile.productsOrServices : await askNonEmpty2(
41538
42187
  baseProfile ? `Produtos ou servicos principais [${baseProfile.productsOrServices}]: ` : "Produtos ou servicos principais: ",
41539
42188
  deps,
41540
42189
  baseProfile?.productsOrServices
41541
42190
  );
41542
- const region = action === "keep" && baseProfile ? baseProfile.region : await askNonEmpty(
42191
+ const region = action === "keep" && baseProfile ? baseProfile.region : await askNonEmpty2(
41543
42192
  baseProfile ? `Regiao de atuacao [${baseProfile.region}]: ` : "Regiao de atuacao: ",
41544
42193
  deps,
41545
42194
  baseProfile?.region
41546
42195
  );
41547
- const team = action === "keep" && baseProfile ? baseProfile.team : await askNonEmpty(
42196
+ const team = action === "keep" && baseProfile ? baseProfile.team : await askNonEmpty2(
41548
42197
  baseProfile ? `Equipe envolvida neste contexto [${baseProfile.team}]: ` : "Equipe envolvida neste contexto: ",
41549
42198
  deps,
41550
42199
  baseProfile?.team
41551
42200
  );
41552
- const goal = action === "keep" && baseProfile ? baseProfile.goal : await askNonEmpty(
42201
+ const goal = action === "keep" && baseProfile ? baseProfile.goal : await askNonEmpty2(
41553
42202
  baseProfile ? `Objetivo principal com este curso [${baseProfile.goal}]: ` : "Objetivo principal com este curso: ",
41554
42203
  deps,
41555
42204
  baseProfile?.goal
@@ -41598,7 +42247,7 @@ function isCompleteProfile(flags) {
41598
42247
  flags.segment && flags.company && flags.products && flags.region && flags.team && flags.goal && flags.level
41599
42248
  );
41600
42249
  }
41601
- async function runInit(deps = defaultDeps3, flags = {}) {
42250
+ async function runInit(deps = defaultDeps4, flags = {}) {
41602
42251
  const session = await deps.getSession();
41603
42252
  if (!session) {
41604
42253
  deps.output("Nao autenticado. Rode `tostudy login` para comecar.", { json: false });
@@ -41713,22 +42362,20 @@ Rode \`tostudy select <n\xFAmero>\` para ativar um curso.`,
41713
42362
  });
41714
42363
  await deps.saveCourseLearnerProfile(activeCourse, learnerProfile, artifacts);
41715
42364
  try {
41716
- generateInstructionFiles(
41717
- {
41718
- courseTitle: matchedCourse.title,
41719
- courseId: activeCourse.courseId,
41720
- progress: progressData?.coursePercent ?? matchedCourse.progress ?? 0,
41721
- moduleCount: progressData?.currentModule?.totalModules ?? 0,
41722
- lessonCount: progressData?.currentLesson?.totalLessons ?? 0,
41723
- currentModuleTitle: progressData?.currentModule?.title,
41724
- currentLessonTitle: progressData?.currentLesson?.title,
41725
- courseDescription: matchedCourse.description ?? void 0
41726
- },
41727
- learnerProfile
42365
+ const pipelineResult = await deps.resolveAndGenerate(
42366
+ { courseId: activeCourse.courseId, enrollmentId: activeCourse.enrollmentId },
42367
+ { forceRefresh: false, collectT1Interactive: true, collectT2Interactive: false },
42368
+ { cwd: process.cwd(), deps: deps.buildPipelineDeps() }
41728
42369
  );
41729
- deps.logger.info("Instruction files enriched with learner profile");
42370
+ deps.logger.info("Instruction files regenerated via pipeline", {
42371
+ wroteFiles: pipelineResult.wroteFiles,
42372
+ usedT0: pipelineResult.usedT0,
42373
+ usedT1: pipelineResult.usedT1,
42374
+ usedT2: pipelineResult.usedT2,
42375
+ bootstrapped: pipelineResult.bootstrapped
42376
+ });
41730
42377
  } catch (err) {
41731
- deps.logger.warn("Failed to update instruction files", {
42378
+ deps.logger.warn("Failed to run instruction pipeline", {
41732
42379
  error: err instanceof Error ? err.message : String(err)
41733
42380
  });
41734
42381
  }
@@ -41744,7 +42391,7 @@ Rode \`tostudy select <n\xFAmero>\` para ativar um curso.`,
41744
42391
  deps.output(artifacts.learnerBrief, { json: false });
41745
42392
  }
41746
42393
  }
41747
- var logger13, defaultDeps3, initCommand;
42394
+ var logger15, defaultDeps4, initCommand;
41748
42395
  var init_init = __esm({
41749
42396
  "src/commands/init.ts"() {
41750
42397
  "use strict";
@@ -41756,9 +42403,10 @@ var init_init = __esm({
41756
42403
  init_init_template();
41757
42404
  init_learner_context();
41758
42405
  init_api();
41759
- init_instruction_files();
41760
- logger13 = createLogger("cli:init");
41761
- defaultDeps3 = {
42406
+ init_instruction_pipeline();
42407
+ init_pipeline_deps();
42408
+ logger15 = createLogger("cli:init");
42409
+ defaultDeps4 = {
41762
42410
  getSession,
41763
42411
  getActiveCourse,
41764
42412
  listCourses,
@@ -41771,24 +42419,26 @@ var init_init = __esm({
41771
42419
  saveCourseLearnerProfile,
41772
42420
  buildInitArtifacts,
41773
42421
  output,
41774
- logger: logger13,
41775
- createHttpProvider
42422
+ logger: logger15,
42423
+ createHttpProvider,
42424
+ resolveAndGenerate,
42425
+ buildPipelineDeps
41776
42426
  };
41777
42427
  initCommand = new Command15("init").description("Generate tutor instructions and learner brief for the active course").option("--segment <segment>", "Learner segment/niche").option("--company <company>", "Company or business type").option("--products <products>", "Main products or services").option("--region <region>", "Operating region").option("--team <team>", "Team involved").option("--goal <goal>", "Primary learning goal").option("--level <level>", "Learner level: beginner, intermediate, advanced").option("--adapt-context", "Adapt examples to learner's real context").option("--json", "Output structured JSON").action(async (opts) => {
41778
- await runInit(defaultDeps3, opts);
42428
+ await runInit(defaultDeps4, opts);
41779
42429
  });
41780
42430
  }
41781
42431
  });
41782
42432
 
41783
42433
  // ../../packages/tostudy-core/src/workspace/setup-workspace.ts
41784
- import fs8 from "node:fs/promises";
41785
- import path7 from "node:path";
42434
+ import fs10 from "node:fs/promises";
42435
+ import path10 from "node:path";
41786
42436
  async function setupWorkspace(input2) {
41787
- const workspacePath = path7.join(input2.basePath, input2.courseSlug);
42437
+ const workspacePath = path10.join(input2.basePath, input2.courseSlug);
41788
42438
  for (const dir of WORKSPACE_DIRS) {
41789
- await fs8.mkdir(path7.join(workspacePath, dir), { recursive: true });
42439
+ await fs10.mkdir(path10.join(workspacePath, dir), { recursive: true });
41790
42440
  }
41791
- const configPath = path7.join(workspacePath, ".ana-config.json");
42441
+ const configPath = path10.join(workspacePath, ".ana-config.json");
41792
42442
  const config2 = {
41793
42443
  courseId: input2.courseId,
41794
42444
  courseSlug: input2.courseSlug,
@@ -41798,7 +42448,7 @@ async function setupWorkspace(input2) {
41798
42448
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
41799
42449
  lastAccessedAt: (/* @__PURE__ */ new Date()).toISOString()
41800
42450
  };
41801
- await fs8.writeFile(configPath, JSON.stringify(config2, null, 2), "utf-8");
42451
+ await fs10.writeFile(configPath, JSON.stringify(config2, null, 2), "utf-8");
41802
42452
  const readme = [
41803
42453
  `# ${input2.courseName}`,
41804
42454
  "",
@@ -41823,7 +42473,7 @@ async function setupWorkspace(input2) {
41823
42473
  "tostudy vault sync # Sincronizar progresso",
41824
42474
  "```"
41825
42475
  ].join("\n");
41826
- await fs8.writeFile(path7.join(workspacePath, "README.md"), readme, "utf-8");
42476
+ await fs10.writeFile(path10.join(workspacePath, "README.md"), readme, "utf-8");
41827
42477
  return { workspacePath, directories: WORKSPACE_DIRS, configPath };
41828
42478
  }
41829
42479
  var WORKSPACE_DIRS;
@@ -41924,8 +42574,8 @@ var init_templates = __esm({
41924
42574
  });
41925
42575
 
41926
42576
  // ../../packages/tostudy-core/src/workspace/extract-exercise.ts
41927
- import fs9 from "node:fs/promises";
41928
- import path8 from "node:path";
42577
+ import fs11 from "node:fs/promises";
42578
+ import path11 from "node:path";
41929
42579
  function padOrder(n) {
41930
42580
  return String(n).padStart(2, "0");
41931
42581
  }
@@ -41949,16 +42599,16 @@ async function extractExercise(input2) {
41949
42599
  const { lessonData, exerciseTier, workspacePath } = input2;
41950
42600
  const moduleDir = `${padOrder(lessonData.moduleOrder)}-${lessonData.moduleSlug}`;
41951
42601
  const lessonDir = `${padOrder(lessonData.lessonOrder)}-${lessonData.lessonSlug}`;
41952
- const exercisePath = path8.join(workspacePath, "exercises", moduleDir, lessonDir);
41953
- await fs9.mkdir(exercisePath, { recursive: true });
42602
+ const exercisePath = path11.join(workspacePath, "exercises", moduleDir, lessonDir);
42603
+ await fs11.mkdir(exercisePath, { recursive: true });
41954
42604
  const extractedFiles = [];
41955
42605
  let hasStarterCode = false;
41956
42606
  if (lessonData.sandpackConfig?.files) {
41957
42607
  for (const [filePath, fileData] of Object.entries(lessonData.sandpackConfig.files)) {
41958
42608
  const cleanPath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
41959
- const fullPath = path8.join(exercisePath, cleanPath);
41960
- await fs9.mkdir(path8.dirname(fullPath), { recursive: true });
41961
- await fs9.writeFile(fullPath, fileData.code, "utf-8");
42609
+ const fullPath = path11.join(exercisePath, cleanPath);
42610
+ await fs11.mkdir(path11.dirname(fullPath), { recursive: true });
42611
+ await fs11.writeFile(fullPath, fileData.code, "utf-8");
41962
42612
  extractedFiles.push(cleanPath);
41963
42613
  hasStarterCode = true;
41964
42614
  }
@@ -41966,13 +42616,13 @@ async function extractExercise(input2) {
41966
42616
  const tierData = getTierData(lessonData.structuredData, exerciseTier);
41967
42617
  const tierCode = tierData?.code;
41968
42618
  if (tierCode) {
41969
- await fs9.writeFile(path8.join(exercisePath, "exercise.js"), tierCode, "utf-8");
42619
+ await fs11.writeFile(path11.join(exercisePath, "exercise.js"), tierCode, "utf-8");
41970
42620
  extractedFiles.push("exercise.js");
41971
42621
  hasStarterCode = true;
41972
42622
  } else {
41973
42623
  const starter = getStarterCode(lessonData.structuredData);
41974
42624
  if (starter) {
41975
- await fs9.writeFile(path8.join(exercisePath, "exercise.js"), starter, "utf-8");
42625
+ await fs11.writeFile(path11.join(exercisePath, "exercise.js"), starter, "utf-8");
41976
42626
  extractedFiles.push("exercise.js");
41977
42627
  hasStarterCode = true;
41978
42628
  }
@@ -41990,8 +42640,8 @@ async function extractExercise(input2) {
41990
42640
  ...exerciseDeps
41991
42641
  }
41992
42642
  };
41993
- await fs9.writeFile(
41994
- path8.join(exercisePath, "package.json"),
42643
+ await fs11.writeFile(
42644
+ path11.join(exercisePath, "package.json"),
41995
42645
  JSON.stringify(pkgJson, null, 2),
41996
42646
  "utf-8"
41997
42647
  );
@@ -42003,20 +42653,20 @@ async function extractExercise(input2) {
42003
42653
  );
42004
42654
  for (const [configFile, configContent] of Object.entries(scaffold.configs)) {
42005
42655
  if (!sandpackFileNames.has(configFile)) {
42006
- await fs9.writeFile(path8.join(exercisePath, configFile), configContent, "utf-8");
42656
+ await fs11.writeFile(path11.join(exercisePath, configFile), configContent, "utf-8");
42007
42657
  extractedFiles.push(configFile);
42008
42658
  }
42009
42659
  }
42010
42660
  const setupSh = `#!/bin/sh
42011
42661
  ${scaffold.setupScript}
42012
42662
  `;
42013
- await fs9.writeFile(path8.join(exercisePath, "setup.sh"), setupSh, "utf-8");
42663
+ await fs11.writeFile(path11.join(exercisePath, "setup.sh"), setupSh, "utf-8");
42014
42664
  extractedFiles.push("setup.sh");
42015
42665
  }
42016
42666
  }
42017
42667
  const readme = generateReadme(lessonData, exerciseTier);
42018
- const readmePath = path8.join(exercisePath, "README.md");
42019
- await fs9.writeFile(readmePath, readme, "utf-8");
42668
+ const readmePath = path11.join(exercisePath, "README.md");
42669
+ await fs11.writeFile(readmePath, readme, "utf-8");
42020
42670
  extractedFiles.push("README.md");
42021
42671
  return {
42022
42672
  exercisePath,
@@ -42088,10 +42738,10 @@ var init_workspace = __esm({
42088
42738
 
42089
42739
  // src/commands/workspace.ts
42090
42740
  import { Command as Command16 } from "commander";
42091
- import path9 from "node:path";
42092
- import os9 from "node:os";
42093
- import fs10 from "node:fs/promises";
42094
- var logger14, workspaceCommand;
42741
+ import path12 from "node:path";
42742
+ import os11 from "node:os";
42743
+ import fs12 from "node:fs/promises";
42744
+ var logger16, workspaceCommand;
42095
42745
  var init_workspace2 = __esm({
42096
42746
  "src/commands/workspace.ts"() {
42097
42747
  "use strict";
@@ -42099,7 +42749,7 @@ var init_workspace2 = __esm({
42099
42749
  init_workspace();
42100
42750
  init_session();
42101
42751
  init_resolve();
42102
- logger14 = createLogger("cli:workspace");
42752
+ logger16 = createLogger("cli:workspace");
42103
42753
  workspaceCommand = new Command16("workspace").description(
42104
42754
  "Gerenciar workspace de estudo local"
42105
42755
  );
@@ -42112,14 +42762,14 @@ var init_workspace2 = __esm({
42112
42762
  let directories;
42113
42763
  if (!opts.path && cwdIsWorkspace) {
42114
42764
  const resolvedCwd = await resolveCwdWorkspacePath(process.cwd());
42115
- workspacePath = resolvedCwd ?? path9.join(process.cwd(), ".tostudy");
42116
- await fs10.mkdir(workspacePath, { recursive: true });
42765
+ workspacePath = resolvedCwd ?? path12.join(process.cwd(), ".tostudy");
42766
+ await fs12.mkdir(workspacePath, { recursive: true });
42117
42767
  directories = ["exercises", "generated", "notes", "diagrams"];
42118
42768
  for (const dir of directories) {
42119
- await fs10.mkdir(path9.join(workspacePath, dir), { recursive: true });
42769
+ await fs12.mkdir(path12.join(workspacePath, dir), { recursive: true });
42120
42770
  }
42121
- const configPath = path9.join(workspacePath, ".ana-config.json");
42122
- await fs10.writeFile(
42771
+ const configPath = path12.join(workspacePath, ".ana-config.json");
42772
+ await fs12.writeFile(
42123
42773
  configPath,
42124
42774
  JSON.stringify(
42125
42775
  {
@@ -42137,7 +42787,7 @@ var init_workspace2 = __esm({
42137
42787
  "utf-8"
42138
42788
  );
42139
42789
  } else {
42140
- const basePath = opts.path ?? path9.join(os9.homedir(), "study");
42790
+ const basePath = opts.path ?? path12.join(os11.homedir(), "study");
42141
42791
  const result2 = await setupWorkspace({
42142
42792
  courseId: activeCourse.courseId,
42143
42793
  courseSlug: courseSlug(activeCourse.courseTitle),
@@ -42165,13 +42815,13 @@ Pr\xF3ximo passo: tostudy export
42165
42815
  );
42166
42816
  }
42167
42817
  } catch (err) {
42168
- logger14.error("workspace setup failed", { error: err });
42818
+ logger16.error("workspace setup failed", { error: err });
42169
42819
  process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
42170
42820
  `);
42171
42821
  process.exit(1);
42172
42822
  }
42173
42823
  });
42174
- workspaceCommand.command("status").description("Mostrar status do workspace do curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path9.join(os9.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
42824
+ workspaceCommand.command("status").description("Mostrar status do workspace do curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path12.join(os11.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
42175
42825
  try {
42176
42826
  const activeCourse = await requireActiveCourse();
42177
42827
  const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
@@ -42190,40 +42840,40 @@ Pr\xF3ximo passo: tostudy export
42190
42840
  const workspacePath = ws.workspacePath;
42191
42841
  let configData = null;
42192
42842
  try {
42193
- const raw = await fs10.readFile(path9.join(workspacePath, ".ana-config.json"), "utf-8");
42843
+ const raw = await fs12.readFile(path12.join(workspacePath, ".ana-config.json"), "utf-8");
42194
42844
  configData = JSON.parse(raw);
42195
42845
  } catch {
42196
42846
  configData = null;
42197
42847
  }
42198
- const exercisesDir = path9.join(workspacePath, "exercises");
42848
+ const exercisesDir = path12.join(workspacePath, "exercises");
42199
42849
  let exerciseCount = 0;
42200
42850
  try {
42201
- const moduleDirs = await fs10.readdir(exercisesDir);
42851
+ const moduleDirs = await fs12.readdir(exercisesDir);
42202
42852
  for (const modDir of moduleDirs) {
42203
- const modPath = path9.join(exercisesDir, modDir);
42204
- const stat = await fs10.stat(modPath);
42853
+ const modPath = path12.join(exercisesDir, modDir);
42854
+ const stat = await fs12.stat(modPath);
42205
42855
  if (stat.isDirectory()) {
42206
- const lessonDirs = await fs10.readdir(modPath);
42856
+ const lessonDirs = await fs12.readdir(modPath);
42207
42857
  for (const lessonDir of lessonDirs) {
42208
- const lessonPath = path9.join(modPath, lessonDir);
42209
- const lstat = await fs10.stat(lessonPath);
42858
+ const lessonPath = path12.join(modPath, lessonDir);
42859
+ const lstat = await fs12.stat(lessonPath);
42210
42860
  if (lstat.isDirectory()) exerciseCount++;
42211
42861
  }
42212
42862
  }
42213
42863
  }
42214
42864
  } catch {
42215
42865
  }
42216
- const generatedDir = path9.join(workspacePath, "generated");
42866
+ const generatedDir = path12.join(workspacePath, "generated");
42217
42867
  let artifactCount = 0;
42218
42868
  try {
42219
- const files = await fs10.readdir(generatedDir);
42869
+ const files = await fs12.readdir(generatedDir);
42220
42870
  artifactCount = files.length;
42221
42871
  } catch {
42222
42872
  }
42223
- const diagramsDir = path9.join(workspacePath, "diagrams");
42873
+ const diagramsDir = path12.join(workspacePath, "diagrams");
42224
42874
  let diagramCount = 0;
42225
42875
  try {
42226
- const files = await fs10.readdir(diagramsDir);
42876
+ const files = await fs12.readdir(diagramsDir);
42227
42877
  diagramCount = files.length;
42228
42878
  } catch {
42229
42879
  }
@@ -42272,10 +42922,10 @@ Pr\xF3ximo passo: tostudy export
42272
42922
 
42273
42923
  // src/commands/export.ts
42274
42924
  import { Command as Command17 } from "commander";
42275
- import path10 from "node:path";
42276
- import os10 from "node:os";
42277
- import fs11 from "node:fs/promises";
42278
- var logger15, exportCommand;
42925
+ import path13 from "node:path";
42926
+ import os12 from "node:os";
42927
+ import fs13 from "node:fs/promises";
42928
+ var logger17, exportCommand;
42279
42929
  var init_export = __esm({
42280
42930
  "src/commands/export.ts"() {
42281
42931
  "use strict";
@@ -42284,8 +42934,8 @@ var init_export = __esm({
42284
42934
  init_http2();
42285
42935
  init_session();
42286
42936
  init_resolve();
42287
- logger15 = createLogger("cli:export");
42288
- exportCommand = new Command17("export").description("Extrair exerc\xEDcio atual para o workspace local").option("--tier <tier>", "Tier do exerc\xEDcio: guided, semiGuided, challenging", "guided").option("--path <dir>", "Diret\xF3rio base do workspace", path10.join(os10.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
42937
+ logger17 = createLogger("cli:export");
42938
+ exportCommand = new Command17("export").description("Extrair exerc\xEDcio atual para o workspace local").option("--tier <tier>", "Tier do exerc\xEDcio: guided, semiGuided, challenging", "guided").option("--path <dir>", "Diret\xF3rio base do workspace", path13.join(os12.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
42289
42939
  try {
42290
42940
  const session = await requireSession();
42291
42941
  const activeCourse = await requireActiveCourse();
@@ -42303,21 +42953,21 @@ var init_export = __esm({
42303
42953
  opts.path
42304
42954
  );
42305
42955
  if (ws.found && ws.source === "cwd" && ws.workspacePath) {
42306
- const configPath = path10.join(ws.workspacePath, ".ana-config.json");
42956
+ const configPath = path13.join(ws.workspacePath, ".ana-config.json");
42307
42957
  let hasConfig = false;
42308
42958
  try {
42309
- await fs11.access(configPath);
42959
+ await fs13.access(configPath);
42310
42960
  hasConfig = true;
42311
42961
  } catch {
42312
42962
  }
42313
42963
  if (!hasConfig) {
42314
42964
  const slug = courseSlug(activeCourse.courseTitle);
42315
- logger15.info("Auto-initializing workspace", { workspacePath: ws.workspacePath });
42316
- await fs11.mkdir(ws.workspacePath, { recursive: true });
42965
+ logger17.info("Auto-initializing workspace", { workspacePath: ws.workspacePath });
42966
+ await fs13.mkdir(ws.workspacePath, { recursive: true });
42317
42967
  for (const dir of ["exercises", "generated", "notes", "diagrams"]) {
42318
- await fs11.mkdir(path10.join(ws.workspacePath, dir), { recursive: true });
42968
+ await fs13.mkdir(path13.join(ws.workspacePath, dir), { recursive: true });
42319
42969
  }
42320
- await fs11.writeFile(
42970
+ await fs13.writeFile(
42321
42971
  configPath,
42322
42972
  JSON.stringify(
42323
42973
  {
@@ -42335,7 +42985,7 @@ var init_export = __esm({
42335
42985
  "utf-8"
42336
42986
  );
42337
42987
  await setCourseWorkspacePath(activeCourse.courseId, ws.workspacePath);
42338
- const isNamespaced = ws.workspacePath === path10.join(process.cwd(), ".tostudy");
42988
+ const isNamespaced = ws.workspacePath === path13.join(process.cwd(), ".tostudy");
42339
42989
  process.stderr.write(
42340
42990
  isNamespaced ? `\u2728 Workspace inicializado em .tostudy/ (isolado do projeto).
42341
42991
  ` : `\u2728 Workspace inicializado nesta pasta.
@@ -42376,7 +43026,7 @@ ${result.files.map((f) => ` \u{1F4C4} ${f}`).join("\n")}
42376
43026
  );
42377
43027
  }
42378
43028
  } catch (err) {
42379
- logger15.error("export failed", { error: err });
43029
+ logger17.error("export failed", { error: err });
42380
43030
  process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
42381
43031
  `);
42382
43032
  process.exit(1);
@@ -42388,17 +43038,17 @@ ${result.files.map((f) => ` \u{1F4C4} ${f}`).join("\n")}
42388
43038
  // src/commands/open.ts
42389
43039
  import { Command as Command18 } from "commander";
42390
43040
  import { execFile as execFile3 } from "node:child_process";
42391
- import path11 from "node:path";
42392
- import os11 from "node:os";
42393
- var logger16, openCommand;
43041
+ import path14 from "node:path";
43042
+ import os13 from "node:os";
43043
+ var logger18, openCommand;
42394
43044
  var init_open = __esm({
42395
43045
  "src/commands/open.ts"() {
42396
43046
  "use strict";
42397
43047
  init_src();
42398
43048
  init_session();
42399
43049
  init_resolve();
42400
- logger16 = createLogger("cli:open");
42401
- openCommand = new Command18("open").description("Abrir workspace do curso na IDE").option("--path <dir>", "Diret\xF3rio base do workspace", path11.join(os11.homedir(), "study")).action(async (opts) => {
43050
+ logger18 = createLogger("cli:open");
43051
+ openCommand = new Command18("open").description("Abrir workspace do curso na IDE").option("--path <dir>", "Diret\xF3rio base do workspace", path14.join(os13.homedir(), "study")).action(async (opts) => {
42402
43052
  try {
42403
43053
  const activeCourse = await requireActiveCourse();
42404
43054
  const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
@@ -42417,7 +43067,7 @@ var init_open = __esm({
42417
43067
  const editor = process.env["EDITOR"] ?? "code";
42418
43068
  execFile3(editor, [ws.workspacePath], (err) => {
42419
43069
  if (err) {
42420
- logger16.error("open failed", { editor, workspacePath: ws.workspacePath });
43070
+ logger18.error("open failed", { editor, workspacePath: ws.workspacePath });
42421
43071
  process.stderr.write(`\u274C Falha ao abrir: ${err.message}
42422
43072
  `);
42423
43073
  process.exit(1);
@@ -42426,7 +43076,7 @@ var init_open = __esm({
42426
43076
  `);
42427
43077
  });
42428
43078
  } catch (err) {
42429
- logger16.error("open command failed", { error: err });
43079
+ logger18.error("open command failed", { error: err });
42430
43080
  process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
42431
43081
  `);
42432
43082
  process.exit(1);
@@ -42453,24 +43103,24 @@ var init_types3 = __esm({
42453
43103
  });
42454
43104
 
42455
43105
  // ../../packages/tostudy-core/src/vault/write-vault.ts
42456
- import fs12 from "node:fs/promises";
42457
- import path12 from "node:path";
43106
+ import fs14 from "node:fs/promises";
43107
+ import path15 from "node:path";
42458
43108
  async function writeVaultFiles(files, outputPath, courseId, courseSlug2) {
42459
43109
  for (const file2 of files) {
42460
- const fullPath = path12.join(outputPath, file2.relativePath);
42461
- await fs12.mkdir(path12.dirname(fullPath), { recursive: true });
42462
- await fs12.writeFile(fullPath, file2.content, "utf-8");
43110
+ const fullPath = path15.join(outputPath, file2.relativePath);
43111
+ await fs14.mkdir(path15.dirname(fullPath), { recursive: true });
43112
+ await fs14.writeFile(fullPath, file2.content, "utf-8");
42463
43113
  }
42464
- const vaultPath = path12.join(outputPath, courseSlug2);
43114
+ const vaultPath = path15.join(outputPath, courseSlug2);
42465
43115
  const marker = {
42466
43116
  courseId,
42467
43117
  courseSlug: courseSlug2,
42468
43118
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
42469
43119
  version: VAULT_MARKER_VERSION
42470
43120
  };
42471
- await fs12.mkdir(vaultPath, { recursive: true });
42472
- await fs12.writeFile(
42473
- path12.join(vaultPath, VAULT_MARKER_FILENAME),
43121
+ await fs14.mkdir(vaultPath, { recursive: true });
43122
+ await fs14.writeFile(
43123
+ path15.join(vaultPath, VAULT_MARKER_FILENAME),
42474
43124
  JSON.stringify(marker, null, 2),
42475
43125
  "utf-8"
42476
43126
  );
@@ -42495,10 +43145,10 @@ var init_vault = __esm({
42495
43145
 
42496
43146
  // src/commands/vault.ts
42497
43147
  import { Command as Command19 } from "commander";
42498
- import path13 from "node:path";
42499
- import os12 from "node:os";
42500
- import fs13 from "node:fs/promises";
42501
- var logger17, vaultCommand;
43148
+ import path16 from "node:path";
43149
+ import os14 from "node:os";
43150
+ import fs15 from "node:fs/promises";
43151
+ var logger19, vaultCommand;
42502
43152
  var init_vault2 = __esm({
42503
43153
  "src/commands/vault.ts"() {
42504
43154
  "use strict";
@@ -42508,9 +43158,9 @@ var init_vault2 = __esm({
42508
43158
  init_http2();
42509
43159
  init_session();
42510
43160
  init_resolve();
42511
- logger17 = createLogger("cli:vault");
43161
+ logger19 = createLogger("cli:vault");
42512
43162
  vaultCommand = new Command19("vault").description("Gerenciar vault Obsidian do curso");
42513
- vaultCommand.command("init").description("Gerar vault Obsidian para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path13.join(os12.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
43163
+ vaultCommand.command("init").description("Gerar vault Obsidian para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path16.join(os14.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
42514
43164
  try {
42515
43165
  const session = await requireSession();
42516
43166
  const activeCourse = await requireActiveCourse();
@@ -42555,7 +43205,7 @@ var init_vault2 = __esm({
42555
43205
  activeCourse.courseId,
42556
43206
  slug
42557
43207
  );
42558
- logger17.info("Vault generated", {
43208
+ logger19.info("Vault generated", {
42559
43209
  courseId: activeCourse.courseId,
42560
43210
  vaultPath: result.vaultPath,
42561
43211
  filesWritten: result.filesWritten
@@ -42588,13 +43238,13 @@ Para visualizar:
42588
43238
  );
42589
43239
  }
42590
43240
  } catch (err) {
42591
- logger17.error("vault init failed", { error: err });
43241
+ logger19.error("vault init failed", { error: err });
42592
43242
  process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
42593
43243
  `);
42594
43244
  process.exit(1);
42595
43245
  }
42596
43246
  });
42597
- vaultCommand.command("sync").description("Sincronizar progresso do curso com o vault local").option("--path <dir>", "Diret\xF3rio base do workspace", path13.join(os12.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
43247
+ vaultCommand.command("sync").description("Sincronizar progresso do curso com o vault local").option("--path <dir>", "Diret\xF3rio base do workspace", path16.join(os14.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
42598
43248
  try {
42599
43249
  const session = await requireSession();
42600
43250
  const activeCourse = await requireActiveCourse();
@@ -42620,10 +43270,10 @@ Para visualizar:
42620
43270
  process.exit(1);
42621
43271
  }
42622
43272
  const data = createHttpProvider(session.apiUrl, session.token);
42623
- const deps = { data, logger: logger17 };
43273
+ const deps = { data, logger: logger19 };
42624
43274
  const progress3 = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
42625
- const markerPath = path13.join(vaultPath, ".ana-vault.json");
42626
- const markerRaw = await fs13.readFile(markerPath, "utf-8");
43275
+ const markerPath = path16.join(vaultPath, ".ana-vault.json");
43276
+ const markerRaw = await fs15.readFile(markerPath, "utf-8");
42627
43277
  const marker = JSON.parse(markerRaw);
42628
43278
  marker.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
42629
43279
  marker.progress = {
@@ -42631,10 +43281,10 @@ Para visualizar:
42631
43281
  currentModule: progress3.currentModule.title,
42632
43282
  currentLesson: progress3.currentLesson.title
42633
43283
  };
42634
- await fs13.writeFile(markerPath, JSON.stringify(marker, null, 2), "utf-8");
42635
- const courseIndexPath = path13.join(vaultPath, slug, "index.md");
43284
+ await fs15.writeFile(markerPath, JSON.stringify(marker, null, 2), "utf-8");
43285
+ const courseIndexPath = path16.join(vaultPath, slug, "index.md");
42636
43286
  try {
42637
- let indexContent = await fs13.readFile(courseIndexPath, "utf-8");
43287
+ let indexContent = await fs15.readFile(courseIndexPath, "utf-8");
42638
43288
  indexContent = indexContent.replace(/\n---\n\n> 📊 Progresso:.*\n/g, "");
42639
43289
  const titleEnd = indexContent.indexOf("\n");
42640
43290
  if (titleEnd !== -1) {
@@ -42645,7 +43295,7 @@ Para visualizar:
42645
43295
  `;
42646
43296
  indexContent = indexContent.slice(0, titleEnd) + banner + indexContent.slice(titleEnd);
42647
43297
  }
42648
- await fs13.writeFile(courseIndexPath, indexContent, "utf-8");
43298
+ await fs15.writeFile(courseIndexPath, indexContent, "utf-8");
42649
43299
  } catch {
42650
43300
  }
42651
43301
  const syncedAt = marker.lastSyncedAt;
@@ -42674,7 +43324,7 @@ Para visualizar:
42674
43324
  );
42675
43325
  }
42676
43326
  } catch (err) {
42677
- logger17.error("vault sync failed", { error: err });
43327
+ logger19.error("vault sync failed", { error: err });
42678
43328
  process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
42679
43329
  `);
42680
43330
  process.exit(1);
@@ -42752,83 +43402,204 @@ var init_profile = __esm({
42752
43402
 
42753
43403
  // src/commands/sync.ts
42754
43404
  import { Command as Command21 } from "commander";
42755
- var logger18, syncCommand;
43405
+ var logger20, syncCommand;
42756
43406
  var init_sync = __esm({
42757
43407
  "src/commands/sync.ts"() {
42758
43408
  "use strict";
42759
43409
  init_src();
42760
- init_courses();
42761
- init_http2();
42762
43410
  init_session();
42763
43411
  init_formatter();
42764
- init_instruction_files();
42765
- init_status();
42766
- logger18 = createLogger("cli:sync");
43412
+ init_instruction_pipeline();
43413
+ init_pipeline_deps();
43414
+ logger20 = createLogger("cli:sync");
42767
43415
  syncCommand = new Command21("sync").description("Regenerate instruction files with updated progress").option("--json", "Output structured JSON").action(async (opts) => {
42768
43416
  try {
42769
- const session = await requireSession();
43417
+ await requireSession();
42770
43418
  const activeCourse = await requireActiveCourse();
42771
- const data = createHttpProvider(session.apiUrl, session.token);
42772
- const deps = { data, logger: logger18 };
42773
- const [courses3, progressData] = await Promise.all([
42774
- listCourses({ userId: session.userId }, deps),
42775
- getProgress({ enrollmentId: activeCourse.enrollmentId }, deps).catch(() => null)
42776
- ]);
42777
- const matchedCourse = courses3.find((c) => c.courseId === activeCourse.courseId);
42778
- if (!matchedCourse) {
42779
- error("Curso ativo n\xE3o encontrado. Rode `tostudy courses` para verificar.");
42780
- }
42781
- const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
42782
- const onboarding = await getCourseOnboardingStatus(activeCourse);
42783
- const slug = generateInstructionFiles(
42784
- {
42785
- courseTitle: matchedCourse.title,
42786
- courseId: activeCourse.courseId,
42787
- progress: progressData?.coursePercent ?? matchedCourse.progress ?? 0,
42788
- moduleCount: progressData?.currentModule?.totalModules ?? 0,
42789
- lessonCount: progressData?.currentLesson?.totalLessons ?? 0,
42790
- currentModuleTitle: progressData?.currentModule?.title,
42791
- currentLessonTitle: progressData?.currentLesson?.title,
42792
- courseDescription: matchedCourse.description ?? void 0,
42793
- workspaceReady: onboarding.workspaceReady,
42794
- workspacePath: onboarding.workspacePath ?? void 0
42795
- },
42796
- onboardingState?.learnerProfile
43419
+ const pipelineResult = await resolveAndGenerate(
43420
+ { courseId: activeCourse.courseId, enrollmentId: activeCourse.enrollmentId },
43421
+ { forceRefresh: true, collectT1Interactive: false, collectT2Interactive: false },
43422
+ { cwd: process.cwd(), deps: buildPipelineDeps() }
42797
43423
  );
42798
43424
  if (opts.json) {
42799
43425
  output(
42800
43426
  {
42801
43427
  status: "ok",
42802
43428
  courseId: activeCourse.courseId,
42803
- slug,
42804
- progress: progressData?.coursePercent ?? matchedCourse.progress ?? 0
43429
+ courseTitle: activeCourse.courseTitle,
43430
+ wroteFiles: pipelineResult.wroteFiles,
43431
+ markerPath: pipelineResult.markerPath,
43432
+ usedT0: pipelineResult.usedT0,
43433
+ usedT1: pipelineResult.usedT1,
43434
+ usedT2: pipelineResult.usedT2
42805
43435
  },
42806
43436
  { json: true }
42807
43437
  );
42808
43438
  } else {
42809
- const progress3 = progressData?.coursePercent ?? matchedCourse.progress ?? 0;
42810
- output(`\u2713 Instru\xE7\xF5es sincronizadas para "${matchedCourse.title}" (${progress3}%)`, {
42811
- json: false
42812
- });
43439
+ output(
43440
+ [
43441
+ `\u2713 Instru\xE7\xF5es sincronizadas para "${activeCourse.courseTitle}"`,
43442
+ ` - T0 (persona): ${pipelineResult.usedT0 ? "presente" : "ausente"}`,
43443
+ ` - T1 (brief base): ${pipelineResult.usedT1 ? "presente" : "ausente"}`,
43444
+ ` - T2 (curso): ${pipelineResult.usedT2 ? "presente" : "ausente"}`,
43445
+ ` - Arquivos: ${pipelineResult.wroteFiles.join(", ")}`
43446
+ ].join("\n"),
43447
+ { json: false }
43448
+ );
42813
43449
  }
42814
43450
  } catch (err) {
42815
43451
  const msg = err instanceof Error ? err.message : String(err);
42816
43452
  if (msg.includes("process.exit")) return;
43453
+ logger20.warn("sync failed", { error: msg });
42817
43454
  error(msg);
42818
43455
  }
42819
43456
  });
42820
43457
  }
42821
43458
  });
42822
43459
 
43460
+ // src/commands/brief.ts
43461
+ import { Command as Command22 } from "commander";
43462
+ var logger21, briefCommand;
43463
+ var init_brief = __esm({
43464
+ "src/commands/brief.ts"() {
43465
+ "use strict";
43466
+ init_src();
43467
+ init_session();
43468
+ init_cache();
43469
+ init_api2();
43470
+ init_formatter();
43471
+ logger21 = createLogger("cli:brief");
43472
+ briefCommand = new Command22("brief").description("Show your base learner brief (T1) status and content").option("--json", "Output structured JSON").action(async (opts) => {
43473
+ try {
43474
+ const session = await requireSession();
43475
+ const cached2 = await readBriefCache();
43476
+ const remote = await fetchLearnerBrief({
43477
+ apiUrl: session.apiUrl,
43478
+ token: session.token
43479
+ });
43480
+ if (opts.json) {
43481
+ output(
43482
+ {
43483
+ cached: cached2?.brief ?? null,
43484
+ remote,
43485
+ cacheFetchedAt: cached2?.fetchedAt ?? null
43486
+ },
43487
+ { json: true }
43488
+ );
43489
+ return;
43490
+ }
43491
+ if (!remote) {
43492
+ const lines2 = [
43493
+ "Voce ainda nao tem um brief base preenchido.",
43494
+ "Para criar: `tostudy brief-create` (terminal) ou `tostudy brief-open` (portal web)."
43495
+ ];
43496
+ output(lines2.join("\n"), { json: false });
43497
+ return;
43498
+ }
43499
+ const lines = [`Brief base (atualizado em ${remote.updatedAt}):`, "", remote.text, ""];
43500
+ if (cached2 && cached2.brief && cached2.brief.updatedAt !== remote.updatedAt) {
43501
+ lines.push("Aviso: cache local esta desatualizado. Rode `tostudy sync` para atualizar.");
43502
+ }
43503
+ output(lines.join("\n"), { json: false });
43504
+ } catch (err) {
43505
+ logger21.error("Failed to show brief", { err });
43506
+ error(`Erro ao buscar brief: ${err instanceof Error ? err.message : String(err)}`);
43507
+ }
43508
+ });
43509
+ }
43510
+ });
43511
+
43512
+ // src/commands/brief-create.ts
43513
+ import { Command as Command23 } from "commander";
43514
+ var logger22, briefCreateCommand;
43515
+ var init_brief_create = __esm({
43516
+ "src/commands/brief-create.ts"() {
43517
+ "use strict";
43518
+ init_src();
43519
+ init_session();
43520
+ init_bootstrap();
43521
+ init_api2();
43522
+ init_cache();
43523
+ init_formatter();
43524
+ logger22 = createLogger("cli:brief-create");
43525
+ briefCreateCommand = new Command23("brief-create").description("Create your base learner brief via interactive prompts (T1 bootstrap)").action(async () => {
43526
+ try {
43527
+ const session = await requireSession();
43528
+ const answers = await collectBootstrapAnswers({ userName: session.userName });
43529
+ const text2 = composeBriefFromAnswers(answers);
43530
+ const previewLines = ["", "Brief composto:", "---", text2, "---"];
43531
+ output(previewLines.join("\n"), { json: false });
43532
+ const brief = await upsertLearnerBrief({
43533
+ apiUrl: session.apiUrl,
43534
+ token: session.token,
43535
+ text: text2,
43536
+ source: "manual"
43537
+ });
43538
+ await writeBriefCache(void 0, {
43539
+ userId: session.userId,
43540
+ brief,
43541
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
43542
+ });
43543
+ const doneLines = [
43544
+ "",
43545
+ "Brief salvo. Rode `tostudy sync` para atualizar os instruction files."
43546
+ ];
43547
+ output(doneLines.join("\n"), { json: false });
43548
+ } catch (err) {
43549
+ logger22.error("Failed to create brief", { err });
43550
+ error(`Erro ao criar brief: ${err instanceof Error ? err.message : String(err)}`);
43551
+ }
43552
+ });
43553
+ }
43554
+ });
43555
+
43556
+ // src/commands/brief-open.ts
43557
+ import { Command as Command24 } from "commander";
43558
+ import { execFile as execFile4 } from "node:child_process";
43559
+ import { platform } from "node:process";
43560
+ function openUrl(url2) {
43561
+ let cmd;
43562
+ let args;
43563
+ if (platform === "darwin") {
43564
+ cmd = "open";
43565
+ args = [url2];
43566
+ } else if (platform === "win32") {
43567
+ cmd = "cmd";
43568
+ args = ["/c", "start", "", url2];
43569
+ } else {
43570
+ cmd = "xdg-open";
43571
+ args = [url2];
43572
+ }
43573
+ execFile4(cmd, args, (err) => {
43574
+ if (err) {
43575
+ output(`Nao consegui abrir o navegador. Acesse manualmente: ${url2}`, { json: false });
43576
+ }
43577
+ });
43578
+ }
43579
+ var BRIEF_URL, briefOpenCommand;
43580
+ var init_brief_open = __esm({
43581
+ "src/commands/brief-open.ts"() {
43582
+ "use strict";
43583
+ init_session();
43584
+ init_formatter();
43585
+ BRIEF_URL = "https://tostudy.ai/student/settings/learner-brief";
43586
+ briefOpenCommand = new Command24("brief-open").description("Open the learner brief editor in your web browser").action(async () => {
43587
+ await requireSession();
43588
+ output(`Abrindo ${BRIEF_URL} no navegador...`, { json: false });
43589
+ openUrl(BRIEF_URL);
43590
+ });
43591
+ }
43592
+ });
43593
+
42823
43594
  // src/cli.ts
42824
43595
  var cli_exports = {};
42825
43596
  __export(cli_exports, {
42826
43597
  CLI_VERSION: () => CLI_VERSION,
42827
43598
  createProgram: () => createProgram
42828
43599
  });
42829
- import { Command as Command22 } from "commander";
43600
+ import { Command as Command25 } from "commander";
42830
43601
  function createProgram() {
42831
- const program2 = new Command22();
43602
+ const program2 = new Command25();
42832
43603
  program2.name("tostudy").description("ToStudy CLI \u2014 study courses from the terminal").version(CLI_VERSION).option("--verbose", "Enable debug output").option("--course <id>", "Override active course ID");
42833
43604
  program2.addCommand(setupCommand);
42834
43605
  program2.addCommand(doctorCommand);
@@ -42846,6 +43617,9 @@ function createProgram() {
42846
43617
  program2.addCommand(validateCommand);
42847
43618
  program2.addCommand(menuCommand);
42848
43619
  program2.addCommand(profileCommand);
43620
+ program2.addCommand(briefCommand);
43621
+ program2.addCommand(briefCreateCommand);
43622
+ program2.addCommand(briefOpenCommand);
42849
43623
  program2.addCommand(workspaceCommand);
42850
43624
  program2.addCommand(syncCommand);
42851
43625
  program2.addCommand(exportCommand);
@@ -42878,7 +43652,10 @@ var init_cli = __esm({
42878
43652
  init_vault2();
42879
43653
  init_profile();
42880
43654
  init_sync();
42881
- CLI_VERSION = true ? "0.8.0" : "0.7.1";
43655
+ init_brief();
43656
+ init_brief_create();
43657
+ init_brief_open();
43658
+ CLI_VERSION = true ? "0.9.0" : "0.7.1";
42882
43659
  }
42883
43660
  });
42884
43661