ronin-skillforce 0.1.0 → 0.2.0-beta.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 (155) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/LICENSE +202 -21
  3. package/NOTICE +19 -0
  4. package/README.md +38 -5
  5. package/bin/cli.js +15 -3
  6. package/bin/commands/archive.js +192 -0
  7. package/bin/commands/capstone-status.js +82 -0
  8. package/bin/commands/create.js +69 -12
  9. package/bin/commands/import.js +5 -5
  10. package/bin/commands/status.js +1 -1
  11. package/bin/commands/studio-autostart.js +414 -0
  12. package/bin/commands/studio-state.js +159 -0
  13. package/bin/commands/studio.js +351 -17
  14. package/bin/commands/trail.js +153 -3
  15. package/bin/commands/web.js +8 -10
  16. package/bin/course-reader.js +6 -15
  17. package/bin/platforms/_shared.js +154 -28
  18. package/bin/platforms/claude.js +23 -42
  19. package/bin/platforms/cursor.js +23 -20
  20. package/bin/platforms/gemini.js +18 -19
  21. package/bin/platforms/opencode.js +47 -28
  22. package/package.json +5 -4
  23. package/plugin.json +1 -1
  24. package/shared/SKILL.md +102 -72
  25. package/shared/agents/coach.md +19 -16
  26. package/shared/agents/navigator.md +27 -34
  27. package/shared/agents/professor.md +16 -34
  28. package/shared/agents/researcher.md +1 -1
  29. package/shared/agents/spotter.md +3 -4
  30. package/shared/commands/professor/archive.md +65 -72
  31. package/shared/commands/professor/capstone-review.md +5 -5
  32. package/shared/commands/professor/capstone.md +3 -3
  33. package/shared/commands/professor/done.md +59 -31
  34. package/shared/commands/professor/hint.md +14 -2
  35. package/shared/commands/professor/navigator.md +27 -1
  36. package/shared/commands/professor/new-topic.md +3 -3
  37. package/shared/commands/professor/next.md +9 -5
  38. package/shared/commands/professor/note.md +6 -6
  39. package/shared/commands/professor/progress.md +1 -1
  40. package/shared/commands/professor/publish.md +5 -5
  41. package/shared/commands/professor/quiz.md +2 -2
  42. package/shared/commands/professor/recall.md +1 -1
  43. package/shared/commands/professor/review.md +14 -0
  44. package/shared/commands/professor/skill-export.md +3 -4
  45. package/shared/commands/professor/spotter.md +21 -4
  46. package/shared/commands/professor/stuck.md +24 -0
  47. package/shared/commands/professor/switch.md +1 -1
  48. package/shared/commands/professor/syllabus.md +1 -1
  49. package/shared/commands/professor/template-export.md +2 -2
  50. package/shared/commands/professor/worktrees.md +3 -7
  51. package/shared/hooks/pre-compact.js +4 -15
  52. package/studio/dist/assets/BetaReportPanel-BJ9HIAnz.js +1 -0
  53. package/studio/dist/assets/{Capstone-Cv9kH6LY.js → Capstone-CoUU-jP7.js} +1 -1
  54. package/studio/dist/assets/{CodeWorkspace-BwCEINWo.js → CodeWorkspace-BNrL4qvR.js} +2 -2
  55. package/studio/dist/assets/{Course-BJ_hafUP.js → Course-BMGTXus_.js} +1 -1
  56. package/studio/dist/assets/{Editor-CV0mNhlz.js → Editor-Cw9j3cnp.js} +1 -1
  57. package/studio/dist/assets/{Home-C1ry89ak.js → Home-D2woITq5.js} +1 -1
  58. package/studio/dist/assets/{Lesson-2e9TD9yL.js → Lesson-CYQhrDCZ.js} +2 -2
  59. package/studio/dist/assets/{NewCourse-BZeOg2xn.js → NewCourse-CMpwVRQb.js} +1 -1
  60. package/studio/dist/assets/{Progress-CRc_xEWC.js → Progress-2rS19Fur.js} +1 -1
  61. package/studio/dist/assets/{Review-6QEbHEfk.js → Review-SlqK-hjp.js} +1 -1
  62. package/studio/dist/assets/{Settings-NFnqVQui.js → Settings-t42u7n9H.js} +1 -1
  63. package/studio/dist/assets/{TutorPanel-DXQ6Z3BY.js → TutorPanel-DQDWIU7f.js} +3 -3
  64. package/studio/dist/assets/{abnfDiagram-WEYOIA3C-qeSjKlGM.js → abnfDiagram-WEYOIA3C-jTUmGBuB.js} +1 -1
  65. package/studio/dist/assets/{anthropic-D7NiQCUH.js → anthropic-iIm2l85r.js} +3 -3
  66. package/studio/dist/assets/{architecture-WOLXFQ4H-COG-m35l.js → architecture-WOLXFQ4H-Ch9Ld_vv.js} +1 -1
  67. package/studio/dist/assets/{architectureDiagram-ZHVYKT53-VO7uKoeD.js → architectureDiagram-ZHVYKT53-Dy-z6wsy.js} +1 -1
  68. package/studio/dist/assets/{blockDiagram-RJAE7QA3-BGJF_qiL.js → blockDiagram-RJAE7QA3-2fHVrc6N.js} +1 -1
  69. package/studio/dist/assets/{bridge-BTh0AeNX.js → bridge-Cr0q4rLp.js} +1 -1
  70. package/studio/dist/assets/{c4Diagram-XDPPPE54-C-S10RgU.js → c4Diagram-XDPPPE54-BuuKlG4V.js} +1 -1
  71. package/studio/dist/assets/{capstone-Amn6Hztq.js → capstone-DhwBeNqk.js} +1 -1
  72. package/studio/dist/assets/{card-store-BrbrtX9w.js → card-store-CVMBc-Pv.js} +1 -1
  73. package/studio/dist/assets/channel-M_JkdHrL.js +1 -0
  74. package/studio/dist/assets/{chunk-2E7WHVMD-fv-iqTyC.js → chunk-2E7WHVMD-lZdQWttk.js} +1 -1
  75. package/studio/dist/assets/{chunk-4EMNKR2T-CY9IZrgv.js → chunk-4EMNKR2T-C6kbMwke.js} +1 -1
  76. package/studio/dist/assets/{chunk-55XCU76K-DyTZz7v_.js → chunk-55XCU76K-rkBzkZog.js} +1 -1
  77. package/studio/dist/assets/{chunk-7S6XSTFO-BNMzTLRW.js → chunk-7S6XSTFO-Skg347QF.js} +1 -1
  78. package/studio/dist/assets/{chunk-AQDTSKZB-CEYANohH.js → chunk-AQDTSKZB-0pSDBJ23.js} +1 -1
  79. package/studio/dist/assets/{chunk-DSNLSHJZ-CDk5EXjH.js → chunk-DSNLSHJZ-BaeQDl2i.js} +1 -1
  80. package/studio/dist/assets/{chunk-HAGFOTED-hNAi1zh_.js → chunk-HAGFOTED-BEm00juw.js} +1 -1
  81. package/studio/dist/assets/{chunk-HNE4Q36P-COJnSYoZ.js → chunk-HNE4Q36P-DyCR-LvS.js} +1 -1
  82. package/studio/dist/assets/{chunk-NQOKRVIF-BU6KVxRr.js → chunk-NQOKRVIF-BNJ9uMmB.js} +1 -1
  83. package/studio/dist/assets/{chunk-UHW7PFBP-DYvxL9JG.js → chunk-UHW7PFBP-DqJczBLs.js} +1 -1
  84. package/studio/dist/assets/{chunk-VQ5W62QI-CuRfk65j.js → chunk-VQ5W62QI-CVWYSP5Q.js} +1 -1
  85. package/studio/dist/assets/{chunk-WQQL4CDK-KaG7x-Mb.js → chunk-WQQL4CDK-DJf0Dx05.js} +1 -1
  86. package/studio/dist/assets/{chunk-YZO67HFG-DbuqIZmk.js → chunk-YZO67HFG-BFUm_JHA.js} +1 -1
  87. package/studio/dist/assets/{chunk-ZAUX2LTX-bk4ivUmc.js → chunk-ZAUX2LTX-C5ETkkSE.js} +1 -1
  88. package/studio/dist/assets/{chunk-ZCHMQSOR-B_LElfwh.js → chunk-ZCHMQSOR-WVX9r9PZ.js} +1 -1
  89. package/studio/dist/assets/{classDiagram-v2-4MYWR3UA-DtCJ6H_X.js → classDiagram-v2-4MYWR3UA-CI77SpuR.js} +1 -1
  90. package/studio/dist/assets/{course-export-DpaOTT3a.js → course-export-C8jSPuda.js} +1 -1
  91. package/studio/dist/assets/{cynefin-EF2NZ3EQ-D9btHJvU.js → cynefin-EF2NZ3EQ-CLffmM25.js} +1 -1
  92. package/studio/dist/assets/{cynefinDiagram-WF24UDRP-CsFJuv27.js → cynefinDiagram-WF24UDRP-BvLwocOb.js} +1 -1
  93. package/studio/dist/assets/{dagre-NK4GIZVX-Cg1Duec7.js → dagre-NK4GIZVX-C2nMAVdh.js} +1 -1
  94. package/studio/dist/assets/{diagram-CBK3MWTC-_KU2m5gd.js → diagram-CBK3MWTC-Bid-pdax.js} +1 -1
  95. package/studio/dist/assets/{diagram-I5XIIV2D-5RaBrwUA.js → diagram-I5XIIV2D-DYJeAvzl.js} +1 -1
  96. package/studio/dist/assets/{diagram-ODIL2UCW-C0VKxoD3.js → diagram-ODIL2UCW-B06X2r_F.js} +1 -1
  97. package/studio/dist/assets/{diagram-PFBEJUXE-BaHcZEP2.js → diagram-PFBEJUXE-CwY--5Rz.js} +1 -1
  98. package/studio/dist/assets/{diagram-RJA3I4VV-cwSAZ90p.js → diagram-RJA3I4VV-4SqhH8zw.js} +1 -1
  99. package/studio/dist/assets/{diagram-TYUNFSNR-BJImxCEN.js → diagram-TYUNFSNR-CrW4AIM9.js} +1 -1
  100. package/studio/dist/assets/{ebnfDiagram-V4P3TTPT-OQQLRXcm.js → ebnfDiagram-V4P3TTPT-xeoCmH-d.js} +1 -1
  101. package/studio/dist/assets/{elk-QONTBCTQ-BJGhQHxQ.js → elk-QONTBCTQ-Dadmz6iS.js} +1 -1
  102. package/studio/dist/assets/{erDiagram-KYQIGQAA-D91EKSep.js → erDiagram-KYQIGQAA-CSuH0bbF.js} +1 -1
  103. package/studio/dist/assets/{eventmodeling-K75KTNOO-DgdTRgnw.js → eventmodeling-K75KTNOO-CciVfx3I.js} +1 -1
  104. package/studio/dist/assets/flowDiagram-IUGLLWBU-DgNR54qK.js +1 -0
  105. package/studio/dist/assets/{ganttDiagram-R7GPNTJY-CSYLPWEI.js → ganttDiagram-R7GPNTJY-C0wtA-2y.js} +1 -1
  106. package/studio/dist/assets/{gitGraph-VSP46ZUC-D6GWaV9y.js → gitGraph-VSP46ZUC-BXoDzaA3.js} +1 -1
  107. package/studio/dist/assets/{gitGraphDiagram-PDYL7XHY-CaV31bvT.js → gitGraphDiagram-PDYL7XHY-vZBlNRPe.js} +1 -1
  108. package/studio/dist/assets/{index-DioXTZVc.js → index-CS8XPYbB.js} +2 -2
  109. package/studio/dist/assets/{info-OHQRW6UA-BN-x8GoN.js → info-OHQRW6UA-bs2Qpf7P.js} +1 -1
  110. package/studio/dist/assets/{infoDiagram-MVDRKAX5-CIose_cy.js → infoDiagram-MVDRKAX5-rPJio6-1.js} +1 -1
  111. package/studio/dist/assets/{ishikawaDiagram-HPEHV2GE-DCihSNBw.js → ishikawaDiagram-HPEHV2GE-CXf9XFff.js} +1 -1
  112. package/studio/dist/assets/{journeyDiagram-BOSHW7O7-Bz3o3RIX.js → journeyDiagram-BOSHW7O7-C3MSH79o.js} +1 -1
  113. package/studio/dist/assets/{kanban-definition-FNSA4HC5-BUyqvlFE.js → kanban-definition-FNSA4HC5-Bdh7SB4n.js} +1 -1
  114. package/studio/dist/assets/{line-B08T8KlK.js → line-CHkoQXJF.js} +1 -1
  115. package/studio/dist/assets/{mermaid-parser.core-BYwreSUy.js → mermaid-parser.core-Do1oxD1S.js} +3 -3
  116. package/studio/dist/assets/{mermaid.core-DTO1oDmq.js → mermaid.core-DgZUD0sU.js} +4 -4
  117. package/studio/dist/assets/{mindmap-definition-6OY57UG3-BGLwKheY.js → mindmap-definition-6OY57UG3-PUaK6oE_.js} +1 -1
  118. package/studio/dist/assets/{node.browser-BISniYn8.js → node.browser-DkpMze_r.js} +1 -1
  119. package/studio/dist/assets/{ollama-De5w84tG.js → ollama-bJNp3jaD.js} +1 -1
  120. package/studio/dist/assets/{openai-compatible-Joy1oK3L.js → openai-compatible-0tmAyMlN.js} +1 -1
  121. package/studio/dist/assets/{packet-JDAUHWVQ-CDIMjHS_.js → packet-JDAUHWVQ-vf2FtqFr.js} +1 -1
  122. package/studio/dist/assets/{pegDiagram-ECIUJNBX-DWXm4mwS.js → pegDiagram-ECIUJNBX-NEVrgPTY.js} +1 -1
  123. package/studio/dist/assets/{pie-XZMESJXO-CY3QQiKZ.js → pie-XZMESJXO-BIWydVNZ.js} +1 -1
  124. package/studio/dist/assets/{pieDiagram-GNMPY74T-M1aCnI7W.js → pieDiagram-GNMPY74T-BI9_u6rT.js} +1 -1
  125. package/studio/dist/assets/{quadrantDiagram-D57K4MC3-BFFtO7U5.js → quadrantDiagram-D57K4MC3-ryNpoBC5.js} +1 -1
  126. package/studio/dist/assets/{radar-ABXABTNO-DT0W4_Xn.js → radar-ABXABTNO-D8TjnvBB.js} +1 -1
  127. package/studio/dist/assets/{railroad-I3PHUGI6-_VCpgZR5.js → railroad-I3PHUGI6-BmEDV5kf.js} +1 -1
  128. package/studio/dist/assets/{railroad-abnf-LNEFI6M7-DkSpQ5YY.js → railroad-abnf-LNEFI6M7-Cs_sArMc.js} +1 -1
  129. package/studio/dist/assets/{railroad-ebnf-DWYD2UWJ-BqWILdGb.js → railroad-ebnf-DWYD2UWJ-DYuCq46d.js} +1 -1
  130. package/studio/dist/assets/{railroad-peg-JR7BVNR7-BB0bQL-Z.js → railroad-peg-JR7BVNR7-BcXSgdMy.js} +1 -1
  131. package/studio/dist/assets/{railroadDiagram-Y72NRBL7-CjLKjIdJ.js → railroadDiagram-Y72NRBL7-DBcc8b2c.js} +1 -1
  132. package/studio/dist/assets/{rehype-katex-C-sKddsH.js → rehype-katex-BfUIq5As.js} +1 -1
  133. package/studio/dist/assets/{remark-math-C-ox67hE.js → remark-math-XvZOjmDO.js} +1 -1
  134. package/studio/dist/assets/{requirementDiagram-EQYSW5RA-CnwzZqR8.js → requirementDiagram-EQYSW5RA-CVcBgM1L.js} +1 -1
  135. package/studio/dist/assets/{sankeyDiagram-SY24JHO5-CPDugTUl.js → sankeyDiagram-SY24JHO5-DjZ81hDs.js} +1 -1
  136. package/studio/dist/assets/{sequenceDiagram-BLEHNHQU-1dLfeU9Y.js → sequenceDiagram-BLEHNHQU-BNwOa2S7.js} +1 -1
  137. package/studio/dist/assets/{stateDiagram-v2-A6IESKWZ-DR7CmEjj.js → stateDiagram-v2-A6IESKWZ-BFWa5lEx.js} +1 -1
  138. package/studio/dist/assets/{swimlanes-B3ENRKFQ-BAWB9omQ.js → swimlanes-B3ENRKFQ-LfM_Jg6Z.js} +1 -1
  139. package/studio/dist/assets/swimlanesDiagram-AYQRV4AK-BDmpoiGp.js +8 -0
  140. package/studio/dist/assets/{timeline-definition-JXYKGLGB-DNkj8z77.js → timeline-definition-JXYKGLGB-BwGzkWgZ.js} +1 -1
  141. package/studio/dist/assets/{treeView-D4JQ5SDB-BA-ouhMb.js → treeView-D4JQ5SDB-MLk3VuRt.js} +1 -1
  142. package/studio/dist/assets/{treemap-SAJKECNS-BrrGxsAb.js → treemap-SAJKECNS-D8iSjp7L.js} +1 -1
  143. package/studio/dist/assets/{usecaseDiagram-WPOLBNNX-BB3D0Dt_.js → usecaseDiagram-WPOLBNNX-BqxpaNlT.js} +1 -1
  144. package/studio/dist/assets/{vennDiagram-Q3Y3IDCW-BrtqtKTE.js → vennDiagram-Q3Y3IDCW-CpeUDMhQ.js} +1 -1
  145. package/studio/dist/assets/{wardley-7MLQ67FV-BKkemDql.js → wardley-7MLQ67FV-tAMbCZAj.js} +1 -1
  146. package/studio/dist/assets/{wardleyDiagram-IVAIDKWA-DFnI3P5z.js → wardleyDiagram-IVAIDKWA-CDJViWzt.js} +1 -1
  147. package/studio/dist/assets/{xychartDiagram-4URNHDU4-Da0DZG_q.js → xychartDiagram-4URNHDU4-CwaQrd3_.js} +1 -1
  148. package/studio/dist/index.html +1 -1
  149. package/templates/claude/plugin.json +1 -1
  150. package/templates/gemini/GEMINI.md +18 -16
  151. package/web/server.js +3 -3
  152. package/studio/dist/assets/BetaReportPanel-8NrDLiPm.js +0 -1
  153. package/studio/dist/assets/channel-SJiUQu_N.js +0 -1
  154. package/studio/dist/assets/flowDiagram-IUGLLWBU-CVnJYMum.js +0 -1
  155. package/studio/dist/assets/swimlanesDiagram-AYQRV4AK-qeuHru16.js +0 -8
@@ -1,7 +1,12 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
1
+ import { existsSync, mkdirSync, readFileSync } from 'fs';
2
2
  import { join, dirname } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
- import { substituteTokens } from './_shared.js';
4
+ import {
5
+ substituteTokens,
6
+ createInstallRun,
7
+ writeManagedFile,
8
+ printInstallSummary,
9
+ } from './_shared.js';
5
10
 
6
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
12
  const PLUGIN_DIR = join(__dirname, '..', '..');
@@ -15,29 +20,27 @@ export function detect() {
15
20
  export async function install(scope = 'local') {
16
21
  const base = scope === 'global' ? (process.env.HOME || '') : process.cwd();
17
22
  const rulesDir = join(base, '.cursor', 'rules');
23
+ const run = createInstallRun();
24
+
18
25
  if (!existsSync(rulesDir)) {
19
26
  mkdirSync(rulesDir, { recursive: true });
20
27
  console.log('✓ Created .cursor/rules/ directory');
21
28
  }
22
29
 
23
- const mdcPath = join(rulesDir, 'professor.mdc');
24
- if (existsSync(mdcPath)) {
25
- console.log(' ↔ .cursor/rules/professor.mdc (already exists)');
26
- } else {
27
- const frontmatterPath = join(PLUGIN_DIR, 'templates', 'cursor', 'professor.mdc');
28
- const frontmatter = existsSync(frontmatterPath)
29
- ? readFileSync(frontmatterPath, 'utf-8')
30
- : '---\ndescription: Socratic learning assistant - professor mode\nalwaysApply: true\n---\n';
31
- const rawSkill = readFileSync(join(PLUGIN_DIR, 'shared', 'SKILL.md'), 'utf-8');
32
- // Strip YAML frontmatter from SKILL.md so the .mdc file has only one frontmatter block
33
- const skill = rawSkill.startsWith('---')
34
- ? rawSkill.replace(/^---[\s\S]*?---\n*/, '')
35
- : rawSkill;
36
- const combined = frontmatter + '\n' + skill;
37
- const adapted = substituteTokens(combined, 'cursor');
38
- writeFileSync(mdcPath, adapted);
39
- console.log(' + .cursor/rules/professor.mdc');
40
- }
30
+ const frontmatterPath = join(PLUGIN_DIR, 'templates', 'cursor', 'professor.mdc');
31
+ const frontmatter = existsSync(frontmatterPath)
32
+ ? readFileSync(frontmatterPath, 'utf-8')
33
+ : '---\ndescription: Socratic learning assistant - professor mode\nalwaysApply: true\n---\n';
34
+ const rawSkill = readFileSync(join(PLUGIN_DIR, 'shared', 'SKILL.md'), 'utf-8');
35
+ // Strip YAML frontmatter from SKILL.md so the .mdc file has only one frontmatter block
36
+ const skill = rawSkill.startsWith('---')
37
+ ? rawSkill.replace(/^---[\s\S]*?---\n*/, '')
38
+ : rawSkill;
39
+ const adapted = substituteTokens(`${frontmatter}\n${skill}`, 'cursor');
40
+
41
+ writeManagedFile(join(rulesDir, 'professor.mdc'), adapted, run, '.cursor/rules/professor.mdc');
42
+
43
+ printInstallSummary('Cursor', run);
41
44
 
42
45
  console.log(`
43
46
  ✅ Setup complete for Cursor!
@@ -1,7 +1,13 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
1
+ import { existsSync, mkdirSync, readFileSync } from 'fs';
2
2
  import { join, dirname } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
- import { substituteTokens, generateCommandFiles } from './_shared.js';
4
+ import {
5
+ substituteTokens,
6
+ generateCommandFiles,
7
+ createInstallRun,
8
+ writeManagedBlock,
9
+ printInstallSummary,
10
+ } from './_shared.js';
5
11
 
6
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
13
  const PLUGIN_DIR = join(__dirname, '..', '..');
@@ -18,6 +24,8 @@ export function detect() {
18
24
  /** Install professor for Gemini CLI. scope = 'global' → ~/, 'local' → cwd/ */
19
25
  export async function install(scope = 'local') {
20
26
  const base = scope === 'global' ? (process.env.HOME || '') : process.cwd();
27
+ const run = createInstallRun();
28
+
21
29
  // 1. Create .gemini/ directory
22
30
  const geminiDir = join(base, '.gemini');
23
31
  if (!existsSync(geminiDir)) {
@@ -25,35 +33,26 @@ export async function install(scope = 'local') {
25
33
  console.log('✓ Created .gemini/ directory');
26
34
  }
27
35
 
28
- // 2. Compile GEMINI.md from preamble + shared/SKILL.md, with ask_user substitution
36
+ // 2. Refresh the Professor block in GEMINI.md (preamble + shared/SKILL.md), ask_user substituted
29
37
  const preamblePath = join(PLUGIN_DIR, 'templates', 'gemini', 'GEMINI.md');
30
38
  const skillPath = join(PLUGIN_DIR, 'shared', 'SKILL.md');
31
39
  const preamble = existsSync(preamblePath) ? readFileSync(preamblePath, 'utf-8') : '';
32
40
  const skill = readFileSync(skillPath, 'utf-8');
33
- const professorBlock =
34
- `<!-- professor:start -->\n${preamble}\n${skill}\n<!-- professor:end -->`;
35
- const adapted = substituteTokens(professorBlock, 'gemini');
41
+ const adapted = substituteTokens(`${preamble}\n${skill}`, 'gemini');
36
42
 
37
43
  // Global: GEMINI.md lives inside ~/.gemini/; local: GEMINI.md lives at cwd/ (Gemini CLI walks upward)
38
44
  const geminiMdPath = scope === 'global' ? join(geminiDir, 'GEMINI.md') : join(base, 'GEMINI.md');
39
- if (existsSync(geminiMdPath)) {
40
- const existing = readFileSync(geminiMdPath, 'utf-8');
41
- if (existing.includes('<!-- professor:start -->')) {
42
- console.log(' ↔ GEMINI.md (professor block already present, skipping)');
43
- } else {
44
- writeFileSync(geminiMdPath, existing + '\n\n' + adapted);
45
- console.log(' + GEMINI.md (professor block appended to existing file)');
46
- }
47
- } else {
48
- writeFileSync(geminiMdPath, adapted);
49
- console.log(' + GEMINI.md');
50
- }
45
+ const geminiLabel = scope === 'global' ? '~/.gemini/GEMINI.md' : 'GEMINI.md';
46
+ writeManagedBlock(geminiMdPath, adapted, run, geminiLabel);
51
47
 
52
48
  // 3. Generate .toml command files for Gemini CLI
49
+ console.log('✓ Generating command files...');
53
50
  const commandsDir = scope === 'global'
54
51
  ? join(geminiDir, 'commands', 'professor')
55
52
  : join(base, '.gemini', 'commands', 'professor');
56
- generateCommandFiles(commandsDir, 'gemini');
53
+ generateCommandFiles(commandsDir, 'gemini', run);
54
+
55
+ printInstallSummary('Gemini CLI', run);
57
56
 
58
57
  console.log(`
59
58
  ✅ Setup complete for Gemini CLI!
@@ -1,7 +1,18 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
2
2
  import { join, dirname } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
- import { substituteTokens, generateCommandFiles, adaptAgentFrontmatter } from './_shared.js';
4
+ import {
5
+ substituteTokens,
6
+ generateCommandFiles,
7
+ adaptAgentFrontmatter,
8
+ createInstallRun,
9
+ writeManagedFile,
10
+ writeManagedBlock,
11
+ isLegacySkillDocument,
12
+ printInstallSummary,
13
+ BLOCK_START,
14
+ BLOCK_END,
15
+ } from './_shared.js';
5
16
 
6
17
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
18
  const PLUGIN_DIR = join(__dirname, '..', '..');
@@ -25,51 +36,49 @@ export async function install(scope = 'local') {
25
36
  ? join(process.env.HOME || '', '.config', 'opencode')
26
37
  : join(process.cwd(), '.opencode');
27
38
 
39
+ const run = createInstallRun();
40
+
28
41
  if (!existsSync(configBase)) {
29
42
  mkdirSync(configBase, { recursive: true });
30
43
  console.log(`✓ Created ${scope === 'global' ? '~/.config/opencode/' : '.opencode/'} directory`);
31
44
  }
32
45
 
33
- // 1. Write AGENTS.md (OpenCode auto-loads this — no config entry needed)
46
+ // 1. Refresh the Professor block in AGENTS.md (OpenCode auto-loads it — no config entry needed)
34
47
  const agentsMdPath = scope === 'global'
35
48
  ? join(configBase, 'AGENTS.md')
36
49
  : join(process.cwd(), 'AGENTS.md');
50
+ const agentsLabel = scope === 'global' ? '~/.config/opencode/AGENTS.md' : 'AGENTS.md';
51
+ const skillDoc = readFileSync(join(PLUGIN_DIR, 'shared', 'SKILL.md'), 'utf-8');
52
+ const skill = substituteTokens(skillDoc, 'opencode');
53
+
54
+ const existingAgentsMd = existsSync(agentsMdPath) ? readFileSync(agentsMdPath, 'utf-8') : null;
55
+ const isLegacyWholeFile = existingAgentsMd !== null
56
+ && !existingAgentsMd.includes(BLOCK_START)
57
+ && isLegacySkillDocument(existingAgentsMd);
37
58
 
38
- if (!existsSync(agentsMdPath)) {
39
- const skill = readFileSync(join(PLUGIN_DIR, 'shared', 'SKILL.md'), 'utf-8');
40
- const adapted = substituteTokens(skill, 'opencode');
41
- writeFileSync(agentsMdPath, adapted);
42
- console.log(` + ${scope === 'global' ? '~/.config/opencode/AGENTS.md' : 'AGENTS.md'}`);
59
+ if (isLegacyWholeFile) {
60
+ writeManagedFile(agentsMdPath, `${BLOCK_START}\n${skill}\n${BLOCK_END}\n`, run, agentsLabel);
43
61
  } else {
44
- console.log(` ↔ AGENTS.md (already exists)`);
62
+ writeManagedBlock(agentsMdPath, skill, run, agentsLabel);
45
63
  }
46
64
 
47
65
  // 2. Write command files into commands/ dir
66
+ console.log('✓ Generating command files...');
48
67
  const commandsDir = scope === 'global'
49
68
  ? join(configBase, 'commands')
50
69
  : join(process.cwd(), '.opencode', 'commands');
51
-
52
- console.log('✓ Generating command files...');
53
- generateCommandFiles(commandsDir, 'opencode');
70
+ generateCommandFiles(commandsDir, 'opencode', run);
54
71
 
55
72
  // 3. Write agents/professor.md
56
73
  const agentsDir = join(configBase, 'agents');
57
- if (!existsSync(agentsDir)) {
58
- mkdirSync(agentsDir, { recursive: true });
59
- }
60
- const agentFilePath = join(agentsDir, 'professor.md');
61
- if (!existsSync(agentFilePath)) {
62
- let agentSrc = readFileSync(join(PLUGIN_DIR, 'shared', 'agents', 'professor.md'), 'utf-8');
63
- agentSrc = substituteTokens(agentSrc, 'opencode');
64
- agentSrc = adaptAgentFrontmatter(agentSrc, 'opencode');
65
- writeFileSync(agentFilePath, agentSrc);
66
- const displayPath = scope === 'global' ? '~/.config/opencode/agents/professor.md' : '.opencode/agents/professor.md';
67
- console.log(` + ${displayPath}`);
68
- } else {
69
- console.log(' ↔ agents/professor.md (already exists)');
70
- }
74
+ let agentSrc = readFileSync(join(PLUGIN_DIR, 'shared', 'agents', 'professor.md'), 'utf-8');
75
+ agentSrc = substituteTokens(agentSrc, 'opencode');
76
+ agentSrc = adaptAgentFrontmatter(agentSrc, 'opencode');
77
+ const displayPath = scope === 'global' ? '~/.config/opencode/agents/professor.md' : '.opencode/agents/professor.md';
78
+ writeManagedFile(join(agentsDir, 'professor.md'), agentSrc, run, displayPath);
71
79
 
72
- // 4. Write opencode.json — $schema only, no instructions[] field
80
+ // 4. Merge opencode.json — $schema only, no instructions[] field. Not plugin-owned content,
81
+ // so it is counted but never backed up.
73
82
  const opencodeJsonPath = join(configBase, 'opencode.json');
74
83
  let config = {};
75
84
  if (existsSync(opencodeJsonPath)) {
@@ -81,9 +90,19 @@ export async function install(scope = 'local') {
81
90
 
82
91
  if (!config.$schema) config.$schema = 'https://opencode.ai/config.json';
83
92
 
84
- writeFileSync(opencodeJsonPath, JSON.stringify(config, null, 2) + '\n');
85
93
  const configDisplayPath = scope === 'global' ? '~/.config/opencode/opencode.json' : '.opencode/opencode.json';
86
- console.log(` + ${configDisplayPath}`);
94
+ const json = JSON.stringify(config, null, 2) + '\n';
95
+ const previous = existsSync(opencodeJsonPath) ? readFileSync(opencodeJsonPath, 'utf-8') : null;
96
+ if (previous === json) {
97
+ run.unchanged++;
98
+ console.log(` = ${configDisplayPath} (unchanged)`);
99
+ } else {
100
+ writeFileSync(opencodeJsonPath, json);
101
+ run.updated++;
102
+ console.log(` ${previous === null ? '+' : '~'} ${configDisplayPath}`);
103
+ }
104
+
105
+ printInstallSummary('OpenCode', run);
87
106
 
88
107
  console.log(`
89
108
  ✅ Setup complete for OpenCode!
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ronin-skillforce",
3
- "version": "0.1.0",
3
+ "version": "0.2.0-beta.0",
4
4
  "description": "Ronin SkillForce — a Socratic learning coach for AI agents and a local Studio. It teaches by asking questions, grounds lectures in real sources and keeps your progress on disk.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -50,7 +50,8 @@
50
50
  "studio/src/lib/",
51
51
  "README.md",
52
52
  "CHANGELOG.md",
53
- "LICENSE"
53
+ "LICENSE",
54
+ "NOTICE"
54
55
  ],
55
56
  "keywords": [
56
57
  "learning",
@@ -81,8 +82,8 @@
81
82
  "engines": {
82
83
  "node": ">=18.0.0"
83
84
  },
84
- "author": "ronnie-tk",
85
- "license": "MIT",
85
+ "author": "Tuấn Khuất (ronnie)",
86
+ "license": "Apache-2.0",
86
87
  "packageManager": "npm@11.19.0",
87
88
  "devDependencies": {
88
89
  "@playwright/test": "^1.50.0",
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "course-learning-plugin",
3
- "version": "0.1.0",
3
+ "version": "0.2.0-beta.0",
4
4
  "description": "A Socratic learning plugin for Claude Code. The professor agent guides learners through courses using the Socratic method — asking questions rather than giving answers.",
5
5
  "agents": [
6
6
  {