dsh-nerd-mode 0.4.0 → 0.4.3
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/README.md +19 -16
- package/README.zh.md +19 -16
- package/assets/calibration.svg +2 -2
- package/assets/section.md +15 -0
- package/client.js +93 -77
- package/cordis.patch.yml +10 -14
- package/lib/index.js +223 -213
- package/package.json +1 -1
- package/skills/study/SKILL.md +3 -3
- package/skills/study-plus/SKILL.md +26 -19
- package/skills/study-plus/scripts/book-data.mjs +3 -3
- package/skills/study-plus/scripts/build-book.mjs +5 -5
package/cordis.patch.yml
CHANGED
|
@@ -6,23 +6,19 @@
|
|
|
6
6
|
recallSessionEnabled: false
|
|
7
7
|
unseenQuestionCount: 3
|
|
8
8
|
unseenQuestionType: short-answer
|
|
9
|
-
section: |
|
|
10
|
-
Nerd Mode is on for this conversation. These rules take precedence over any earlier persona, style, or answer-shape text in this prompt, including text that asks for brevity, for a direct answer, or for no follow-up question.
|
|
11
|
-
|
|
12
|
-
The `study` and `study-plus` skills are available through the skill tool. An explicit `/study-plus` means the user chose tracked study: load `study-plus` directly with the topic and supplied material, without a route question. Otherwise, load `study` by default for one concept or one learning task and use chat output. Follow `study` for lecture-writing standards. Save other artifacts only when the user asks for a path. Do not let `study` read or write tracker files in `study/<topic>/`. Do not grade, score, or log in `study`.
|
|
13
|
-
|
|
14
|
-
Every other request keeps the base mode's behavior: produce the artifact, run the command, make the edit. Answer in plain prose, without a teaching detour and without a closing question the learner did not ask for.
|
|
15
|
-
|
|
16
|
-
Study-plus loads `study` before Teach. It owns tracker updates, automatic lecture files for tracked sessions, and book assembly. Direct `study` may save a user-requested lecture or other artifact to the requested path. The four tracker files remain study-plus-only. Do not grade in `study`.
|
|
17
|
-
|
|
18
|
-
Teaching level: run calibration once per new topic, after graph approval and before any tracker file is written. Ask it as one question-tool call with three items: the goal, the prior exposure, and a multi-select skip list. Ask the goal and background items only when the current request and the supplied material do not already state them. Derive `beginner`, `standard`, or `advanced`. Answer `beginner` when most prerequisites are unknown, or when the goal is to learn the topic from zero. Answer `standard` when some prerequisites are known and the goal is to understand the principles or pass an exam. Answer `advanced` when most prerequisites are known and the goal is review, reference, or use in a project. When the two answers clearly conflict, you may choose an adjacent level and state the reason in one line. Write `Goal`, `Background`, and `Teaching level` in `graph.md`. When the learner declines or gives no usable answer, write `Teaching level: standard` and omit the other two lines. Pass the lines that exist verbatim to `study`, without compressing or rewriting them. A missing `Teaching level` line means `standard`. Never ask for calibration on a topic that already has a `graph.md`. A mid-topic change of depth lasts for this session only. Only an explicit "from now on" rewrites the `Teaching level` line.
|
|
19
|
-
|
|
20
|
-
Routing order: an explicit `/study-plus` loads `study-plus` directly, even for multiple independent concepts, and never asks a route question. Otherwise, for multiple independent concepts, ask exactly one question-tool choice before loading either skill. This includes default Nerd Mode and explicit `/study` entry. Offer only one-off complete Book, Chapter, and Section content in this conversation, or tracked study-plus with one concept per session. If the learner chooses one-off, load `study` with the current topic and supplied material. If the learner chooses tracked, load `study-plus` with the current topic and supplied material. Never ask again after the learner chooses. For one concept or one learning task without explicit `/study-plus`, load `study` by default.
|
|
21
|
-
|
|
22
|
-
`study` defaults to chat output. Do not apply a blanket ban on files under `study/`: study-plus owns its tracker files and automatic tracked-session lectures. Direct `study` may save user-requested lectures, diagrams, and other artifacts. Only study-plus may read or write `graph.md`, `state.md`, `log.md`, and `plan.md`.
|
|
23
9
|
- id: study-plus-skills
|
|
24
10
|
name: '@deepseek-ai/dsh-skill-filesystem'
|
|
25
11
|
config:
|
|
26
12
|
providerName: study-plus
|
|
27
13
|
includeDefaultRoots: false
|
|
28
14
|
bundledSkillDir: !!js process.getBuiltinModule('node:path').join(process.getBuiltinModule('node:path').dirname(process.getBuiltinModule('node:module').createRequire(baseUrl).resolve('dsh-nerd-mode/package.json')), 'skills')
|
|
15
|
+
|
|
16
|
+
# Goal mode stays off. The built-in create_goal description tells the model to
|
|
17
|
+
# arm a goal for any long-running request, which turns one study answer into an
|
|
18
|
+
# auto-continuing run. Without the three packages below, no goal can be created.
|
|
19
|
+
- id: tool-goal
|
|
20
|
+
disabled: true
|
|
21
|
+
- id: command-goal
|
|
22
|
+
disabled: true
|
|
23
|
+
- id: goal-round-driver
|
|
24
|
+
disabled: true
|
package/lib/index.js
CHANGED
|
@@ -1,213 +1,223 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Nerd Mode: a per-session switch. While it is on, one prompt section joins
|
|
3
|
-
* every request and the `study` skill becomes the default answer shape. State
|
|
4
|
-
* folds from the session log, so it survives compaction, resume, and fork. The
|
|
5
|
-
* tool set never changes.
|
|
6
|
-
*
|
|
7
|
-
* The section registers at the profile level, not inside a preset, so it
|
|
8
|
-
* reaches every agent in every preset. See docs/adr/0008.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export const
|
|
17
|
-
|
|
18
|
-
export const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
function
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Nerd Mode: a per-session switch. While it is on, one prompt section joins
|
|
3
|
+
* every request and the `study` skill becomes the default answer shape. State
|
|
4
|
+
* folds from the session log, so it survives compaction, resume, and fork. The
|
|
5
|
+
* tool set never changes.
|
|
6
|
+
*
|
|
7
|
+
* The section registers at the profile level, not inside a preset, so it
|
|
8
|
+
* reaches every agent in every preset. See docs/adr/0008.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync } from 'node:fs'
|
|
12
|
+
import { fileURLToPath } from 'node:url'
|
|
13
|
+
import { COMMAND, KEY, fold, init } from './fold.js'
|
|
14
|
+
import z from '@deepseek-ai/schemastery'
|
|
15
|
+
|
|
16
|
+
export const name = 'dsh-nerd-mode'
|
|
17
|
+
export const PACKAGE_NAME = 'dsh-nerd-mode'
|
|
18
|
+
export const inject = ['systemPrompt', 'sessionProjections']
|
|
19
|
+
|
|
20
|
+
// The prompt text ships with the package. Keeping it out of the config stops
|
|
21
|
+
// every profile patch from carrying a stale copy of it.
|
|
22
|
+
const BUNDLED_SECTION = readFileSync(
|
|
23
|
+
fileURLToPath(new URL('../assets/section.md', import.meta.url)),
|
|
24
|
+
'utf8',
|
|
25
|
+
).trim()
|
|
26
|
+
|
|
27
|
+
export const Config = z.object({
|
|
28
|
+
section: z.string().pattern(/\S/).default(BUNDLED_SECTION),
|
|
29
|
+
recallSessionEnabled: z.boolean().default(false).volatile(),
|
|
30
|
+
unseenQuestionCount: z.number().step(1).min(0).max(10).default(3).volatile(),
|
|
31
|
+
unseenQuestionType: z.union([
|
|
32
|
+
z.const('short-answer'),
|
|
33
|
+
z.const('multiple-choice'),
|
|
34
|
+
z.const('mixed'),
|
|
35
|
+
]).default('short-answer').volatile(),
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const SECTION = 'study:policy'
|
|
39
|
+
// Between PLAN_POLICY (500) and TEAM_POLICY (600).
|
|
40
|
+
const SECTION_ORDER = 550
|
|
41
|
+
const studyPlusDefaultsBySession = new WeakMap()
|
|
42
|
+
const directStudyPlusBySession = new WeakMap()
|
|
43
|
+
// A PTC skill load leaves no message in the history, so keep its id for later
|
|
44
|
+
// turns the way an injected skill message would.
|
|
45
|
+
const ptcStudyPlusBySession = new WeakMap()
|
|
46
|
+
const SKILL_COMMAND = /(^|\s)\/([a-z0-9]+(?:-[a-z0-9]+)*)(?=\s|$)/g
|
|
47
|
+
|
|
48
|
+
// shortcut: parse-only schemas, no zod. The projection registry calls `parse`
|
|
49
|
+
// and nothing else. Upgrade to zod if the host starts introspecting schemas.
|
|
50
|
+
const stateSchema = {
|
|
51
|
+
parse: (value) => ({
|
|
52
|
+
active: value?.active === true,
|
|
53
|
+
running:
|
|
54
|
+
value?.running && typeof value.running.commandId === 'string'
|
|
55
|
+
? { commandId: value.running.commandId, wanted: value.running.wanted === true }
|
|
56
|
+
: null,
|
|
57
|
+
}),
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const viewSchema = { parse: (value) => ({ active: value?.active === true }) }
|
|
61
|
+
|
|
62
|
+
function studyPlusSettings(config, session, invocationId) {
|
|
63
|
+
let defaults = studyPlusDefaultsBySession.get(session)
|
|
64
|
+
if (defaults?.invocationId !== invocationId) {
|
|
65
|
+
defaults = {
|
|
66
|
+
invocationId,
|
|
67
|
+
recallSessionEnabled: config.recallSessionEnabled?.get?.() ?? false,
|
|
68
|
+
unseenQuestionCount: config.unseenQuestionCount?.get?.() ?? 3,
|
|
69
|
+
unseenQuestionType: config.unseenQuestionType?.get?.() ?? 'short-answer',
|
|
70
|
+
}
|
|
71
|
+
studyPlusDefaultsBySession.set(session, defaults)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return [
|
|
75
|
+
'',
|
|
76
|
+
'',
|
|
77
|
+
'Study-plus profile defaults for this tracked session:',
|
|
78
|
+
`- Recall Session: ${defaults.recallSessionEnabled ? 'enabled' : 'disabled'}.`,
|
|
79
|
+
`- Post-lecture questions: ${defaults.unseenQuestionCount}; type: ${defaults.unseenQuestionType}.`,
|
|
80
|
+
'Apply these defaults only during tracked study-plus sessions. Ignore them for ordinary study and all other requests.',
|
|
81
|
+
'An explicit preference in the current /study-plus request overrides its profile default for this session and does not update the profile.',
|
|
82
|
+
].join('\n')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Return the message id when the message is a direct /study-plus command, else undefined. */
|
|
86
|
+
function directStudyPlusId(message) {
|
|
87
|
+
if (message?.source?.kind !== 'user') return undefined
|
|
88
|
+
const text = (message.content ?? [])
|
|
89
|
+
.filter((block) => block.type === 'text')
|
|
90
|
+
.map((block) => block.text)
|
|
91
|
+
.join('\n')
|
|
92
|
+
return [...text.matchAll(SKILL_COMMAND)].at(-1)?.[2] === 'study-plus' ? message.id : undefined
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function studyPlusInvocation(session) {
|
|
96
|
+
const directInvocation = directStudyPlusBySession.get(session)
|
|
97
|
+
if (directInvocation !== undefined) return directInvocation
|
|
98
|
+
const ptcInvocation = ptcStudyPlusBySession.get(session)
|
|
99
|
+
if (ptcInvocation !== undefined) return ptcInvocation
|
|
100
|
+
|
|
101
|
+
// The host returns the full surface history here, not just this turn. See docs/agents/dsh-session-api.md.
|
|
102
|
+
const invocation = session.deriveMessages().findLast((message) => message.source?.kind === 'skill-invocation')
|
|
103
|
+
if (invocation?.source.name === 'study-plus') return invocation.id
|
|
104
|
+
|
|
105
|
+
const completedCalls = new Set()
|
|
106
|
+
let loadedId
|
|
107
|
+
let directId
|
|
108
|
+
let skillCall
|
|
109
|
+
let ptcSkillCall
|
|
110
|
+
for (let seq = session.seq - 1; seq >= 0; seq -= 1) {
|
|
111
|
+
const event = session.eventAt(seq)
|
|
112
|
+
if (event?.type === 'turn/start') break
|
|
113
|
+
// A resumed, forked, or seeded log may carry a partial event payload, so
|
|
114
|
+
// every nested read here is guarded. One bad record must not fail a turn.
|
|
115
|
+
if (event?.type === 'tool/result') {
|
|
116
|
+
const result = event.data?.message
|
|
117
|
+
if (result !== undefined && result.isError !== true && result.source?.callId !== undefined) {
|
|
118
|
+
completedCalls.add(result.source.callId)
|
|
119
|
+
}
|
|
120
|
+
} else if (event?.type === 'tool/call' && event.data?.name === 'skill') {
|
|
121
|
+
try {
|
|
122
|
+
if (JSON.parse(event.data.arguments).name === 'study-plus') skillCall = event.data.callId
|
|
123
|
+
} catch {}
|
|
124
|
+
} else if (event?.type === 'tool/ptc-dispatch' && event.data?.name === 'skill') {
|
|
125
|
+
// PTC records a skill call made inside run_code as a dispatch, not as a
|
|
126
|
+
// `tool/call`, and the dispatch event itself carries the outcome.
|
|
127
|
+
if (event.data.isError !== true && event.data.arguments?.name === 'study-plus') {
|
|
128
|
+
ptcSkillCall = event.data.subCallId
|
|
129
|
+
}
|
|
130
|
+
} else if (event?.type === 'user/message') {
|
|
131
|
+
// `user/message` stores the message fields directly in `data`; only
|
|
132
|
+
// `tool/result` and `system/message` wrap them under `data.message`.
|
|
133
|
+
const message = event.data
|
|
134
|
+
if (message?.source?.kind === 'skill-invocation') {
|
|
135
|
+
if (message.source.name === 'study-plus') loadedId = message.id
|
|
136
|
+
} else {
|
|
137
|
+
directId = directStudyPlusId(message) ?? directId
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// A skill load counts only when its tool call completed. A typed command counts at once.
|
|
143
|
+
if (skillCall !== undefined) {
|
|
144
|
+
return completedCalls.has(skillCall) ? loadedId ?? directId ?? skillCall : undefined
|
|
145
|
+
}
|
|
146
|
+
if (ptcSkillCall !== undefined) {
|
|
147
|
+
ptcStudyPlusBySession.set(session, ptcSkillCall)
|
|
148
|
+
return loadedId ?? directId ?? ptcSkillCall
|
|
149
|
+
}
|
|
150
|
+
return loadedId ?? directId
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const projection = {
|
|
154
|
+
key: KEY,
|
|
155
|
+
stateVersion: 1,
|
|
156
|
+
stateSchema,
|
|
157
|
+
init,
|
|
158
|
+
apply: fold,
|
|
159
|
+
wire: {
|
|
160
|
+
viewSchema,
|
|
161
|
+
view: (state) => ({ active: state.active }),
|
|
162
|
+
},
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* @param config - `section` is an optional overlay text; a missing value uses
|
|
167
|
+
* the bundled `assets/section.md`.
|
|
168
|
+
*/
|
|
169
|
+
export function apply(ctx, config) {
|
|
170
|
+
const section = config?.section
|
|
171
|
+
if (typeof section !== 'string' || section.trim() === '') {
|
|
172
|
+
throw new Error('dsh-nerd-mode needs a non-empty string `section`')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
ctx.sessionProjections.register(projection)
|
|
176
|
+
|
|
177
|
+
ctx.effect(
|
|
178
|
+
() => ctx.on('agent/inbox/claimed', ({ agent, message }) => {
|
|
179
|
+
directStudyPlusBySession.set(agent.session, directStudyPlusId(message))
|
|
180
|
+
}),
|
|
181
|
+
'dsh-nerd-mode: study-plus claim',
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
ctx.effect(
|
|
185
|
+
() =>
|
|
186
|
+
ctx.systemPrompt.section({
|
|
187
|
+
name: SECTION,
|
|
188
|
+
order: SECTION_ORDER,
|
|
189
|
+
text: (context) => {
|
|
190
|
+
if (context.agent === undefined) return ''
|
|
191
|
+
if (!ctx.sessionProjections.stateOf(context.agent.session, KEY).active) return ''
|
|
192
|
+
const session = context.agent.session
|
|
193
|
+
const invocationId = studyPlusInvocation(session)
|
|
194
|
+
if (invocationId === undefined) return section
|
|
195
|
+
return section + studyPlusSettings(config ?? {}, session, invocationId)
|
|
196
|
+
},
|
|
197
|
+
}),
|
|
198
|
+
'dsh-nerd-mode: section',
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
ctx.inject(['commands'], (commandCtx) => {
|
|
202
|
+
commandCtx.commands.register({
|
|
203
|
+
name: COMMAND,
|
|
204
|
+
description: 'Turn Nerd Mode on or off for this conversation',
|
|
205
|
+
input: { hint: '[on|off]' },
|
|
206
|
+
handler: ({ agent, rawInput }) => {
|
|
207
|
+
const arg = rawInput.trim().toLowerCase()
|
|
208
|
+
const active = ctx.sessionProjections.stateOf(agent.session, KEY).active
|
|
209
|
+
|
|
210
|
+
if (arg === 'off') {
|
|
211
|
+
return { kind: 'success', text: active ? 'Nerd Mode off.' : 'Nerd Mode is already off.' }
|
|
212
|
+
}
|
|
213
|
+
if (arg !== '' && arg !== 'on') {
|
|
214
|
+
return { kind: 'error', text: `Unknown argument "${arg}". Use /nerd-mode on or /nerd-mode off.` }
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
kind: 'success',
|
|
218
|
+
text: active ? 'Nerd Mode is already on.' : 'Nerd Mode on. /study guides a concept or a task.',
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
})
|
|
222
|
+
})
|
|
223
|
+
}
|
package/package.json
CHANGED
package/skills/study/SKILL.md
CHANGED
|
@@ -48,7 +48,7 @@ For `advanced`:
|
|
|
48
48
|
|
|
49
49
|
Accuracy rules outrank the level. No level removes a symbol definition, a justification for a derivation step, a stated assumption or approximation, or a bound. No level adds filler, repeated explanation, or a section the concept does not need. The level affects the lecture only.
|
|
50
50
|
|
|
51
|
-
For a one-off request with no graph, derive the level from the request and the supplied material. When neither states a goal or a background, ask one combined question before teaching: what the learner wants to reach and whether they have met the topic before. State the derived level in one line, then teach. Write no file.
|
|
51
|
+
For a one-off request with no graph, derive the level from the brief the upper layer supplies and from the request and the supplied material. A brief from an upper layer is final: never ask again for a goal or a background it leaves out. A bare topic, or "I want to learn X", states no goal. When no brief arrives and neither the request nor the supplied material states a goal or a background, ask one combined question before teaching: what the learner wants to reach and whether they have met the topic before. State the derived level in one line, then teach. Write no file.
|
|
52
52
|
|
|
53
53
|
## Format the answer
|
|
54
54
|
|
|
@@ -73,6 +73,6 @@ The upper layer may hand you an approved concept graph, the relevant supplied ma
|
|
|
73
73
|
|
|
74
74
|
Default to chat output. If the learner asks to save a lecture or other artifact, save it to the requested path and show the same content in chat when practical.
|
|
75
75
|
|
|
76
|
-
When an SVG would materially clarify a relationship, create it by default
|
|
76
|
+
When an SVG would materially clarify a relationship, create it by default. When the upper layer scopes a Course, write `study/<course>/diagrams/<name>.svg`. When the guide runs alone, write `diagrams/<name>.svg` and create no folder under `study/`. Load `diagram` for its visual authoring rules, but use this study-specific path instead of its generic default. Sanitize `course` and `name` as single lowercase kebab-case path segments with no slashes or dot segments. If either is unclear, choose a safe fallback. Before writing, confirm the resolved parent stays inside the intended `diagrams/` root and the target is not `study/<course>/graph.md`, `state.md`, `log.md`, or `plan.md`. Compute plotted points rather than placing them by eye. Before replacing an SVG with the same name, copy the old file to `<name>.svg.bak`, replacing any older backup.
|
|
77
77
|
|
|
78
|
-
Never read or write `study/<
|
|
78
|
+
Never read or write `study/<course>/graph.md`, `state.md`, `log.md`, or `plan.md`. Do not read or change tracker state. Study-plus owns the choice of lecture paths and the automatic lecture save during tracked sessions. Study may read or write ordinary artifacts, including lectures, drafts, and figures, at paths the user or the upper layer gives it.
|