cotutor 0.1.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 (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +49 -0
  3. package/agents/chinese-teacher.md +19 -0
  4. package/agents/homework-aide.md +17 -0
  5. package/agents/math-teacher.md +19 -0
  6. package/agents/planner.md +18 -0
  7. package/agents/reading-teacher.md +19 -0
  8. package/bin/cotutor.js +17 -0
  9. package/dist/cli/doctor.d.ts +19 -0
  10. package/dist/cli/doctor.js +190 -0
  11. package/dist/cli/init.d.ts +17 -0
  12. package/dist/cli/init.js +114 -0
  13. package/dist/cli/main.d.ts +1 -0
  14. package/dist/cli/main.js +123 -0
  15. package/dist/cli/serve.d.ts +19 -0
  16. package/dist/cli/serve.js +22 -0
  17. package/dist/cli/skeleton.d.ts +21 -0
  18. package/dist/cli/skeleton.js +82 -0
  19. package/dist/cli/workspace.d.ts +61 -0
  20. package/dist/cli/workspace.js +199 -0
  21. package/dist/lib/agent-file.d.ts +10 -0
  22. package/dist/lib/agent-file.js +16 -0
  23. package/dist/lib/context-pack.d.ts +12 -0
  24. package/dist/lib/context-pack.js +46 -0
  25. package/dist/lib/conversation.d.ts +28 -0
  26. package/dist/lib/conversation.js +44 -0
  27. package/dist/lib/index.d.ts +9 -0
  28. package/dist/lib/index.js +9 -0
  29. package/dist/lib/kid-view.d.ts +26 -0
  30. package/dist/lib/kid-view.js +38 -0
  31. package/dist/lib/ledger.d.ts +21 -0
  32. package/dist/lib/ledger.js +92 -0
  33. package/dist/lib/plan.d.ts +12 -0
  34. package/dist/lib/plan.js +78 -0
  35. package/dist/lib/sections.d.ts +12 -0
  36. package/dist/lib/sections.js +151 -0
  37. package/dist/lib/transcript.d.ts +36 -0
  38. package/dist/lib/transcript.js +95 -0
  39. package/dist/schema/config.d.ts +182 -0
  40. package/dist/schema/config.js +144 -0
  41. package/dist/schema/context-pack.d.ts +33 -0
  42. package/dist/schema/context-pack.js +26 -0
  43. package/dist/schema/conversation.d.ts +63 -0
  44. package/dist/schema/conversation.js +30 -0
  45. package/dist/schema/index.d.ts +17 -0
  46. package/dist/schema/index.js +17 -0
  47. package/dist/schema/issues.d.ts +6 -0
  48. package/dist/schema/issues.js +37 -0
  49. package/dist/schema/ledger.d.ts +100 -0
  50. package/dist/schema/ledger.js +44 -0
  51. package/dist/schema/plan.d.ts +26 -0
  52. package/dist/schema/plan.js +19 -0
  53. package/dist/schema/sections.d.ts +30 -0
  54. package/dist/schema/sections.js +26 -0
  55. package/dist/server/app.d.ts +13 -0
  56. package/dist/server/app.js +45 -0
  57. package/package.json +62 -0
@@ -0,0 +1,44 @@
1
+ /**
2
+ * 公共账本(《cotutor契约草案.md》§5):observations.jsonl 与 artifacts.jsonl,追加式、一行一条、id 唯一;
3
+ * 改状态追加新行,同 id 后者为准,不改历史。retracted 用追加行(2026-09-08 拍板)。
4
+ */
5
+ import { z } from 'zod';
6
+ export const EvidenceSchema = z.union([
7
+ z.object({ conversation: z.string().min(1), job: z.string().optional() }),
8
+ z.object({ artifact: z.string().min(1) }),
9
+ ]);
10
+ /** 完整观察行 */
11
+ export const ObservationSchema = z.object({
12
+ id: z.string().min(1),
13
+ date: z.string().min(1),
14
+ /** 写它的老师(agent 名)或 parent */
15
+ author: z.string().min(1),
16
+ subject: z.string().optional(),
17
+ topic: z.string().optional(),
18
+ /** 一句话 */
19
+ claim: z.string().min(1),
20
+ evidence: EvidenceSchema.optional(),
21
+ retracted: z.boolean().default(false),
22
+ });
23
+ /** 纠错行:只带 id + retracted + 谁 + 何时 */
24
+ export const ObservationRetractSchema = z.object({
25
+ id: z.string().min(1),
26
+ retracted: z.literal(true),
27
+ by: z.string().min(1),
28
+ date: z.string().min(1),
29
+ });
30
+ export const ObservationLineSchema = z.union([ObservationSchema, ObservationRetractSchema]);
31
+ export const ARTIFACT_KINDS = ['课包', '补讲', '批改', '计划', '其它'];
32
+ export const ARTIFACT_STATUS = ['draft', 'ready', 'accepted', 'retired'];
33
+ /** 产物事件行:首行要带 kind / by / path,后续行只带变化的字段 */
34
+ export const ArtifactEventSchema = z.object({
35
+ id: z.string().min(1),
36
+ kind: z.enum(ARTIFACT_KINDS).optional(),
37
+ by: z.string().optional(),
38
+ at: z.string().min(1),
39
+ status: z.enum(ARTIFACT_STATUS).optional(),
40
+ path: z.string().optional(),
41
+ source: z.object({ conversation: z.string().min(1), job: z.string().optional() }).optional(),
42
+ /** manifest 指纹,同内容重出验收延续;验收开关关掉时可以没有 accepted 行 */
43
+ hash: z.string().optional(),
44
+ });
@@ -0,0 +1,26 @@
1
+ /**
2
+ * 计划文件 vault/计划/<周>.md(《cotutor契约草案.md》§8):frontmatter week / status / author,
3
+ * 正文按老师分 H2(用显示名,2026-09-08 拍板),行内自由。家长改成 confirmed 即真相。
4
+ */
5
+ import { z } from 'zod';
6
+ export declare const PLAN_STATUS: readonly ["draft", "confirmed"];
7
+ export type PlanStatus = (typeof PLAN_STATUS)[number];
8
+ export declare const WEEK_RE: RegExp;
9
+ export declare const PlanSectionSchema: z.ZodObject<{
10
+ title: z.ZodString;
11
+ lines: z.ZodArray<z.ZodString>;
12
+ }, z.core.$strip>;
13
+ export type PlanSection = z.infer<typeof PlanSectionSchema>;
14
+ export declare const PlanSchema: z.ZodObject<{
15
+ week: z.ZodString;
16
+ status: z.ZodEnum<{
17
+ draft: "draft";
18
+ confirmed: "confirmed";
19
+ }>;
20
+ author: z.ZodOptional<z.ZodString>;
21
+ sections: z.ZodArray<z.ZodObject<{
22
+ title: z.ZodString;
23
+ lines: z.ZodArray<z.ZodString>;
24
+ }, z.core.$strip>>;
25
+ }, z.core.$strip>;
26
+ export type Plan = z.infer<typeof PlanSchema>;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 计划文件 vault/计划/<周>.md(《cotutor契约草案.md》§8):frontmatter week / status / author,
3
+ * 正文按老师分 H2(用显示名,2026-09-08 拍板),行内自由。家长改成 confirmed 即真相。
4
+ */
5
+ import { z } from 'zod';
6
+ export const PLAN_STATUS = ['draft', 'confirmed'];
7
+ export const WEEK_RE = /^\d{4}-W\d{2}$/;
8
+ export const PlanSectionSchema = z.object({
9
+ /** H2 标题原文(老师显示名,或「家长」等对不上老师的段) */
10
+ title: z.string().min(1),
11
+ /** 该段的非空行(去掉列表符号) */
12
+ lines: z.array(z.string()),
13
+ });
14
+ export const PlanSchema = z.object({
15
+ week: z.string().regex(WEEK_RE),
16
+ status: z.enum(PLAN_STATUS),
17
+ author: z.string().optional(),
18
+ sections: z.array(PlanSectionSchema),
19
+ });
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 老师最终文本里的两种固定段(《cotutor契约草案.md》§4 / §6):
3
+ * - 「## 待裁量」需要家长拍板的问题 + 选项(HoldupAskV0,沿用 growth-apps 的形状);
4
+ * - 「## 转交」交给另一位老师(HandoffV0,第一期只允许一跳)。
5
+ * 孩子视图剥掉它们;家长视图渲染成按钮;应用按转交 resume 目标老师。解析不出整段当正文——格式是增强不是门槛。
6
+ */
7
+ import { z } from 'zod';
8
+ export declare const HoldupOptionSchema: z.ZodObject<{
9
+ label: z.ZodString;
10
+ note: z.ZodOptional<z.ZodString>;
11
+ recommended: z.ZodOptional<z.ZodBoolean>;
12
+ }, z.core.$strip>;
13
+ export type HoldupOption = z.infer<typeof HoldupOptionSchema>;
14
+ export declare const HoldupAskSchema: z.ZodObject<{
15
+ question: z.ZodString;
16
+ options: z.ZodDefault<z.ZodArray<z.ZodObject<{
17
+ label: z.ZodString;
18
+ note: z.ZodOptional<z.ZodString>;
19
+ recommended: z.ZodOptional<z.ZodBoolean>;
20
+ }, z.core.$strip>>>;
21
+ }, z.core.$strip>;
22
+ export type HoldupAsk = z.infer<typeof HoldupAskSchema>;
23
+ export declare const HandoffSchema: z.ZodObject<{
24
+ to: z.ZodString;
25
+ why: z.ZodOptional<z.ZodString>;
26
+ refs: z.ZodDefault<z.ZodArray<z.ZodString>>;
27
+ }, z.core.$strip>;
28
+ export type Handoff = z.infer<typeof HandoffSchema>;
29
+ export declare const HOLDUP_HEADING = "\u5F85\u88C1\u91CF";
30
+ export declare const HANDOFF_HEADING = "\u8F6C\u4EA4";
@@ -0,0 +1,26 @@
1
+ /**
2
+ * 老师最终文本里的两种固定段(《cotutor契约草案.md》§4 / §6):
3
+ * - 「## 待裁量」需要家长拍板的问题 + 选项(HoldupAskV0,沿用 growth-apps 的形状);
4
+ * - 「## 转交」交给另一位老师(HandoffV0,第一期只允许一跳)。
5
+ * 孩子视图剥掉它们;家长视图渲染成按钮;应用按转交 resume 目标老师。解析不出整段当正文——格式是增强不是门槛。
6
+ */
7
+ import { z } from 'zod';
8
+ import { AGENT_NAME_RE } from "./config.js";
9
+ export const HoldupOptionSchema = z.object({
10
+ label: z.string().min(1),
11
+ note: z.string().optional(),
12
+ recommended: z.boolean().optional(),
13
+ });
14
+ export const HoldupAskSchema = z.object({
15
+ question: z.string().min(1),
16
+ options: z.array(HoldupOptionSchema).default([]),
17
+ });
18
+ export const HandoffSchema = z.object({
19
+ /** 目标老师(agent 名) */
20
+ to: z.string().regex(AGENT_NAME_RE),
21
+ why: z.string().optional(),
22
+ /** 相关文件或产物引用 */
23
+ refs: z.array(z.string()).default([]),
24
+ });
25
+ export const HOLDUP_HEADING = '待裁量';
26
+ export const HANDOFF_HEADING = '转交';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * 服务的路由层:纯函数 route(method, path, ws) → {status, json|html},测试不用起端口。
3
+ * R1 只有查询接口;发消息 / 会话 / 页面在 R2。
4
+ */
5
+ import type { IncomingMessage, ServerResponse } from 'node:http';
6
+ import { type Workspace } from '../cli/workspace.ts';
7
+ export interface RouteResult {
8
+ status: number;
9
+ json?: unknown;
10
+ html?: string;
11
+ }
12
+ export declare function route(method: string, path: string, ws: Workspace): RouteResult;
13
+ export declare function createHandler(ws: Workspace): (req: IncomingMessage, res: ServerResponse) => void;
@@ -0,0 +1,45 @@
1
+ import { listTeachers } from "../schema/index.js";
2
+ import { workspaceReport } from "../cli/workspace.js";
3
+ const esc = (s) => s.replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[c] ?? c);
4
+ export function route(method, path, ws) {
5
+ if (method !== 'GET')
6
+ return { status: 405, json: { error: 'method_not_allowed' } };
7
+ const url = new URL(path, 'http://x');
8
+ switch (url.pathname) {
9
+ case '/api/health':
10
+ return { status: 200, json: { ok: true } };
11
+ case '/api/workspace':
12
+ return { status: 200, json: workspaceReport(ws) };
13
+ case '/api/config':
14
+ return {
15
+ status: 200,
16
+ json: { title: ws.config.title, kid: ws.config.kid, server: ws.config.server, agent: ws.config.agents.default, teachers: listTeachers(ws.config) },
17
+ };
18
+ case '/api/teachers':
19
+ return { status: 200, json: listTeachers(ws.config, { kidOnly: url.searchParams.get('kid') === '1' }) };
20
+ case '/': {
21
+ const teachers = listTeachers(ws.config, { kidOnly: true })
22
+ .map((t) => `<li>${esc(t.avatar ?? '')} ${esc(t.display)}${t.subject ? `<small> · ${esc(t.subject)}</small>` : ''}</li>`)
23
+ .join('');
24
+ return {
25
+ status: 200,
26
+ html: `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${esc(ws.config.title)}</title><h1>${esc(ws.config.title)}</h1><p>R1 骨架:老师们已就位,对话页在 R2。</p><ul>${teachers}</ul>`,
27
+ };
28
+ }
29
+ default:
30
+ return { status: 404, json: { error: 'not_found', path: url.pathname } };
31
+ }
32
+ }
33
+ export function createHandler(ws) {
34
+ return (req, res) => {
35
+ const r = route(req.method ?? 'GET', req.url ?? '/', ws);
36
+ if (r.html !== undefined) {
37
+ res.writeHead(r.status, { 'content-type': 'text/html; charset=utf-8' });
38
+ res.end(r.html);
39
+ }
40
+ else {
41
+ res.writeHead(r.status, { 'content-type': 'application/json; charset=utf-8' });
42
+ res.end(JSON.stringify(r.json ?? null));
43
+ }
44
+ };
45
+ }
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "cotutor",
3
+ "version": "0.1.0",
4
+ "description": "家里的 AI 助教团队:老师 = 可直接对话的 agent 文件 + 自己的会话;一孩一 workspace",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": "github:TarsLab/cotutor",
8
+ "homepage": "https://github.com/TarsLab/cotutor#readme",
9
+ "bugs": "https://github.com/TarsLab/cotutor/issues",
10
+ "keywords": [
11
+ "ai",
12
+ "agent",
13
+ "claude-code",
14
+ "qwen-code",
15
+ "tutor",
16
+ "education",
17
+ "homeschool",
18
+ "cli"
19
+ ],
20
+ "bin": {
21
+ "cotutor": "bin/cotutor.js"
22
+ },
23
+ "exports": {
24
+ "./schema": {
25
+ "types": "./dist/schema/index.d.ts",
26
+ "default": "./dist/schema/index.js"
27
+ },
28
+ "./workspace": {
29
+ "types": "./dist/cli/workspace.d.ts",
30
+ "default": "./dist/cli/workspace.js"
31
+ },
32
+ "./lib": {
33
+ "types": "./dist/lib/index.d.ts",
34
+ "default": "./dist/lib/index.js"
35
+ },
36
+ "./package.json": "./package.json"
37
+ },
38
+ "files": [
39
+ "bin",
40
+ "dist",
41
+ "agents",
42
+ "README.md",
43
+ "LICENSE"
44
+ ],
45
+ "scripts": {
46
+ "typecheck": "tsc --noEmit",
47
+ "test": "node --experimental-strip-types scripts/test.ts",
48
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
49
+ "prepublishOnly": "pnpm run typecheck && pnpm run test && pnpm run build && pnpm run smoke",
50
+ "smoke": "node --experimental-strip-types scripts/smoke.ts"
51
+ },
52
+ "engines": {
53
+ "node": ">=22.18"
54
+ },
55
+ "dependencies": {
56
+ "zod": "^4.4.3"
57
+ },
58
+ "devDependencies": {
59
+ "@types/node": "^22.20.1",
60
+ "typescript": "^5.6.0"
61
+ }
62
+ }