@tostudy-ai/cli 0.17.0 → 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
@@ -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));
@@ -1990,7 +1997,7 @@ Quando \`tostudy next --json\` retornar \`MODULE_COMPLETE\`:
1990
1997
  Isso garante continuidade entre sess\xF5es e evita estouro de contexto em sess\xF5es longas.`);
1991
1998
  sections.push(renderHandlingSituations());
1992
1999
  sections.push(renderTechnicalReference());
1993
- sections.push(`<!-- tostudy-template-version: 5 -->`);
2000
+ sections.push(`<!-- tostudy-template-version: ${UNIVERSAL_TEMPLATE_VERSION} -->`);
1994
2001
  return sections.join("\n\n") + "\n";
1995
2002
  }
1996
2003
  function renderTitle(input2) {
@@ -2235,12 +2242,14 @@ function renderTechnicalReference() {
2235
2242
  }
2236
2243
  function renderFooter(course) {
2237
2244
  return `<!-- tostudy-course-id: ${course.courseId} -->
2238
- <!-- tostudy-template-version: 4 -->`;
2245
+ <!-- tostudy-template-version: ${COURSE_TEMPLATE_VERSION} -->`;
2239
2246
  }
2240
- var TONE_LABELS, LEVEL_LABELS;
2247
+ var COURSE_TEMPLATE_VERSION, UNIVERSAL_TEMPLATE_VERSION, TONE_LABELS, LEVEL_LABELS;
2241
2248
  var init_instruction_template_v3 = __esm({
2242
2249
  "src/output/instruction-template-v3.ts"() {
2243
2250
  "use strict";
2251
+ COURSE_TEMPLATE_VERSION = 4;
2252
+ UNIVERSAL_TEMPLATE_VERSION = 5;
2244
2253
  TONE_LABELS = {
2245
2254
  formal: "formal",
2246
2255
  casual: "casual",
@@ -2972,6 +2981,24 @@ async function writeWorkspaceMarker(workspacePath, input2, options = {}) {
2972
2981
  }
2973
2982
  const existing = await readWorkspaceMarker(workspacePath);
2974
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
+ }
2975
3002
  const marker = {
2976
3003
  version: CURRENT_VERSION,
2977
3004
  courseId: input2.courseId,
@@ -2981,7 +3008,7 @@ async function writeWorkspaceMarker(workspacePath, input2, options = {}) {
2981
3008
  createdAt: existing?.createdAt ?? now,
2982
3009
  updatedAt: now
2983
3010
  };
2984
- writeFileSync3(filePath, JSON.stringify(marker, null, 2), { mode: 384 });
3011
+ writeFileSync3(filePath, JSON.stringify({ ...carried, ...marker }, null, 2), { mode: 384 });
2985
3012
  return filePath;
2986
3013
  }
2987
3014
  var CURRENT_VERSION, SUPPORTED_VERSIONS;
@@ -3026,6 +3053,7 @@ async function readWorkspaceState(workspacePath) {
3026
3053
  state.courseLevel = raw.courseLevel;
3027
3054
  }
3028
3055
  if (typeof raw.lastInitCourseId === "string") state.lastInitCourseId = raw.lastInitCourseId;
3056
+ if (typeof raw.templateVersion === "number") state.templateVersion = raw.templateVersion;
3029
3057
  return state;
3030
3058
  } catch {
3031
3059
  return null;
@@ -3079,16 +3107,31 @@ async function setLastInitCourseId(courseId, _configDir) {
3079
3107
  }
3080
3108
  async function checkCourseDrift() {
3081
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));
3082
3111
  const result = await findWorkspaceState2();
3083
3112
  if (!result) return null;
3084
3113
  const state = result.state;
3085
- if (!state.lastInitCourseId) return null;
3086
- if (state.lastInitCourseId === state.courseId) return null;
3087
- return [
3088
- `\u26A0\uFE0F Curso ativo mudou para "${state.courseTitle}".`,
3089
- ` Rode \`tostudy init\` para atualizar o contexto do assistente.`,
3090
- ""
3091
- ].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;
3092
3135
  }
3093
3136
  var init_course_drift = __esm({
3094
3137
  "src/onboarding/course-drift.ts"() {
@@ -3479,7 +3522,7 @@ var CLI_VERSION;
3479
3522
  var init_version = __esm({
3480
3523
  "src/version.ts"() {
3481
3524
  "use strict";
3482
- CLI_VERSION = true ? "0.17.0" : "0.7.1";
3525
+ CLI_VERSION = true ? "0.17.1" : "0.7.1";
3483
3526
  }
3484
3527
  });
3485
3528
 
@@ -4195,13 +4238,18 @@ async function resolveAndGenerate(input2, options = {}, context) {
4195
4238
  } catch (err) {
4196
4239
  logger6.warn("Failed to write workspace marker", { err });
4197
4240
  }
4241
+ let workspaceStateRecorded = false;
4198
4242
  if (markerPath) {
4199
4243
  try {
4200
4244
  await updateWorkspaceState(context.cwd, {
4201
4245
  courseTags: courseMeta.courseTags,
4202
4246
  courseLevel: courseMeta.courseLevel,
4203
- 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
4204
4251
  });
4252
+ workspaceStateRecorded = true;
4205
4253
  } catch (err) {
4206
4254
  logger6.warn("Failed to update workspace state v2 fields", {
4207
4255
  error: err instanceof Error ? err.message : String(err)
@@ -4215,7 +4263,8 @@ async function resolveAndGenerate(input2, options = {}, context) {
4215
4263
  usedT1: studentBrief !== null,
4216
4264
  usedT2: enrollmentProfile !== null,
4217
4265
  bootstrapped,
4218
- renderedInstruction: content
4266
+ renderedInstruction: content,
4267
+ workspaceStateRecorded
4219
4268
  };
4220
4269
  }
4221
4270
  var logger6, defaultDeps2;
@@ -21609,6 +21658,7 @@ var init_sync = __esm({
21609
21658
  init_guards();
21610
21659
  init_formatter();
21611
21660
  init_instruction_pipeline();
21661
+ init_workspace_state();
21612
21662
  init_root_agents_consent();
21613
21663
  init_pipeline_deps();
21614
21664
  logger22 = createLogger("cli:sync");
@@ -21622,8 +21672,10 @@ var init_sync = __esm({
21622
21672
  try {
21623
21673
  await requireSession();
21624
21674
  const activeCourse = await requireActiveCourse();
21675
+ const found = await findWorkspaceState();
21676
+ const workspaceCwd = found?.workspacePath ?? process.cwd();
21625
21677
  const rootAgents = await resolveRootAgentsConsent({
21626
- cwd: process.cwd(),
21678
+ cwd: workspaceCwd,
21627
21679
  flag: opts.rootAgents,
21628
21680
  interactive: isInteractiveTerminal(opts.json)
21629
21681
  });
@@ -21631,7 +21683,7 @@ var init_sync = __esm({
21631
21683
  { courseId: activeCourse.courseId, enrollmentId: activeCourse.enrollmentId },
21632
21684
  { forceRefresh: true, collectT1Interactive: false, collectT2Interactive: false },
21633
21685
  {
21634
- cwd: process.cwd(),
21686
+ cwd: workspaceCwd,
21635
21687
  deps: buildPipelineDeps({
21636
21688
  allRuntimes: opts.allRuntimes,
21637
21689
  rootAgentsConsent: rootAgents.consent
@@ -21651,21 +21703,25 @@ var init_sync = __esm({
21651
21703
  markerPath: pipelineResult.markerPath,
21652
21704
  usedT0: pipelineResult.usedT0,
21653
21705
  usedT1: pipelineResult.usedT1,
21654
- usedT2: pipelineResult.usedT2
21706
+ usedT2: pipelineResult.usedT2,
21707
+ workspaceStateRecorded: pipelineResult.workspaceStateRecorded
21655
21708
  },
21656
21709
  { json: true }
21657
21710
  );
21658
21711
  } else {
21659
- output(
21660
- [
21661
- `\u2713 Instru\xE7\xF5es sincronizadas para "${activeCourse.courseTitle}"`,
21662
- ` - T0 (persona): ${pipelineResult.usedT0 ? "presente" : "ausente"}`,
21663
- ` - T1 (brief base): ${pipelineResult.usedT1 ? "presente" : "ausente"}`,
21664
- ` - T2 (curso): ${pipelineResult.usedT2 ? "presente" : "ausente"}`,
21665
- ` - Arquivos: ${pipelineResult.wroteFiles.join(", ")}`
21666
- ].join("\n"),
21667
- { json: false }
21668
- );
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 });
21669
21725
  }
21670
21726
  } catch (err) {
21671
21727
  const msg = err instanceof Error ? err.message : String(err);
@@ -21920,6 +21976,7 @@ var init_context = __esm({
21920
21976
  init_dist();
21921
21977
  init_workspace_state();
21922
21978
  init_storage();
21979
+ init_course_state();
21923
21980
  init_formatter();
21924
21981
  init_errors();
21925
21982
  logger27 = createLogger("cli:context");
@@ -21930,6 +21987,8 @@ var init_context = __esm({
21930
21987
  if (opts.json) jsonError("no_workspace");
21931
21988
  error(getErrors().noActiveCourse.trimEnd());
21932
21989
  }
21990
+ const driftWarning = await checkCourseDrift();
21991
+ if (driftWarning && !opts.json) process.stderr.write(driftWarning + "\n");
21933
21992
  const sessionCtx = await loadSessionContext(ws.workspacePath);
21934
21993
  const result = {
21935
21994
  workspace: {
@@ -21948,7 +22007,8 @@ var init_context = __esm({
21948
22007
  summary: s.summary,
21949
22008
  savedAt: s.savedAt
21950
22009
  })),
21951
- totalModulesCompleted: sessionCtx.moduleSummaries.length
22010
+ totalModulesCompleted: sessionCtx.moduleSummaries.length,
22011
+ driftWarning: driftWarning ?? null
21952
22012
  };
21953
22013
  logger27.debug("Context loaded", {
21954
22014
  courseId: ws.state.courseId,