@tostudy-ai/cli 0.16.12 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -281,7 +281,7 @@ var require_picocolors = __commonJS({
281
281
  }
282
282
  });
283
283
 
284
- // ../../node_modules/@parisgroup-ai/logger/dist/logger.base-DkbC4pBB.mjs
284
+ // ../../node_modules/@parisgroup-ai/logger/dist/logger.base-Bznav79x.mjs
285
285
  function formatJson(entry) {
286
286
  const { timestamp, level, message, service, environment, context, data, error: error49 } = entry;
287
287
  const output3 = {
@@ -385,8 +385,8 @@ function extractErrorInfo(error49, includeStack, visited = /* @__PURE__ */ new W
385
385
  return info;
386
386
  }
387
387
  var import_picocolors, LOG_LEVELS, LEVEL_COLORS, LEVEL_WIDTH, BaseLogger;
388
- var init_logger_base_DkbC4pBB = __esm({
389
- "../../node_modules/@parisgroup-ai/logger/dist/logger.base-DkbC4pBB.mjs"() {
388
+ var init_logger_base_Bznav79x = __esm({
389
+ "../../node_modules/@parisgroup-ai/logger/dist/logger.base-Bznav79x.mjs"() {
390
390
  init_sanitize_DSkXy3jL();
391
391
  import_picocolors = __toESM(require_picocolors(), 1);
392
392
  LOG_LEVELS = {
@@ -708,7 +708,7 @@ function createLogger(module, options) {
708
708
  var asyncLocalStorage, ConsoleTransport, FileTransport, HttpTransport, SINGLETON_KEY$1, SEVERITY, OtlpLogsTransport, SINGLETON_KEY, Logger, logger;
709
709
  var init_dist = __esm({
710
710
  "../../node_modules/@parisgroup-ai/logger/dist/index.mjs"() {
711
- init_logger_base_DkbC4pBB();
711
+ init_logger_base_Bznav79x();
712
712
  init_sanitize_DSkXy3jL();
713
713
  asyncLocalStorage = new AsyncLocalStorage();
714
714
  ConsoleTransport = class {
@@ -1917,6 +1917,13 @@ var init_ide_detector = __esm({
1917
1917
  });
1918
1918
 
1919
1919
  // src/output/instruction-template-v3.ts
1920
+ var instruction_template_v3_exports = {};
1921
+ __export(instruction_template_v3_exports, {
1922
+ COURSE_TEMPLATE_VERSION: () => COURSE_TEMPLATE_VERSION,
1923
+ UNIVERSAL_TEMPLATE_VERSION: () => UNIVERSAL_TEMPLATE_VERSION,
1924
+ renderCourseInstruction: () => renderCourseInstruction,
1925
+ renderUniversalInstruction: () => renderUniversalInstruction
1926
+ });
1920
1927
  function renderCourseInstruction(input2) {
1921
1928
  const sections = [];
1922
1929
  sections.push(renderTitle(input2));
@@ -1926,7 +1933,11 @@ function renderCourseInstruction(input2) {
1926
1933
  sections.push(renderHowToAdapt(input2.enrollmentProfile));
1927
1934
  sections.push(renderPrecedence());
1928
1935
  sections.push(renderWhereStudentIs(input2.progress));
1936
+ sections.push(renderMemoryLoad());
1929
1937
  sections.push(renderHowToConduct());
1938
+ sections.push(renderStudentAuthorship());
1939
+ sections.push(renderConceptBeforeApplication());
1940
+ sections.push(renderTrainingCaseFraming());
1930
1941
  sections.push(renderSilentTools());
1931
1942
  sections.push(renderHandlingSituations());
1932
1943
  sections.push(renderTechnicalReference());
@@ -1967,6 +1978,9 @@ Rode \`tostudy context --json\` em sil\xEAncio para descobrir o estado do aluno.
1967
1978
  3. Se houver summaries, mencione brevemente: "Da \xFAltima vez paramos em {ultimoModulo}. Pronto pra continuar?"
1968
1979
  4. Siga "Como Conduzir a Aula" abaixo.`);
1969
1980
  sections.push(renderHowToConduct());
1981
+ sections.push(renderStudentAuthorship());
1982
+ sections.push(renderConceptBeforeApplication());
1983
+ sections.push(renderTrainingCaseFraming());
1970
1984
  sections.push(renderSilentTools());
1971
1985
  sections.push(`## Compacta\xE7\xE3o de Contexto (Autom\xE1tico)
1972
1986
 
@@ -1983,7 +1997,7 @@ Quando \`tostudy next --json\` retornar \`MODULE_COMPLETE\`:
1983
1997
  Isso garante continuidade entre sess\xF5es e evita estouro de contexto em sess\xF5es longas.`);
1984
1998
  sections.push(renderHandlingSituations());
1985
1999
  sections.push(renderTechnicalReference());
1986
- sections.push(`<!-- tostudy-template-version: 4 -->`);
2000
+ sections.push(`<!-- tostudy-template-version: ${UNIVERSAL_TEMPLATE_VERSION} -->`);
1987
2001
  return sections.join("\n\n") + "\n";
1988
2002
  }
1989
2003
  function renderTitle(input2) {
@@ -2042,7 +2056,7 @@ function renderWhoIsStudent(brief, profile) {
2042
2056
  if (brief && brief.text.trim().length > 0) {
2043
2057
  parts.push(brief.text);
2044
2058
  parts.push(
2045
- "_(Este \xE9 o brief que o aluno escreveu sobre si mesmo. Use como contexto principal \u2014 nomes, empresa, projetos reais, motiva\xE7\xF5es. Tudo que voc\xEA disser deve parecer que voc\xEA leu e entendeu quem ele \xE9.)_"
2059
+ '_(Este \xE9 o brief que o aluno escreveu sobre si mesmo. Use como contexto principal \u2014 nomes, empresa, projetos reais, motiva\xE7\xF5es. Tudo que voc\xEA disser deve parecer que voc\xEA leu e entendeu quem ele \xE9. O brief descreve quem ele \xE9 profissionalmente \u2014 N\xC3O o n\xEDvel dele neste curso: "avan\xE7ado" em outra \xE1rea n\xE3o vale como avan\xE7ado aqui.)_'
2046
2060
  );
2047
2061
  } else {
2048
2062
  parts.push(
@@ -2075,7 +2089,7 @@ function renderHowToAdapt(profile) {
2075
2089
  if (!profile) {
2076
2090
  return `### Como Adaptar
2077
2091
 
2078
- Sem informa\xE7\xE3o de n\xEDvel espec\xEDfico deste curso \u2014 comece explicativo mas breve; calibre pelas primeiras respostas do aluno. Na d\xFAvida, prefira "porqu\xEA antes do como".`;
2092
+ Sem informa\xE7\xE3o de n\xEDvel espec\xEDfico deste curso \u2014 o brief base N\xC3O vale como n\xEDvel neste assunto. Comece explicativo mas breve; calibre pelo que o aluno demonstrar aqui. Na d\xFAvida, prefira "porqu\xEA antes do como".`;
2079
2093
  }
2080
2094
  if (profile.learnerLevel === "beginner") {
2081
2095
  return `### Como Adaptar
@@ -2115,6 +2129,9 @@ Voc\xEA tem DUAS fontes que te orientam, e elas resolvem conflitos assim:
2115
2129
  | ----------------------------- | ----------------------------- | ---------------------------------------------- |
2116
2130
  | Sua **persona** (acima) | voz, tom, nome, express\xF5es | usar catchphrase do creator ao cumprimentar |
2117
2131
  | **Contexto do aluno** (acima) | profundidade, ritmo, exemplos | "vou come\xE7ar pelo porqu\xEA" (iniciante) |
2132
+ | **Brief base (T1)** | quem ele \xE9 (nomes, projetos) | exemplos com o contexto real dele |
2133
+
2134
+ O brief base NUNCA define o n\xEDvel neste curso \u2014 o n\xEDvel vem do campo "Neste Curso" e do que o aluno demonstra nas respostas aqui.
2118
2135
 
2119
2136
  **Se conflitam, ambos valem ao mesmo tempo.** Voc\xEA mant\xE9m sua voz E adapta a profundidade. Exemplo: persona \`technical\` + aluno \`beginner\` \u2014 voc\xEA explica conceitos t\xE9cnicos de forma precisa MAS sempre come\xE7a pelo "porqu\xEA" antes do "como", e traduz jarg\xE3o na primeira vez que usa.`;
2120
2137
  }
@@ -2153,6 +2170,33 @@ Quando o aluno passou na li\xE7\xE3o:
2153
2170
  - Pergunte se ele quer seguir ou pausar.
2154
2171
  - Ao seguir \u2014 \`tostudy next\` (sil\xEAncio) \u2014 apresente a pr\xF3xima.`;
2155
2172
  }
2173
+ function renderMemoryLoad() {
2174
+ return `## Mem\xF3ria do Aluno
2175
+
2176
+ No in\xEDcio de cada sess\xE3o, rode \`tostudy memory --json\` em sil\xEAncio. Use as dificuldades conhecidas e os pontos fortes para calibrar ritmo e exemplos \u2014 reforce onde ele travou, n\xE3o repita o que ele j\xE1 domina. NUNCA mostre esse bloco cru ao aluno.`;
2177
+ }
2178
+ function renderStudentAuthorship() {
2179
+ return `## Autoria do Aluno (OBRIGAT\xD3RIO)
2180
+
2181
+ Em li\xE7\xF5es de **exerc\xEDcio, quiz ou checkpoint**, a resposta \xE9 do ALUNO \u2014 nunca sua.
2182
+
2183
+ 1. **Nunca redija a resposta sozinho.** Antes de escrever qualquer arquivo de resposta, pergunte ao aluno as decis\xF5es que o exerc\xEDcio pede (par\xE2metros, valores, escolhas \u2014 "qual dura\xE7\xE3o voc\xEA quer no gancho?", "qual curva voc\xEA escolhe para a aterrissagem?"). Rascunhe A PARTIR das respostas dele, com ele.
2184
+ 2. **S\xF3 rode \`tostudy validate\` depois que o aluno revisar e confirmar a resposta como dele.** "Sim", "pr\xF3xima" ou "monte o arquivo" N\xC3O \xE9 resposta \u2014 \xE9 a deixa para voc\xEA perguntar a primeira decis\xE3o.
2185
+ 3. **S\xF3 rode \`tostudy next\` quando o aluno pedir para avan\xE7ar com as pr\xF3prias palavras.** Nunca encadeie validate \u2192 next sem uma fala do aluno entre os dois.
2186
+ 4. Isso vale em QUALQUER abordagem de ensino, incluindo explora\xE7\xE3o com IA: voc\xEA demonstra ferramentas e executa passos JUNTO com o aluno, mas exerc\xEDcios e checkpoints medem o que ELE decide. Sem as decis\xF5es dele, n\xE3o h\xE1 o que validar.`;
2187
+ }
2188
+ function renderConceptBeforeApplication() {
2189
+ return `## Conceito Antes da Aplica\xE7\xE3o (OBRIGAT\xD3RIO)
2190
+
2191
+ 1. **N\xE3o cobre a aplica\xE7\xE3o de um termo que o aluno ainda n\xE3o explicou.** Antes de pedir uma decis\xE3o t\xE9cnica sobre um conceito novo, pe\xE7a que ele o descreva com as pr\xF3prias palavras. Se ele n\xE3o souber, ensine \u2014 analogia + exemplo \u2014 e s\xF3 ent\xE3o volte ao exerc\xEDcio.
2192
+ 2. **"N\xE3o entendi" trava a aplica\xE7\xE3o.** Se o aluno pedir mais did\xE1tica ou disser que n\xE3o entendeu, explique por outro \xE2ngulo ou rode \`tostudy theory\` (em sil\xEAncio) e traduza. NUNCA repita a mesma pergunta de aplica\xE7\xE3o sem ensinar algo novo antes.
2193
+ 3. **Dificuldade registrada = ritmo did\xE1tico.** Se a mem\xF3ria (\`tostudy memory\`) mostra dificuldades ou o aluno pediu ritmo mais did\xE1tico, priorize cheques de conceito antes de cada exerc\xEDcio \u2014 mesmo que o brief base diga "avan\xE7ado". O n\xEDvel dele NESTE curso \xE9 o que ele demonstra aqui.`;
2194
+ }
2195
+ function renderTrainingCaseFraming() {
2196
+ return `## Enquadramento de Caso de Treino (OBRIGAT\xD3RIO)
2197
+
2198
+ 1. **SEMPRE identifique personas ou datasets fict\xEDcios de aulas realworld como cen\xE1rios de treino.** Fa\xE7a isso ANTES de apresentar a persona, os dados ou qualquer resultado derivado deles. **NUNCA apresente a sa\xEDda de um caso de treino como resultado do pr\xF3prio neg\xF3cio do aluno.** Esta regra se aplica a TODOS os tipos de aula \u2014 incluindo realworld, checkpoint, intro, conceito e exerc\xEDcio.`;
2199
+ }
2156
2200
  function renderSilentTools() {
2157
2201
  return `## Ferramentas Silenciosas
2158
2202
 
@@ -2164,6 +2208,8 @@ Estes comandos s\xE3o suas ferramentas. Rode em sil\xEAncio (sem anunciar), use
2164
2208
  - \`tostudy next --json\` \u2014 avan\xE7a para a pr\xF3xima li\xE7\xE3o.
2165
2209
  - \`tostudy hint --json\` \u2014 dica progressiva (3 n\xEDveis).
2166
2210
  - \`tostudy validate <arquivo>\` \u2014 valida exerc\xEDcio (exit 0 = passou, 1 = falhou).
2211
+ - \`tostudy theory --json\` \u2014 micro-aula extra na li\xE7\xE3o atual (aceita \`--focus "<tema>"\`). Use quando o aluno disser "n\xE3o entendi" ou pedir mais did\xE1tica.
2212
+ - \`tostudy level --json\` \u2014 v\xEA/ajusta o n\xEDvel de scaffolding dos exerc\xEDcios (os n\xEDveis DISPON\xCDVEIS v\xEAm no \`--json\`; ofere\xE7a s\xF3 esses). Use quando o exerc\xEDcio est\xE1 acima ou abaixo do ponto do aluno.
2167
2213
 
2168
2214
  - \`tostudy insight <tipo> "<descri\xE7\xE3o>" --json\` \u2014 registra na mem\xF3ria do curso um sinal cognitivo do aluno. Tipos: \`difficulty\` (travou ou confundiu um conceito), \`breakthrough\` (teve um clique, dominou algo), \`question\` (d\xFAvida recorrente).
2169
2215
 
@@ -2177,6 +2223,7 @@ function renderHandlingSituations() {
2177
2223
  | Situa\xE7\xE3o | O que fazer |
2178
2224
  | ------------------------------------- | ---------------------------------------------------------- |
2179
2225
  | \`tostudy validate\` falhou | Mostrar feedback, sugerir \`tostudy hint\`, tentar de novo |
2226
+ | Aluno diz "n\xE3o entendi" / quer did\xE1tica | Explicar por outro \xE2ngulo, \`tostudy theory\` (em sil\xEAncio); n\xE3o repetir a pergunta de aplica\xE7\xE3o |
2180
2227
  | "Nenhuma li\xE7\xE3o ativa" | Rodar \`tostudy start\` para carregar m\xF3dulo |
2181
2228
  | Comando retorna erro | Verificar \`tostudy doctor\` para diagn\xF3stico |
2182
2229
  | Aluno perdido / sem saber o que fazer | Rodar \`tostudy progress\` e resumir estado atual |
@@ -2195,12 +2242,14 @@ function renderTechnicalReference() {
2195
2242
  }
2196
2243
  function renderFooter(course) {
2197
2244
  return `<!-- tostudy-course-id: ${course.courseId} -->
2198
- <!-- tostudy-template-version: 3 -->`;
2245
+ <!-- tostudy-template-version: ${COURSE_TEMPLATE_VERSION} -->`;
2199
2246
  }
2200
- var TONE_LABELS, LEVEL_LABELS;
2247
+ var COURSE_TEMPLATE_VERSION, UNIVERSAL_TEMPLATE_VERSION, TONE_LABELS, LEVEL_LABELS;
2201
2248
  var init_instruction_template_v3 = __esm({
2202
2249
  "src/output/instruction-template-v3.ts"() {
2203
2250
  "use strict";
2251
+ COURSE_TEMPLATE_VERSION = 4;
2252
+ UNIVERSAL_TEMPLATE_VERSION = 5;
2204
2253
  TONE_LABELS = {
2205
2254
  formal: "formal",
2206
2255
  casual: "casual",
@@ -2932,6 +2981,24 @@ async function writeWorkspaceMarker(workspacePath, input2, options = {}) {
2932
2981
  }
2933
2982
  const existing = await readWorkspaceMarker(workspacePath);
2934
2983
  const now = (/* @__PURE__ */ new Date()).toISOString();
2984
+ let carried = {};
2985
+ if (existing && existing.courseId === input2.courseId) {
2986
+ try {
2987
+ const raw = JSON.parse(readFileSync2(filePath, "utf-8"));
2988
+ for (const key of [
2989
+ "currentLessonId",
2990
+ "currentModuleId",
2991
+ "courseTags",
2992
+ "courseLevel",
2993
+ "lastInitCourseId",
2994
+ "templateVersion"
2995
+ ]) {
2996
+ if (raw[key] !== void 0) carried[key] = raw[key];
2997
+ }
2998
+ } catch {
2999
+ carried = {};
3000
+ }
3001
+ }
2935
3002
  const marker = {
2936
3003
  version: CURRENT_VERSION,
2937
3004
  courseId: input2.courseId,
@@ -2941,7 +3008,7 @@ async function writeWorkspaceMarker(workspacePath, input2, options = {}) {
2941
3008
  createdAt: existing?.createdAt ?? now,
2942
3009
  updatedAt: now
2943
3010
  };
2944
- writeFileSync3(filePath, JSON.stringify(marker, null, 2), { mode: 384 });
3011
+ writeFileSync3(filePath, JSON.stringify({ ...carried, ...marker }, null, 2), { mode: 384 });
2945
3012
  return filePath;
2946
3013
  }
2947
3014
  var CURRENT_VERSION, SUPPORTED_VERSIONS;
@@ -2986,6 +3053,7 @@ async function readWorkspaceState(workspacePath) {
2986
3053
  state.courseLevel = raw.courseLevel;
2987
3054
  }
2988
3055
  if (typeof raw.lastInitCourseId === "string") state.lastInitCourseId = raw.lastInitCourseId;
3056
+ if (typeof raw.templateVersion === "number") state.templateVersion = raw.templateVersion;
2989
3057
  return state;
2990
3058
  } catch {
2991
3059
  return null;
@@ -3039,16 +3107,31 @@ async function setLastInitCourseId(courseId, _configDir) {
3039
3107
  }
3040
3108
  async function checkCourseDrift() {
3041
3109
  const { findWorkspaceState: findWorkspaceState2 } = await Promise.resolve().then(() => (init_workspace_state(), workspace_state_exports));
3110
+ const { COURSE_TEMPLATE_VERSION: COURSE_TEMPLATE_VERSION2 } = await Promise.resolve().then(() => (init_instruction_template_v3(), instruction_template_v3_exports));
3042
3111
  const result = await findWorkspaceState2();
3043
3112
  if (!result) return null;
3044
3113
  const state = result.state;
3045
- if (!state.lastInitCourseId) return null;
3046
- if (state.lastInitCourseId === state.courseId) return null;
3047
- return [
3048
- `\u26A0\uFE0F Curso ativo mudou para "${state.courseTitle}".`,
3049
- ` Rode \`tostudy init\` para atualizar o contexto do assistente.`,
3050
- ""
3051
- ].join("\n");
3114
+ const warnings = [];
3115
+ if (state.lastInitCourseId && state.lastInitCourseId !== state.courseId) {
3116
+ warnings.push(
3117
+ [
3118
+ `\u26A0\uFE0F Curso ativo mudou para "${state.courseTitle}".`,
3119
+ ` Rode \`tostudy init\` para atualizar o contexto do assistente.`,
3120
+ ""
3121
+ ].join("\n")
3122
+ );
3123
+ }
3124
+ if ((state.templateVersion ?? 0) < COURSE_TEMPLATE_VERSION2) {
3125
+ const from = state.templateVersion ? `v${state.templateVersion}` : "vers\xE3o antiga";
3126
+ warnings.push(
3127
+ [
3128
+ `\u26A0\uFE0F As instru\xE7\xF5es do tutor neste workspace est\xE3o desatualizadas (${from} < v${COURSE_TEMPLATE_VERSION2}).`,
3129
+ ` Rode \`tostudy sync\` para atualizar as regras do tutor.`,
3130
+ ""
3131
+ ].join("\n")
3132
+ );
3133
+ }
3134
+ return warnings.length > 0 ? warnings.join("\n") : null;
3052
3135
  }
3053
3136
  var init_course_drift = __esm({
3054
3137
  "src/onboarding/course-drift.ts"() {
@@ -3439,7 +3522,7 @@ var CLI_VERSION;
3439
3522
  var init_version = __esm({
3440
3523
  "src/version.ts"() {
3441
3524
  "use strict";
3442
- CLI_VERSION = true ? "0.16.12" : "0.7.1";
3525
+ CLI_VERSION = true ? "0.17.1" : "0.7.1";
3443
3526
  }
3444
3527
  });
3445
3528
 
@@ -4155,13 +4238,18 @@ async function resolveAndGenerate(input2, options = {}, context) {
4155
4238
  } catch (err) {
4156
4239
  logger6.warn("Failed to write workspace marker", { err });
4157
4240
  }
4241
+ let workspaceStateRecorded = false;
4158
4242
  if (markerPath) {
4159
4243
  try {
4160
4244
  await updateWorkspaceState(context.cwd, {
4161
4245
  courseTags: courseMeta.courseTags,
4162
4246
  courseLevel: courseMeta.courseLevel,
4163
- lastInitCourseId: input2.courseId
4247
+ lastInitCourseId: input2.courseId,
4248
+ // GH #1534: record which template generated the instruction files so
4249
+ // checkCourseDrift() can flag stale workspaces after a template bump.
4250
+ templateVersion: COURSE_TEMPLATE_VERSION
4164
4251
  });
4252
+ workspaceStateRecorded = true;
4165
4253
  } catch (err) {
4166
4254
  logger6.warn("Failed to update workspace state v2 fields", {
4167
4255
  error: err instanceof Error ? err.message : String(err)
@@ -4175,7 +4263,8 @@ async function resolveAndGenerate(input2, options = {}, context) {
4175
4263
  usedT1: studentBrief !== null,
4176
4264
  usedT2: enrollmentProfile !== null,
4177
4265
  bootstrapped,
4178
- renderedInstruction: content
4266
+ renderedInstruction: content,
4267
+ workspaceStateRecorded
4179
4268
  };
4180
4269
  }
4181
4270
  var logger6, defaultDeps2;
@@ -19663,6 +19752,13 @@ var init_credit_payment_enums = __esm({
19663
19752
  }
19664
19753
  });
19665
19754
 
19755
+ // ../../packages/types/src/meeting-integrations.ts
19756
+ var init_meeting_integrations = __esm({
19757
+ "../../packages/types/src/meeting-integrations.ts"() {
19758
+ "use strict";
19759
+ }
19760
+ });
19761
+
19666
19762
  // ../../packages/types/src/referral.ts
19667
19763
  var init_referral = __esm({
19668
19764
  "../../packages/types/src/referral.ts"() {
@@ -19686,6 +19782,7 @@ var init_src = __esm({
19686
19782
  init_lesson_review();
19687
19783
  init_errors4();
19688
19784
  init_credit_payment_enums();
19785
+ init_meeting_integrations();
19689
19786
  init_referral();
19690
19787
  }
19691
19788
  });
@@ -21561,6 +21658,7 @@ var init_sync = __esm({
21561
21658
  init_guards();
21562
21659
  init_formatter();
21563
21660
  init_instruction_pipeline();
21661
+ init_workspace_state();
21564
21662
  init_root_agents_consent();
21565
21663
  init_pipeline_deps();
21566
21664
  logger22 = createLogger("cli:sync");
@@ -21574,8 +21672,10 @@ var init_sync = __esm({
21574
21672
  try {
21575
21673
  await requireSession();
21576
21674
  const activeCourse = await requireActiveCourse();
21675
+ const found = await findWorkspaceState();
21676
+ const workspaceCwd = found?.workspacePath ?? process.cwd();
21577
21677
  const rootAgents = await resolveRootAgentsConsent({
21578
- cwd: process.cwd(),
21678
+ cwd: workspaceCwd,
21579
21679
  flag: opts.rootAgents,
21580
21680
  interactive: isInteractiveTerminal(opts.json)
21581
21681
  });
@@ -21583,7 +21683,7 @@ var init_sync = __esm({
21583
21683
  { courseId: activeCourse.courseId, enrollmentId: activeCourse.enrollmentId },
21584
21684
  { forceRefresh: true, collectT1Interactive: false, collectT2Interactive: false },
21585
21685
  {
21586
- cwd: process.cwd(),
21686
+ cwd: workspaceCwd,
21587
21687
  deps: buildPipelineDeps({
21588
21688
  allRuntimes: opts.allRuntimes,
21589
21689
  rootAgentsConsent: rootAgents.consent
@@ -21603,21 +21703,25 @@ var init_sync = __esm({
21603
21703
  markerPath: pipelineResult.markerPath,
21604
21704
  usedT0: pipelineResult.usedT0,
21605
21705
  usedT1: pipelineResult.usedT1,
21606
- usedT2: pipelineResult.usedT2
21706
+ usedT2: pipelineResult.usedT2,
21707
+ workspaceStateRecorded: pipelineResult.workspaceStateRecorded
21607
21708
  },
21608
21709
  { json: true }
21609
21710
  );
21610
21711
  } else {
21611
- output(
21612
- [
21613
- `\u2713 Instru\xE7\xF5es sincronizadas para "${activeCourse.courseTitle}"`,
21614
- ` - T0 (persona): ${pipelineResult.usedT0 ? "presente" : "ausente"}`,
21615
- ` - T1 (brief base): ${pipelineResult.usedT1 ? "presente" : "ausente"}`,
21616
- ` - T2 (curso): ${pipelineResult.usedT2 ? "presente" : "ausente"}`,
21617
- ` - Arquivos: ${pipelineResult.wroteFiles.join(", ")}`
21618
- ].join("\n"),
21619
- { json: false }
21620
- );
21712
+ const lines = [
21713
+ `\u2713 Instru\xE7\xF5es sincronizadas para "${activeCourse.courseTitle}"`,
21714
+ ` - T0 (persona): ${pipelineResult.usedT0 ? "presente" : "ausente"}`,
21715
+ ` - T1 (brief base): ${pipelineResult.usedT1 ? "presente" : "ausente"}`,
21716
+ ` - T2 (curso): ${pipelineResult.usedT2 ? "presente" : "ausente"}`,
21717
+ ` - Arquivos: ${pipelineResult.wroteFiles.join(", ")}`
21718
+ ];
21719
+ if (!pipelineResult.workspaceStateRecorded) {
21720
+ lines.push(
21721
+ `\u26A0 N\xE3o consegui gravar .tostudy/workspace.json \u2014 o aviso de template pode continuar aparecendo. Verifique as permiss\xF5es do diret\xF3rio do workspace.`
21722
+ );
21723
+ }
21724
+ output(lines.join("\n"), { json: false });
21621
21725
  }
21622
21726
  } catch (err) {
21623
21727
  const msg = err instanceof Error ? err.message : String(err);
@@ -21872,6 +21976,7 @@ var init_context = __esm({
21872
21976
  init_dist();
21873
21977
  init_workspace_state();
21874
21978
  init_storage();
21979
+ init_course_state();
21875
21980
  init_formatter();
21876
21981
  init_errors();
21877
21982
  logger27 = createLogger("cli:context");
@@ -21882,6 +21987,8 @@ var init_context = __esm({
21882
21987
  if (opts.json) jsonError("no_workspace");
21883
21988
  error(getErrors().noActiveCourse.trimEnd());
21884
21989
  }
21990
+ const driftWarning = await checkCourseDrift();
21991
+ if (driftWarning && !opts.json) process.stderr.write(driftWarning + "\n");
21885
21992
  const sessionCtx = await loadSessionContext(ws.workspacePath);
21886
21993
  const result = {
21887
21994
  workspace: {
@@ -21900,7 +22007,8 @@ var init_context = __esm({
21900
22007
  summary: s.summary,
21901
22008
  savedAt: s.savedAt
21902
22009
  })),
21903
- totalModulesCompleted: sessionCtx.moduleSummaries.length
22010
+ totalModulesCompleted: sessionCtx.moduleSummaries.length,
22011
+ driftWarning: driftWarning ?? null
21904
22012
  };
21905
22013
  logger27.debug("Context loaded", {
21906
22014
  courseId: ws.state.courseId,