ai-growth 1.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 (75) hide show
  1. package/.env.example +19 -0
  2. package/AGENTS.md +232 -0
  3. package/IMPLEMENTATION_PLAN.md +207 -0
  4. package/LICENSE +21 -0
  5. package/PRODUCT_SPEC.md +1776 -0
  6. package/README.md +384 -0
  7. package/SKILL.md +121 -0
  8. package/START-HERE.md +156 -0
  9. package/dist/api/app.js +206 -0
  10. package/dist/api/server.js +42 -0
  11. package/dist/cli/main.js +799 -0
  12. package/dist/core/actions.js +264 -0
  13. package/dist/core/context.js +7 -0
  14. package/dist/core/directions.js +26 -0
  15. package/dist/core/evidence.js +155 -0
  16. package/dist/core/goals.js +188 -0
  17. package/dist/core/lifeContext.js +41 -0
  18. package/dist/core/memory.js +35 -0
  19. package/dist/core/metrics.js +74 -0
  20. package/dist/core/profile.js +95 -0
  21. package/dist/core/progress.js +244 -0
  22. package/dist/core/prompts/index.js +40 -0
  23. package/dist/core/replan.js +100 -0
  24. package/dist/core/rollover.js +68 -0
  25. package/dist/core/rolloverTypes.js +1 -0
  26. package/dist/core/stateMachine.js +61 -0
  27. package/dist/core/status.js +57 -0
  28. package/dist/core/types.js +9 -0
  29. package/dist/daemon/main.js +64 -0
  30. package/dist/db/cli.js +19 -0
  31. package/dist/db/client.js +36 -0
  32. package/dist/db/migrate.js +34 -0
  33. package/dist/db/migrations/V1__init.sql +249 -0
  34. package/dist/device/card.js +257 -0
  35. package/dist/device/deck.js +157 -0
  36. package/dist/device/fontCoverage.js +162 -0
  37. package/dist/device/http.js +170 -0
  38. package/dist/device/png.js +111 -0
  39. package/dist/device/provision.js +185 -0
  40. package/dist/device/render.js +194 -0
  41. package/dist/device/renderCard.js +48 -0
  42. package/dist/device/report.js +146 -0
  43. package/dist/device/serial.js +201 -0
  44. package/dist/device/token.js +78 -0
  45. package/dist/device/types.js +2 -0
  46. package/dist/mcp/ctx.js +48 -0
  47. package/dist/mcp/server.js +15 -0
  48. package/dist/mcp/tools.js +586 -0
  49. package/dist/passport/builder.js +83 -0
  50. package/dist/passport/state.js +81 -0
  51. package/dist/passport/sync.js +61 -0
  52. package/dist/reminder/notify.js +30 -0
  53. package/dist/reminder/scheduler.js +87 -0
  54. package/dist/reminder/service.js +102 -0
  55. package/dist/shared/config.js +50 -0
  56. package/dist/shared/result.js +27 -0
  57. package/dist/shared/time.js +67 -0
  58. package/dist/shared/util.js +21 -0
  59. package/dist/ui/app.js +500 -0
  60. package/dist/ui/index.html +38 -0
  61. package/dist/ui/style.css +367 -0
  62. package/docs/images/cards.png +0 -0
  63. package/docs/images/cover.png +0 -0
  64. package/launchd/com.lairey.ai-growth.daemon.plist +45 -0
  65. package/llms.txt +52 -0
  66. package/package.json +79 -0
  67. package/scripts/check-device-cards.mjs +177 -0
  68. package/scripts/doctor.sh +194 -0
  69. package/scripts/gen-device-assets.mjs +188 -0
  70. package/scripts/install.sh +291 -0
  71. package/scripts/preview-cards.mjs +120 -0
  72. package/scripts/reset-dev-data.sh +92 -0
  73. package/scripts/smoke-device.mjs +401 -0
  74. package/scripts/uninstall.sh +63 -0
  75. package/server.json +56 -0
@@ -0,0 +1,68 @@
1
+ import { setSystemState, lastRolloverDate, todayIn } from './profile.js';
2
+ import { listOpenActionsBefore, expireActionInternal, markPendingReplanInternal } from './actions.js';
3
+ import { cancelRemindersByAction } from '../reminder/service.js';
4
+ /**
5
+ * Daily Rollover(§11 / §23.4)。必须幂等。
6
+ *
7
+ * 当检测日期从 D1 进入 D2:
8
+ * 1. D1 未完成 DAILY → EXPIRED,取消未触发提醒
9
+ * 2. D1 未完成 MAIN → PENDING_REPLAN,取消未触发提醒
10
+ * 3. 不 clone 昨日任务到今天(Agent 在 daily planning 时按今天上下文重新创建)
11
+ *
12
+ * 幂等分两层:日期级(last_rollover_date)与 action 级(已是目标状态则跳过)。
13
+ * 后者不可省:若某个 MAIN 连续多天未被 replan,它每天都会出现在开放集合中,
14
+ * 而 `PENDING_REPLAN → PENDING_REPLAN` 是非法转换 —— 会抛错导致整个事务回滚,
15
+ * 把系统永久卡在无法 rollover 的状态。
16
+ */
17
+ export function runDailyRollover(ctx, todayArg) {
18
+ const today = todayArg ?? todayIn(ctx);
19
+ const last = lastRolloverDate(ctx);
20
+ if (last === today) {
21
+ return { today, alreadyRan: true, expiredDailyActionIds: [], pendingReplanMainQuestIds: [], cancelledReminderCount: 0 };
22
+ }
23
+ const openBefore = listOpenActionsBefore(ctx, today);
24
+ const expiredDailyActionIds = [];
25
+ const pendingReplanMainQuestIds = [];
26
+ let cancelledReminderCount = 0;
27
+ const tx = ctx.db.transaction(() => {
28
+ for (const action of openBefore) {
29
+ if (action.kind === 'DAILY') {
30
+ expireActionInternal(ctx, action.id);
31
+ expiredDailyActionIds.push(action.id);
32
+ }
33
+ else if (action.status === 'PENDING_REPLAN') {
34
+ // 已在等待 Replan:不再重复转换状态,只兜底清理提醒(action 级幂等)
35
+ cancelledReminderCount += cancelRemindersByAction(ctx, action.id, 'daily rollover (already pending replan)');
36
+ continue;
37
+ }
38
+ else {
39
+ markPendingReplanInternal(ctx, action.id);
40
+ pendingReplanMainQuestIds.push(action.id);
41
+ }
42
+ // rollover 专用:昨日未触发提醒一律取消(即使 action 状态已终态也兜底清理)
43
+ cancelledReminderCount += cancelRemindersByAction(ctx, action.id, 'daily rollover');
44
+ }
45
+ setSystemState(ctx, 'last_rollover_date', today);
46
+ });
47
+ tx();
48
+ return {
49
+ today,
50
+ alreadyRan: false,
51
+ expiredDailyActionIds,
52
+ pendingReplanMainQuestIds,
53
+ cancelledReminderCount,
54
+ };
55
+ }
56
+ /** 是否有等待处理的 Replan(growth_status / Agent Handoff 用) */
57
+ export function hasPendingReplan(ctx) {
58
+ const row = ctx.db
59
+ .prepare(`SELECT COUNT(*) AS n FROM actions WHERE status='PENDING_REPLAN' AND deleted_at IS NULL`)
60
+ .get();
61
+ return row.n > 0;
62
+ }
63
+ export function listPendingReplanActions(ctx) {
64
+ const rows = ctx.db
65
+ .prepare(`SELECT * FROM actions WHERE status='PENDING_REPLAN' AND deleted_at IS NULL ORDER BY date_key`)
66
+ .all();
67
+ return rows;
68
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,61 @@
1
+ import { DomainError, ErrorCodes } from '../shared/result.js';
2
+ /**
3
+ * 状态机转换表(附录 B)。不得允许非法状态转换静默发生。
4
+ *
5
+ * 关于 PLANNED → COMPLETED:IN_PROGRESS 是**可选**的中间态(用于跨度较大的任务)。
6
+ * 用户/Agent 说"做完了"时若从未显式 start,直接完成是合法的 —— 否则会强迫用户
7
+ * 为一个仪式性动作多操作一步,违反 §21「低风险任务允许一句话确认」与 §17.2。
8
+ *
9
+ * 关于 EXPIRED:任何**未终结**的 DAILY 跨天后都必须能过期。
10
+ * 用户会「开始了没做完」(IN_PROGRESS) 或「主动延期」(DELAYED),这些同样要过期,
11
+ * 否则 rollover 会抛异常并整体回滚,把系统卡死在无法 rollover 的状态。
12
+ * (EXPIRED 仅 DAILY 的约束由 assertActionTransition 的 kind 检查保证。)
13
+ *
14
+ * Action 注意:PENDING_REPLAN 主要为 MAIN。
15
+ */
16
+ const ACTION_TRANSITIONS = {
17
+ DRAFT: ['PLANNED', 'CANCELLED'],
18
+ PLANNED: ['IN_PROGRESS', 'COMPLETED', 'CANCELLED', 'SKIPPED', 'EXPIRED', 'PENDING_REPLAN', 'DELAYED', 'DRAFT'],
19
+ IN_PROGRESS: ['COMPLETED', 'SKIPPED', 'CANCELLED', 'PENDING_REPLAN', 'DELAYED', 'EXPIRED'],
20
+ DELAYED: ['PLANNED', 'COMPLETED', 'CANCELLED', 'PENDING_REPLAN', 'SKIPPED', 'EXPIRED'],
21
+ PENDING_REPLAN: ['PLANNED', 'IN_PROGRESS', 'COMPLETED', 'CANCELLED', 'DELAYED', 'SKIPPED', 'EXPIRED'],
22
+ COMPLETED: [],
23
+ SKIPPED: [],
24
+ EXPIRED: [],
25
+ CANCELLED: [],
26
+ };
27
+ const GOAL_TRANSITIONS = {
28
+ DRAFT: ['CONFIRMED'],
29
+ CONFIRMED: ['ACTIVE'],
30
+ ACTIVE: ['PAUSED', 'COMPLETED', 'ABANDONED'],
31
+ PAUSED: ['ACTIVE', 'ABANDONED', 'COMPLETED'],
32
+ COMPLETED: [],
33
+ ABANDONED: [],
34
+ };
35
+ export function assertActionTransition(from, to, kind) {
36
+ if (to === 'EXPIRED' && kind !== 'DAILY') {
37
+ throw new DomainError(ErrorCodes.INVALID_STATE, `EXPIRED is only valid for DAILY actions, got ${kind}`);
38
+ }
39
+ const allowed = ACTION_TRANSITIONS[from] ?? [];
40
+ if (!allowed.includes(to)) {
41
+ throw new DomainError(ErrorCodes.INVALID_STATE, `Illegal action transition: ${from} → ${to}`);
42
+ }
43
+ }
44
+ export function canActionTransition(from, to, kind) {
45
+ try {
46
+ assertActionTransition(from, to, kind);
47
+ return true;
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ }
53
+ export function assertGoalTransition(from, to) {
54
+ const allowed = GOAL_TRANSITIONS[from] ?? [];
55
+ if (!allowed.includes(to)) {
56
+ throw new DomainError(ErrorCodes.INVALID_STATE, `Illegal goal transition: ${from} → ${to}`);
57
+ }
58
+ }
59
+ export function isTerminalActionStatus(s) {
60
+ return ['COMPLETED', 'SKIPPED', 'EXPIRED', 'CANCELLED'].includes(s);
61
+ }
@@ -0,0 +1,57 @@
1
+ import { getProfile, ensureProfile, getSetting } from './profile.js';
2
+ import { getActiveGoals, getCurrentStage } from './goals.js';
3
+ import { getTodayPlan } from './actions.js';
4
+ import { hasPendingReplan } from './rollover.js';
5
+ import { getActiveLifeContext } from './lifeContext.js';
6
+ /**
7
+ * growth_status(§27 Agent Handoff)。
8
+ * 一个新 Agent 调用此 tool 就能判断当前该做什么。
9
+ */
10
+ export function growthStatus(ctx) {
11
+ const profile = getProfile(ctx);
12
+ const onboarding = !profile || profile.onboardingStatus === 'NOT_STARTED'
13
+ ? 'NOT_STARTED'
14
+ : profile.onboardingStatus === 'IN_PROGRESS'
15
+ ? 'IN_PROGRESS'
16
+ : 'COMPLETED';
17
+ const goals = getActiveGoals(ctx);
18
+ const primaryGoal = goals[0];
19
+ const stage = primaryGoal ? getCurrentStage(ctx, primaryGoal.id) : null;
20
+ const { plan, actions: todayActionsAll } = getTodayPlan(ctx);
21
+ const life = getActiveLifeContext(ctx);
22
+ const passportUpdated = getSetting(ctx, 'passport_last_generated_at') ?? undefined;
23
+ let suggested = 'START_ONBOARDING';
24
+ if (onboarding !== 'COMPLETED') {
25
+ suggested = onboarding === 'NOT_STARTED' ? 'START_ONBOARDING' : 'CONTINUE_ONBOARDING';
26
+ }
27
+ else if (hasPendingReplan(ctx)) {
28
+ suggested = 'RESOLVE_REPLAN';
29
+ }
30
+ else if (goals.length === 0) {
31
+ suggested = 'DISCUSS_GOAL';
32
+ }
33
+ else if (!plan) {
34
+ suggested = 'RUN_DAILY_PLANNING';
35
+ }
36
+ else if (plan.status === 'DRAFT') {
37
+ suggested = 'CONFIRM_TODAY_PLAN';
38
+ }
39
+ else {
40
+ suggested = 'CONTINUE_TODAY';
41
+ }
42
+ const todayActions = todayActionsAll.filter((a) => !['COMPLETED', 'SKIPPED', 'CANCELLED', 'EXPIRED'].includes(a.status));
43
+ return {
44
+ onboarding,
45
+ hasProfile: !!profile,
46
+ activeGoalCount: goals.length,
47
+ currentStageTitle: stage?.title,
48
+ currentStageOrder: stage ? `${stage.order}` : undefined,
49
+ todayPlanStatus: !plan ? 'NOT_CREATED' : plan.status,
50
+ pendingReplan: hasPendingReplan(ctx),
51
+ lifeContextMode: life?.mode ?? 'NORMAL',
52
+ passportLastUpdatedAt: passportUpdated,
53
+ suggestedNextOperation: suggested,
54
+ openActionCountToday: todayActions.length,
55
+ };
56
+ }
57
+ export { ensureProfile, getProfile, getActiveGoals };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 领域类型定义(与 DB schema 对应)。
3
+ * 状态机规则见附录 B:
4
+ * - Action: DRAFT → PLANNED → IN_PROGRESS → {COMPLETED, SKIPPED, CANCELLED, EXPIRED(DAILY), PENDING_REPLAN(MAIN), DELAYED}
5
+ * - Goal: DRAFT → CONFIRMED → ACTIVE → {PAUSED↔ACTIVE, COMPLETED, ABANDONED}
6
+ */
7
+ /** 打开(未终态)的 action */
8
+ export const OPEN_ACTION_STATUSES = ['DRAFT', 'PLANNED', 'IN_PROGRESS', 'DELAYED', 'PENDING_REPLAN'];
9
+ export const TERMINAL_ACTION_STATUSES = ['COMPLETED', 'SKIPPED', 'EXPIRED', 'CANCELLED'];
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+ import { appendFileSync, mkdirSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { loadConfig } from '../shared/config.js';
5
+ import { Scheduler } from '../reminder/scheduler.js';
6
+ import { createApiServer } from '../api/app.js';
7
+ import { isDeviceLanEnabled, getOrCreateDeviceToken } from '../device/token.js';
8
+ import { makeCtx } from '../mcp/ctx.js';
9
+ const cfg = loadConfig();
10
+ mkdirSync(cfg.dataDir, { recursive: true });
11
+ const logFile = join(cfg.dataDir, 'daemon.log');
12
+ function log(msg) {
13
+ const line = `[${new Date().toISOString()}] ${msg}`;
14
+ process.stderr.write(line + '\n');
15
+ try {
16
+ appendFileSync(logFile, line + '\n');
17
+ }
18
+ catch {
19
+ /* 日志失败不影响服务 */
20
+ }
21
+ }
22
+ process.on('uncaughtException', (e) => log(`uncaught: ${e.message}\n${e.stack}`));
23
+ process.on('unhandledRejection', (e) => log(`unhandled: ${e}`));
24
+ // ---- 计划/提醒调度 ----
25
+ const scheduler = new Scheduler(undefined, cfg);
26
+ scheduler.start();
27
+ log(`daemon started (data: ${cfg.dataDir}, pid: ${process.pid})`);
28
+ /**
29
+ * ---- 面板 API 也由 daemon 托管 ----
30
+ * 面板要能当桌面应用/常驻组件用,就必须和 daemon 同生命周期:
31
+ * 只托管 daemon 的话,用户重启后还得手工 `npm run api`,违反"不要求用户维护系统"。
32
+ * 端口被占用不致命:独立跑 `npm run api` 时 daemon 仍然正常工作。
33
+ */
34
+ let api = null;
35
+ if (process.env.AI_GROWTH_API_DISABLED !== '1') {
36
+ api = createApiServer(scheduler.db, cfg);
37
+ const lan = isDeviceLanEnabled(cfg, makeCtx(scheduler.db, cfg, 'api'));
38
+ // 局域网关 → 只绑回环(默认);局域网开 → 绑 0.0.0.0,此时 /api/device/*
39
+ // 对任何来源都强制校验令牌。见 src/device/token.ts。
40
+ const bindHost = lan ? '0.0.0.0' : '127.0.0.1';
41
+ api.on('error', (e) => {
42
+ if (e.code === 'EADDRINUSE') {
43
+ log(`api port ${cfg.apiPort} already in use — 面板可能由独立进程提供,daemon 继续运行`);
44
+ api = null;
45
+ }
46
+ else {
47
+ log(`api error: ${e.message}`);
48
+ }
49
+ });
50
+ api.listen(cfg.apiPort, bindHost, () => {
51
+ log(`panel api ready: http://127.0.0.1:${cfg.apiPort}${lan ? ` (device access: 0.0.0.0:${cfg.apiPort})` : ''}`);
52
+ if (lan) {
53
+ const t = getOrCreateDeviceToken(makeCtx(scheduler.db, cfg, 'api'));
54
+ log(`device lan enabled · token ${t.slice(0, 8)}…(完整令牌:ai-growth device)`);
55
+ }
56
+ });
57
+ }
58
+ // launchd KeepAlive 下优雅退出
59
+ process.on('SIGTERM', () => {
60
+ log('SIGTERM, stopping');
61
+ scheduler.stop();
62
+ api?.close();
63
+ process.exit(0);
64
+ });
package/dist/db/cli.js ADDED
@@ -0,0 +1,19 @@
1
+ import { loadConfig } from '../shared/config.js';
2
+ import { openDb, dbPath } from './client.js';
3
+ import { mkdirSync } from 'node:fs';
4
+ const cmd = process.argv[2] ?? 'migrate';
5
+ if (cmd === 'migrate') {
6
+ const cfg = loadConfig();
7
+ mkdirSync(cfg.dataDir, { recursive: true });
8
+ const db = openDb(cfg.dataDir);
9
+ const rows = db.prepare('SELECT version, name, applied_at FROM _migrations ORDER BY version').all();
10
+ console.log(`DB ready at ${dbPath(cfg.dataDir)}`);
11
+ for (const r of rows) {
12
+ console.log(` applied V${r.version}__${r.name} at ${r.applied_at}`);
13
+ }
14
+ db.close();
15
+ }
16
+ else {
17
+ console.error(`Unknown command: ${cmd}`);
18
+ process.exit(1);
19
+ }
@@ -0,0 +1,36 @@
1
+ import Database from 'better-sqlite3';
2
+ import { mkdirSync, existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { runMigrations } from './migrate.js';
5
+ let _db = null;
6
+ export function openDb(dataDir, inMemory = false) {
7
+ if (_db)
8
+ return _db;
9
+ if (!inMemory)
10
+ mkdirSync(dataDir, { recursive: true });
11
+ const db = new Database(inMemory ? ':memory:' : join(dataDir, 'growth.db'));
12
+ db.pragma('journal_mode = WAL');
13
+ db.pragma('foreign_keys = ON');
14
+ runMigrations(db);
15
+ _db = db;
16
+ return db;
17
+ }
18
+ /** 测试用:独立于全局单例打开内存库 */
19
+ export function openTestDb() {
20
+ const db = new Database(':memory:');
21
+ db.pragma('foreign_keys = ON');
22
+ runMigrations(db);
23
+ return db;
24
+ }
25
+ export function closeDb() {
26
+ if (_db) {
27
+ _db.close();
28
+ _db = null;
29
+ }
30
+ }
31
+ export function dbPath(dataDir) {
32
+ return join(dataDir, 'growth.db');
33
+ }
34
+ export function dataDirExists(p) {
35
+ return existsSync(join(p, 'growth.db'));
36
+ }
@@ -0,0 +1,34 @@
1
+ import { readFileSync, readdirSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ // SQL 文件与编译产物同目录:src/db/migrations ↔ dist/db/migrations
5
+ const migrationsDir = join(dirname(fileURLToPath(import.meta.url)), 'migrations');
6
+ function loadMigrations() {
7
+ // 构建后:SQL 文件已被复制到 dist/db/migrations/
8
+ const files = readdirSync(migrationsDir)
9
+ .filter((f) => f.endsWith('.sql'))
10
+ .sort();
11
+ return files.map((f) => {
12
+ const m = f.match(/^V(\d+)__(.+)\.sql$/);
13
+ if (!m)
14
+ throw new Error(`Bad migration filename: ${f}`);
15
+ return { version: Number(m[1]), name: m[2].replace(/\.sql$/, ''), sql: readFileSync(join(migrationsDir, f), 'utf8') };
16
+ });
17
+ }
18
+ export function runMigrations(db) {
19
+ db.exec(`CREATE TABLE IF NOT EXISTS _migrations (
20
+ version INTEGER PRIMARY KEY,
21
+ name TEXT NOT NULL,
22
+ applied_at TEXT NOT NULL
23
+ )`);
24
+ const applied = new Set(db.prepare('SELECT version FROM _migrations').all().map((r) => r.version));
25
+ for (const m of loadMigrations()) {
26
+ if (applied.has(m.version))
27
+ continue;
28
+ const tx = db.transaction(() => {
29
+ db.exec(m.sql);
30
+ db.prepare('INSERT INTO _migrations (version, name, applied_at) VALUES (?, ?, ?)').run(m.version, m.name, new Date().toISOString());
31
+ });
32
+ tx();
33
+ }
34
+ }
@@ -0,0 +1,249 @@
1
+ -- V1__init.sql — AI Growth 初始 schema
2
+ -- 约定:时间一律存 ISO timestamp (UTC);"哪一天" 由 Core 按 settings/profile 中 timezone 计算。
3
+
4
+ CREATE TABLE IF NOT EXISTS system_state (
5
+ key TEXT PRIMARY KEY,
6
+ value TEXT NOT NULL,
7
+ updated_at TEXT NOT NULL
8
+ );
9
+
10
+ CREATE TABLE IF NOT EXISTS settings (
11
+ key TEXT PRIMARY KEY,
12
+ value TEXT NOT NULL,
13
+ updated_at TEXT NOT NULL
14
+ );
15
+
16
+ CREATE TABLE IF NOT EXISTS profiles (
17
+ id TEXT PRIMARY KEY,
18
+ display_name TEXT,
19
+ timezone TEXT NOT NULL DEFAULT 'Asia/Shanghai',
20
+ onboarding_status TEXT NOT NULL DEFAULT 'NOT_STARTED', -- NOT_STARTED | IN_PROGRESS | COMPLETED
21
+ time_preferences TEXT NOT NULL DEFAULT '{}', -- JSON
22
+ task_capacity TEXT NOT NULL DEFAULT '{}', -- JSON {defaultDailyActions, maxTotal,...}
23
+ learning_preferences TEXT NOT NULL DEFAULT '{}', -- JSON
24
+ reminder_preferences TEXT NOT NULL DEFAULT '{}', -- JSON
25
+ constraints TEXT NOT NULL DEFAULT '{}', -- JSON
26
+ notes TEXT,
27
+ created_at TEXT NOT NULL,
28
+ updated_at TEXT NOT NULL,
29
+ deleted_at TEXT
30
+ );
31
+
32
+ CREATE TABLE IF NOT EXISTS directions (
33
+ id TEXT PRIMARY KEY,
34
+ title TEXT NOT NULL,
35
+ description TEXT,
36
+ domain TEXT NOT NULL DEFAULT 'OTHER',
37
+ origin TEXT NOT NULL DEFAULT 'USER_CONFIRMED', -- USER_CONFIRMED | INFERRED
38
+ confirmed INTEGER NOT NULL DEFAULT 0,
39
+ created_at TEXT NOT NULL,
40
+ updated_at TEXT NOT NULL,
41
+ deleted_at TEXT
42
+ );
43
+
44
+ CREATE TABLE IF NOT EXISTS goals (
45
+ id TEXT PRIMARY KEY,
46
+ title TEXT NOT NULL,
47
+ why TEXT NOT NULL DEFAULT '',
48
+ desired_outcome TEXT NOT NULL DEFAULT '',
49
+ success_criteria TEXT NOT NULL DEFAULT '[]', -- JSON string[]
50
+ motivation TEXT,
51
+ start_date TEXT,
52
+ target_date TEXT,
53
+ status TEXT NOT NULL DEFAULT 'DRAFT'
54
+ CHECK (status IN ('DRAFT','CONFIRMED','ACTIVE','PAUSED','COMPLETED','ABANDONED')),
55
+ daily_time_budget_minutes INTEGER,
56
+ acceptance_criteria TEXT NOT NULL DEFAULT '[]', -- JSON
57
+ created_at TEXT NOT NULL,
58
+ updated_at TEXT NOT NULL,
59
+ deleted_at TEXT
60
+ );
61
+
62
+ CREATE TABLE IF NOT EXISTS stages (
63
+ id TEXT PRIMARY KEY,
64
+ goal_id TEXT NOT NULL REFERENCES goals(id),
65
+ title TEXT NOT NULL,
66
+ objective TEXT,
67
+ order_index INTEGER NOT NULL,
68
+ planned_start_date TEXT,
69
+ planned_end_date TEXT,
70
+ status TEXT NOT NULL DEFAULT 'PLANNED'
71
+ CHECK (status IN ('PLANNED','ACTIVE','COMPLETED','DELAYED','SKIPPED')),
72
+ exit_criteria TEXT NOT NULL DEFAULT '[]', -- JSON
73
+ created_at TEXT NOT NULL,
74
+ updated_at TEXT NOT NULL,
75
+ deleted_at TEXT
76
+ );
77
+ CREATE INDEX IF NOT EXISTS idx_stages_goal ON stages(goal_id);
78
+
79
+ CREATE TABLE IF NOT EXISTS daily_plans (
80
+ id TEXT PRIMARY KEY,
81
+ date_key TEXT NOT NULL, -- YYYY-MM-DD (local tz)
82
+ rationale TEXT,
83
+ status TEXT NOT NULL DEFAULT 'DRAFT' CHECK (status IN ('DRAFT','CONFIRMED')),
84
+ created_at TEXT NOT NULL,
85
+ confirmed_at TEXT,
86
+ UNIQUE(date_key)
87
+ );
88
+
89
+ CREATE TABLE IF NOT EXISTS actions (
90
+ id TEXT PRIMARY KEY,
91
+ kind TEXT NOT NULL CHECK (kind IN ('MAIN_QUEST','DAILY')),
92
+ domain TEXT NOT NULL DEFAULT 'OTHER',
93
+ title TEXT NOT NULL,
94
+ description TEXT,
95
+ why_today TEXT, -- Main Quest 必填:为什么今天做
96
+ estimated_minutes INTEGER,
97
+ completion_criteria TEXT, -- 完成标准
98
+ evidence_requirements TEXT, -- Evidence 要求
99
+ verification_strategy TEXT, -- 验收方式
100
+ basis TEXT, -- 依据说明(必须可追溯到用户认可的 Goal/意图/约束)
101
+ basis_goal_id TEXT REFERENCES goals(id),
102
+ basis_direction_id TEXT REFERENCES directions(id),
103
+ basis_stage_id TEXT REFERENCES stages(id),
104
+ plan_id TEXT REFERENCES daily_plans(id),
105
+ date_key TEXT NOT NULL, -- 属于哪个自然日
106
+ status TEXT NOT NULL DEFAULT 'PLANNED'
107
+ CHECK (status IN ('DRAFT','PLANNED','IN_PROGRESS','COMPLETED','SKIPPED','DELAYED','PENDING_REPLAN','EXPIRED','CANCELLED')),
108
+ skipped_reason TEXT,
109
+ completed_at TEXT,
110
+ created_at TEXT NOT NULL,
111
+ updated_at TEXT NOT NULL,
112
+ deleted_at TEXT
113
+ );
114
+ CREATE INDEX IF NOT EXISTS idx_actions_date ON actions(date_key);
115
+ CREATE INDEX IF NOT EXISTS idx_actions_plan ON actions(plan_id);
116
+ CREATE INDEX IF NOT EXISTS idx_actions_status ON actions(status);
117
+
118
+ CREATE TABLE IF NOT EXISTS task_feedback (
119
+ id TEXT PRIMARY KEY,
120
+ action_id TEXT NOT NULL REFERENCES actions(id),
121
+ reason TEXT NOT NULL CHECK (reason IN
122
+ ('NOT_INTERESTED','BAD_TIMING','TOO_HARD','TOO_EASY','ALREADY_DONE','NOT_RELEVANT','NO_TIME','OTHER')),
123
+ comment TEXT,
124
+ created_at TEXT NOT NULL
125
+ );
126
+ CREATE INDEX IF NOT EXISTS idx_feedback_action ON task_feedback(action_id);
127
+
128
+ CREATE TABLE IF NOT EXISTS evidence (
129
+ id TEXT PRIMARY KEY,
130
+ action_id TEXT REFERENCES actions(id),
131
+ type TEXT NOT NULL CHECK (type IN
132
+ ('TEXT','FILE','GIT','METRIC','QUIZ','ARTIFACT','REFLECTION','MANUAL')),
133
+ strength TEXT NOT NULL DEFAULT 'USER_CONFIRMED' CHECK (strength IN
134
+ ('AUTO_VERIFIED','USER_CONFIRMED','AGENT_OBSERVED','UNVERIFIED')),
135
+ content TEXT NOT NULL,
136
+ metadata TEXT NOT NULL DEFAULT '{}', -- JSON
137
+ created_at TEXT NOT NULL
138
+ );
139
+ CREATE INDEX IF NOT EXISTS idx_evidence_action ON evidence(action_id);
140
+
141
+ CREATE TABLE IF NOT EXISTS assessments (
142
+ id TEXT PRIMARY KEY,
143
+ action_id TEXT REFERENCES actions(id),
144
+ goal_id TEXT REFERENCES goals(id),
145
+ topic TEXT NOT NULL,
146
+ -- 新建评估默认 EXPOSURE(接触过),由 submit_assessment 升级
147
+ level TEXT NOT NULL DEFAULT 'EXPOSURE' CHECK (level IN
148
+ ('EXPOSURE','RECALL','EXPLAIN','APPLY','REAL_WORLD_EVIDENCE')),
149
+ method TEXT, -- explain | quiz | coding-challenge | practical | mini-project | real-project
150
+ passed INTEGER NOT NULL DEFAULT 0,
151
+ result TEXT NOT NULL DEFAULT '{}', -- JSON
152
+ created_at TEXT NOT NULL,
153
+ updated_at TEXT NOT NULL
154
+ );
155
+
156
+ CREATE TABLE IF NOT EXISTS growth_events (
157
+ id TEXT PRIMARY KEY,
158
+ date_key TEXT NOT NULL,
159
+ title TEXT NOT NULL,
160
+ description TEXT,
161
+ domain TEXT NOT NULL DEFAULT 'OTHER',
162
+ evidence_ids TEXT NOT NULL DEFAULT '[]', -- JSON
163
+ goal_id TEXT,
164
+ stage_id TEXT,
165
+ tags TEXT NOT NULL DEFAULT '[]', -- JSON
166
+ created_at TEXT NOT NULL
167
+ );
168
+ CREATE INDEX IF NOT EXISTS idx_growth_events_date ON growth_events(date_key);
169
+
170
+ CREATE TABLE IF NOT EXISTS life_contexts (
171
+ id TEXT PRIMARY KEY,
172
+ mode TEXT NOT NULL CHECK (mode IN
173
+ ('NORMAL','BUSY','RECOVERY','TRAVEL','FOCUS','CUSTOM')),
174
+ available_minutes_per_day INTEGER,
175
+ start_date TEXT NOT NULL, -- date key
176
+ expected_end_date TEXT, -- date key
177
+ note TEXT,
178
+ confirmed INTEGER NOT NULL DEFAULT 0,
179
+ active INTEGER NOT NULL DEFAULT 1,
180
+ created_at TEXT NOT NULL,
181
+ updated_at TEXT NOT NULL
182
+ );
183
+
184
+ CREATE TABLE IF NOT EXISTS memories (
185
+ id TEXT PRIMARY KEY,
186
+ type TEXT NOT NULL CHECK (type IN ('FACT','PATTERN','DECISION','CHANGE')),
187
+ content TEXT NOT NULL,
188
+ confidence REAL NOT NULL DEFAULT 0.5,
189
+ evidence_refs TEXT NOT NULL DEFAULT '[]', -- JSON evidence ids
190
+ confirmed_by_user INTEGER NOT NULL DEFAULT 0,
191
+ created_at TEXT NOT NULL,
192
+ updated_at TEXT NOT NULL,
193
+ deleted_at TEXT
194
+ );
195
+
196
+ CREATE TABLE IF NOT EXISTS reminders (
197
+ id TEXT PRIMARY KEY,
198
+ action_id TEXT NOT NULL REFERENCES actions(id),
199
+ scheduled_at TEXT NOT NULL, -- ISO timestamp
200
+ status TEXT NOT NULL DEFAULT 'SCHEDULED'
201
+ CHECK (status IN ('SCHEDULED','SENT','CANCELLED')),
202
+ channel TEXT NOT NULL DEFAULT 'LOCAL_NOTIFICATION'
203
+ CHECK (channel IN ('LOCAL_NOTIFICATION','WEB','OTHER')),
204
+ fired_at TEXT,
205
+ cancelled_at TEXT,
206
+ created_at TEXT NOT NULL,
207
+ updated_at TEXT NOT NULL
208
+ );
209
+ CREATE INDEX IF NOT EXISTS idx_reminders_action ON reminders(action_id);
210
+ CREATE INDEX IF NOT EXISTS idx_reminders_status ON reminders(status);
211
+
212
+ CREATE TABLE IF NOT EXISTS passport_snapshots (
213
+ id TEXT PRIMARY KEY,
214
+ passport TEXT NOT NULL, -- JSON
215
+ generated_at TEXT NOT NULL,
216
+ dirty INTEGER NOT NULL DEFAULT 0
217
+ );
218
+
219
+ CREATE TABLE IF NOT EXISTS sync_jobs (
220
+ id TEXT PRIMARY KEY,
221
+ kind TEXT NOT NULL, -- PASSPORT_SYNC
222
+ payload TEXT NOT NULL DEFAULT '{}',-- JSON
223
+ status TEXT NOT NULL DEFAULT 'PENDING' CHECK (status IN ('PENDING','RUNNING','DONE','FAILED')),
224
+ attempts INTEGER NOT NULL DEFAULT 0,
225
+ last_error TEXT,
226
+ created_at TEXT NOT NULL,
227
+ updated_at TEXT NOT NULL
228
+ );
229
+
230
+ CREATE TABLE IF NOT EXISTS human_versions (
231
+ id TEXT PRIMARY KEY,
232
+ version INTEGER NOT NULL,
233
+ title TEXT NOT NULL,
234
+ description TEXT NOT NULL,
235
+ evidence_ids TEXT NOT NULL DEFAULT '[]', -- JSON
236
+ created_at TEXT NOT NULL
237
+ );
238
+
239
+ CREATE TABLE IF NOT EXISTS audit_log (
240
+ id TEXT PRIMARY KEY,
241
+ actor TEXT NOT NULL, -- agent | user | daemon | system
242
+ entity_type TEXT NOT NULL,
243
+ entity_id TEXT NOT NULL,
244
+ before TEXT, -- JSON
245
+ after TEXT, -- JSON
246
+ reason TEXT,
247
+ created_at TEXT NOT NULL
248
+ );
249
+ CREATE INDEX IF NOT EXISTS idx_audit_entity ON audit_log(entity_type, entity_id);