forge-openclaw-plugin 0.2.15 → 0.2.18
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 +6 -3
- package/dist/assets/{board-C_m78kvK.js → board-2KevHCI0.js} +2 -2
- package/dist/assets/{board-C_m78kvK.js.map → board-2KevHCI0.js.map} +1 -1
- package/dist/assets/index-CDYW4WDH.js +36 -0
- package/dist/assets/index-CDYW4WDH.js.map +1 -0
- package/dist/assets/index-yroQr6YZ.css +1 -0
- package/dist/assets/{motion-CpZvZumD.js → motion-q19HPmWs.js} +2 -2
- package/dist/assets/{motion-CpZvZumD.js.map → motion-q19HPmWs.js.map} +1 -1
- package/dist/assets/{table-DtyXTw03.js → table-BDMHBY4a.js} +2 -2
- package/dist/assets/{table-DtyXTw03.js.map → table-BDMHBY4a.js.map} +1 -1
- package/dist/assets/{ui-BXbpiKyS.js → ui-CQ_AsFs8.js} +2 -2
- package/dist/assets/{ui-BXbpiKyS.js.map → ui-CQ_AsFs8.js.map} +1 -1
- package/dist/assets/{vendor-QBH6qVEe.js → vendor-5HifrnRK.js} +90 -75
- package/dist/assets/{vendor-QBH6qVEe.js.map → vendor-5HifrnRK.js.map} +1 -1
- package/dist/assets/{viz-w-IMeueL.js → viz-CQzkRnTu.js} +2 -2
- package/dist/assets/{viz-w-IMeueL.js.map → viz-CQzkRnTu.js.map} +1 -1
- package/dist/index.html +8 -8
- package/dist/openclaw/local-runtime.js +142 -9
- package/dist/openclaw/plugin-entry-shared.js +7 -1
- package/dist/openclaw/tools.js +15 -0
- package/dist/server/app.js +129 -11
- package/dist/server/openapi.js +181 -4
- package/dist/server/repositories/habits.js +358 -0
- package/dist/server/repositories/rewards.js +62 -0
- package/dist/server/services/context.js +16 -6
- package/dist/server/services/dashboard.js +6 -3
- package/dist/server/services/entity-crud.js +23 -1
- package/dist/server/services/gamification.js +66 -18
- package/dist/server/services/insights.js +2 -1
- package/dist/server/services/reviews.js +2 -1
- package/dist/server/types.js +140 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server/migrations/003_habits.sql +30 -0
- package/server/migrations/004_habit_links.sql +8 -0
- package/server/migrations/005_habit_psyche_links.sql +24 -0
- package/skills/forge-openclaw/SKILL.md +16 -2
- package/skills/forge-openclaw/cron_jobs.md +395 -0
- package/dist/assets/index-BWtLtXwb.js +0 -36
- package/dist/assets/index-BWtLtXwb.js.map +0 -1
- package/dist/assets/index-Dp5GXY_z.css +0 -1
|
@@ -43,6 +43,22 @@ const DEFAULT_RULES = [
|
|
|
43
43
|
description: "Reward a concrete decision to apply a useful insight.",
|
|
44
44
|
config: { fixedXp: 15 }
|
|
45
45
|
},
|
|
46
|
+
{
|
|
47
|
+
id: "reward_rule_habit_aligned",
|
|
48
|
+
family: "consistency",
|
|
49
|
+
code: "habit_aligned",
|
|
50
|
+
title: "Habit alignment",
|
|
51
|
+
description: "Award XP when a habit outcome matches the intended direction.",
|
|
52
|
+
config: { award: "habit.rewardXp" }
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "reward_rule_habit_misaligned",
|
|
56
|
+
family: "recovery",
|
|
57
|
+
code: "habit_misaligned",
|
|
58
|
+
title: "Habit miss",
|
|
59
|
+
description: "Apply a small XP penalty when a habit outcome moves against the intended direction.",
|
|
60
|
+
config: { penalty: "habit.penaltyXp" }
|
|
61
|
+
},
|
|
46
62
|
{
|
|
47
63
|
id: "reward_rule_psyche_reflection_capture",
|
|
48
64
|
family: "alignment",
|
|
@@ -633,6 +649,52 @@ export function recordSessionEvent(input, activity, now = new Date()) {
|
|
|
633
649
|
}, now);
|
|
634
650
|
return { sessionEvent, rewardEvent };
|
|
635
651
|
}
|
|
652
|
+
export function recordHabitCheckInReward(habit, status, dateKey, activity) {
|
|
653
|
+
ensureDefaultRewardRules();
|
|
654
|
+
const aligned = (habit.polarity === "positive" && status === "done") ||
|
|
655
|
+
(habit.polarity === "negative" && status === "missed");
|
|
656
|
+
const rule = getRuleByCode(aligned ? "habit_aligned" : "habit_misaligned");
|
|
657
|
+
const deltaXp = aligned ? habit.rewardXp : -Math.abs(habit.penaltyXp);
|
|
658
|
+
const actionLabel = habit.polarity === "positive"
|
|
659
|
+
? status === "done"
|
|
660
|
+
? "completed"
|
|
661
|
+
: "missed"
|
|
662
|
+
: status === "done"
|
|
663
|
+
? "performed"
|
|
664
|
+
: "resisted";
|
|
665
|
+
const eventLog = recordEventLog({
|
|
666
|
+
eventKind: aligned ? "reward.habit_aligned" : "reward.habit_misaligned",
|
|
667
|
+
entityType: "habit",
|
|
668
|
+
entityId: habit.id,
|
|
669
|
+
actor: activity.actor ?? null,
|
|
670
|
+
source: activity.source,
|
|
671
|
+
metadata: {
|
|
672
|
+
habitId: habit.id,
|
|
673
|
+
status,
|
|
674
|
+
polarity: habit.polarity,
|
|
675
|
+
dateKey,
|
|
676
|
+
deltaXp
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
return insertLedgerEvent({
|
|
680
|
+
ruleId: rule?.id ?? null,
|
|
681
|
+
eventLogId: eventLog.id,
|
|
682
|
+
entityType: "habit",
|
|
683
|
+
entityId: habit.id,
|
|
684
|
+
actor: activity.actor ?? null,
|
|
685
|
+
source: activity.source,
|
|
686
|
+
deltaXp,
|
|
687
|
+
reasonTitle: aligned ? `${habit.title} aligned` : `${habit.title} slipped`,
|
|
688
|
+
reasonSummary: `Habit ${actionLabel} on ${dateKey}.`,
|
|
689
|
+
reversibleGroup: `habit:${habit.id}:${dateKey}`,
|
|
690
|
+
metadata: {
|
|
691
|
+
habitId: habit.id,
|
|
692
|
+
status,
|
|
693
|
+
polarity: habit.polarity,
|
|
694
|
+
dateKey
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
}
|
|
636
698
|
export function listSessionEvents(limit = 50) {
|
|
637
699
|
const rows = getDatabase()
|
|
638
700
|
.prepare(`SELECT id, session_id, event_type, actor, source, metrics_json, created_at
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { listActivityEvents } from "../repositories/activity-events.js";
|
|
2
2
|
import { listGoals } from "../repositories/goals.js";
|
|
3
|
+
import { listHabits } from "../repositories/habits.js";
|
|
4
|
+
import { listRewardLedger } from "../repositories/rewards.js";
|
|
3
5
|
import { listTags, listTagsByIds } from "../repositories/tags.js";
|
|
4
6
|
import { listTasks } from "../repositories/tasks.js";
|
|
5
7
|
import { getDashboard } from "./dashboard.js";
|
|
@@ -109,6 +111,7 @@ export function getOverviewContext(now = new Date()) {
|
|
|
109
111
|
const dashboard = getDashboard();
|
|
110
112
|
const focusTasks = dashboard.tasks.filter((task) => task.status === "focus" || task.status === "in_progress").length;
|
|
111
113
|
const overdueTasks = dashboard.tasks.filter((task) => task.status !== "done" && task.dueDate !== null && task.dueDate < now.toISOString().slice(0, 10)).length;
|
|
114
|
+
const dueHabits = dashboard.habits.filter((habit) => habit.dueToday).slice(0, 6);
|
|
112
115
|
return overviewContextSchema.parse({
|
|
113
116
|
generatedAt: now.toISOString(),
|
|
114
117
|
strategicHeader: {
|
|
@@ -124,8 +127,9 @@ export function getOverviewContext(now = new Date()) {
|
|
|
124
127
|
projects: dashboard.projects.slice(0, 5),
|
|
125
128
|
activeGoals: dashboard.goals.filter((goal) => goal.status === "active").slice(0, 6),
|
|
126
129
|
topTasks: sortStrategicTasks(dashboard.tasks.filter((task) => task.status !== "done")).slice(0, 6),
|
|
130
|
+
dueHabits,
|
|
127
131
|
recentEvidence: listActivityEvents({ limit: 12 }),
|
|
128
|
-
achievements: buildAchievementSignals(listGoals(), listTasks(), now),
|
|
132
|
+
achievements: buildAchievementSignals(listGoals(), listTasks(), listHabits(), now),
|
|
129
133
|
domainBalance: buildDomainBalance(listGoals(), listTasks()),
|
|
130
134
|
neglectedGoals: buildNeglectedGoals(listGoals(), listTasks(), now)
|
|
131
135
|
});
|
|
@@ -133,10 +137,12 @@ export function getOverviewContext(now = new Date()) {
|
|
|
133
137
|
export function getTodayContext(now = new Date()) {
|
|
134
138
|
const goals = listGoals();
|
|
135
139
|
const tasks = listTasks();
|
|
136
|
-
const
|
|
140
|
+
const habits = listHabits();
|
|
141
|
+
const gamification = buildGamificationProfile(goals, tasks, habits, now);
|
|
137
142
|
const inProgressTasks = sortStrategicTasks(tasks.filter((task) => task.status === "in_progress")).slice(0, 4);
|
|
138
143
|
const readyTasks = sortStrategicTasks(tasks.filter((task) => task.status === "focus" || task.status === "backlog")).slice(0, 4);
|
|
139
144
|
const deferredTasks = sortStrategicTasks(tasks.filter((task) => task.status === "blocked")).slice(0, 4);
|
|
145
|
+
const dueHabits = habits.filter((habit) => habit.dueToday).slice(0, 6);
|
|
140
146
|
const completedTasks = [...tasks]
|
|
141
147
|
.filter((task) => task.status === "done" && task.completedAt !== null)
|
|
142
148
|
.sort((left, right) => Date.parse(right.completedAt ?? "") - Date.parse(left.completedAt ?? ""))
|
|
@@ -185,13 +191,17 @@ export function getTodayContext(now = new Date()) {
|
|
|
185
191
|
completed: false
|
|
186
192
|
}
|
|
187
193
|
],
|
|
188
|
-
|
|
194
|
+
dueHabits,
|
|
195
|
+
milestoneRewards: buildMilestoneRewards(goals, tasks, habits, now),
|
|
196
|
+
recentHabitRewards: listRewardLedger({ entityType: "habit", limit: 8 }),
|
|
189
197
|
momentum: {
|
|
190
198
|
streakDays: gamification.streakDays,
|
|
191
199
|
momentumScore: gamification.momentumScore,
|
|
192
|
-
recoveryHint:
|
|
193
|
-
?
|
|
194
|
-
:
|
|
200
|
+
recoveryHint: dueHabits.length > 0
|
|
201
|
+
? `${dueHabits.length} habit${dueHabits.length === 1 ? "" : "s"} still need a check-in today. Closing one will keep momentum honest.`
|
|
202
|
+
: overdueCount > 0
|
|
203
|
+
? `Clear ${overdueCount} overdue task${overdueCount === 1 ? "" : "s"} to keep momentum from decaying.`
|
|
204
|
+
: "No overdue drag right now. Preserve the rhythm with one decisive completion."
|
|
195
205
|
}
|
|
196
206
|
});
|
|
197
207
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { listGoals } from "../repositories/goals.js";
|
|
2
2
|
import { listActivityEvents } from "../repositories/activity-events.js";
|
|
3
|
+
import { listHabits } from "../repositories/habits.js";
|
|
3
4
|
import { buildNotesSummaryByEntity } from "../repositories/notes.js";
|
|
4
5
|
import { listTagsByIds, listTags } from "../repositories/tags.js";
|
|
5
6
|
import { listTasks } from "../repositories/tasks.js";
|
|
@@ -116,6 +117,7 @@ function buildGoalSummary(tasks, goalId) {
|
|
|
116
117
|
export function getDashboard() {
|
|
117
118
|
const goals = listGoals();
|
|
118
119
|
const tasks = listTasks();
|
|
120
|
+
const habits = listHabits();
|
|
119
121
|
const tags = listTags();
|
|
120
122
|
const now = new Date();
|
|
121
123
|
const weekStart = startOfWeek(now).toISOString();
|
|
@@ -150,9 +152,9 @@ export function getDashboard() {
|
|
|
150
152
|
const suggestedTags = tags.filter((tag) => ["value", "execution"].includes(tag.kind)).slice(0, 6);
|
|
151
153
|
const owners = [...new Set(tasks.map((task) => task.owner).filter(Boolean))].sort((left, right) => left.localeCompare(right));
|
|
152
154
|
const executionBuckets = buildExecutionBuckets(tasks, todayIso, weekEndIso);
|
|
153
|
-
const gamification = buildGamificationProfile(goals, tasks, now);
|
|
154
|
-
const achievements = buildAchievementSignals(goals, tasks, now);
|
|
155
|
-
const milestoneRewards = buildMilestoneRewards(goals, tasks, now);
|
|
155
|
+
const gamification = buildGamificationProfile(goals, tasks, habits, now);
|
|
156
|
+
const achievements = buildAchievementSignals(goals, tasks, habits, now);
|
|
157
|
+
const milestoneRewards = buildMilestoneRewards(goals, tasks, habits, now);
|
|
156
158
|
const recentActivity = listActivityEvents({ limit: 12 });
|
|
157
159
|
const notesSummaryByEntity = buildNotesSummaryByEntity();
|
|
158
160
|
return dashboardPayloadSchema.parse({
|
|
@@ -160,6 +162,7 @@ export function getDashboard() {
|
|
|
160
162
|
goals: goalCards,
|
|
161
163
|
projects,
|
|
162
164
|
tasks,
|
|
165
|
+
habits,
|
|
163
166
|
tags,
|
|
164
167
|
suggestedTags,
|
|
165
168
|
owners,
|
|
@@ -4,11 +4,12 @@ import { createNote, deleteNote, getNoteById, listNotes, unlinkNotesForEntity, u
|
|
|
4
4
|
import { createBehaviorPatternSchema, createBehaviorSchema, createBeliefEntrySchema, createEmotionDefinitionSchema, createEventTypeSchema, createModeGuideSessionSchema, createModeProfileSchema, createPsycheValueSchema, createTriggerReportSchema, updateBehaviorPatternSchema, updateBehaviorSchema, updateBeliefEntrySchema, updateEmotionDefinitionSchema, updateEventTypeSchema, updateModeGuideSessionSchema, updateModeProfileSchema, updatePsycheValueSchema, updateTriggerReportSchema } from "../psyche-types.js";
|
|
5
5
|
import { buildSettingsBinPayload, cascadeSoftDeleteAnchoredCollaboration, clearDeletedEntityRecord, getDeletedEntityRecord, listDeletedEntities, restoreAnchoredCollaboration, restoreDeletedEntityRecord, upsertDeletedEntityRecord } from "../repositories/deleted-entities.js";
|
|
6
6
|
import { createGoal, deleteGoal, getGoalById, listGoals, updateGoal } from "../repositories/goals.js";
|
|
7
|
+
import { createHabit, deleteHabit, getHabitById, listHabits, updateHabit } from "../repositories/habits.js";
|
|
7
8
|
import { createBehavior, createBehaviorPattern, createBeliefEntry, createEmotionDefinition, createEventType, createModeGuideSession, createModeProfile, createPsycheValue, createTriggerReport, deleteBehavior, deleteBehaviorPattern, deleteBeliefEntry, deleteEmotionDefinition, deleteEventType, deleteModeGuideSession, deleteModeProfile, deletePsycheValue, deleteTriggerReport, getBehaviorById, getBehaviorPatternById, getBeliefEntryById, getEmotionDefinitionById, getEventTypeById, getModeGuideSessionById, getModeProfileById, getPsycheValueById, getTriggerReportById, listBehaviors, listBehaviorPatterns, listBeliefEntries, listEmotionDefinitions, listEventTypes, listModeGuideSessions, listModeProfiles, listPsycheValues, listTriggerReports, updateBehavior, updateBehaviorPattern, updateBeliefEntry, updateEmotionDefinition, updateEventType, updateModeGuideSession, updateModeProfile, updatePsycheValue, updateTriggerReport } from "../repositories/psyche.js";
|
|
8
9
|
import { createProject, deleteProject, getProjectById, listProjects, updateProject } from "../repositories/projects.js";
|
|
9
10
|
import { createTag, deleteTag, getTagById, listTags, updateTag } from "../repositories/tags.js";
|
|
10
11
|
import { createTask, deleteTask, getTaskById, listTasks, updateTask } from "../repositories/tasks.js";
|
|
11
|
-
import { createGoalSchema, createInsightSchema, createNoteSchema, createProjectSchema, createTagSchema, createTaskSchema, updateGoalSchema, updateInsightSchema, updateNoteSchema, updateProjectSchema, updateTagSchema, updateTaskSchema } from "../types.js";
|
|
12
|
+
import { createGoalSchema, createHabitSchema, createInsightSchema, createNoteSchema, createProjectSchema, createTagSchema, createTaskSchema, updateGoalSchema, updateHabitSchema, updateInsightSchema, updateNoteSchema, updateProjectSchema, updateTagSchema, updateTaskSchema } from "../types.js";
|
|
12
13
|
class AtomicBatchRollback extends Error {
|
|
13
14
|
index;
|
|
14
15
|
code;
|
|
@@ -49,6 +50,15 @@ const CRUD_ENTITY_CAPABILITIES = {
|
|
|
49
50
|
update: (id, patch, context) => updateTask(id, patch, context),
|
|
50
51
|
hardDelete: (id, context) => deleteTask(id, context)
|
|
51
52
|
},
|
|
53
|
+
habit: {
|
|
54
|
+
entityType: "habit",
|
|
55
|
+
routeBase: "/api/v1/habits",
|
|
56
|
+
list: () => listHabits(),
|
|
57
|
+
get: (id) => getHabitById(id),
|
|
58
|
+
create: (data, context) => createHabit(data, context),
|
|
59
|
+
update: (id, patch, context) => updateHabit(id, patch, context),
|
|
60
|
+
hardDelete: (id, context) => deleteHabit(id, context)
|
|
61
|
+
},
|
|
52
62
|
tag: {
|
|
53
63
|
entityType: "tag",
|
|
54
64
|
routeBase: "/api/v1/tags",
|
|
@@ -174,6 +184,7 @@ const CREATE_ENTITY_SCHEMAS = {
|
|
|
174
184
|
goal: createGoalSchema,
|
|
175
185
|
project: createProjectSchema,
|
|
176
186
|
task: createTaskSchema,
|
|
187
|
+
habit: createHabitSchema,
|
|
177
188
|
tag: createTagSchema,
|
|
178
189
|
note: createNoteSchema,
|
|
179
190
|
insight: createInsightSchema,
|
|
@@ -191,6 +202,7 @@ const UPDATE_ENTITY_SCHEMAS = {
|
|
|
191
202
|
goal: updateGoalSchema,
|
|
192
203
|
project: updateProjectSchema,
|
|
193
204
|
task: updateTaskSchema,
|
|
205
|
+
habit: updateHabitSchema,
|
|
194
206
|
tag: updateTagSchema,
|
|
195
207
|
note: updateNoteSchema,
|
|
196
208
|
insight: updateInsightSchema,
|
|
@@ -312,6 +324,16 @@ function matchesLinkedTo(entityType, entity, linkedTo) {
|
|
|
312
324
|
return linkedTo.entityType === "goal" && entity.goalId === linkedTo.id;
|
|
313
325
|
case "task":
|
|
314
326
|
return (linkedTo.entityType === "goal" && entity.goalId === linkedTo.id) || (linkedTo.entityType === "project" && entity.projectId === linkedTo.id);
|
|
327
|
+
case "habit":
|
|
328
|
+
return ((linkedTo.entityType === "goal" && Array.isArray(entity.linkedGoalIds) && entity.linkedGoalIds.includes(linkedTo.id)) ||
|
|
329
|
+
(linkedTo.entityType === "project" && Array.isArray(entity.linkedProjectIds) && entity.linkedProjectIds.includes(linkedTo.id)) ||
|
|
330
|
+
(linkedTo.entityType === "task" && Array.isArray(entity.linkedTaskIds) && entity.linkedTaskIds.includes(linkedTo.id)) ||
|
|
331
|
+
(linkedTo.entityType === "psyche_value" && Array.isArray(entity.linkedValueIds) && entity.linkedValueIds.includes(linkedTo.id)) ||
|
|
332
|
+
(linkedTo.entityType === "behavior_pattern" && Array.isArray(entity.linkedPatternIds) && entity.linkedPatternIds.includes(linkedTo.id)) ||
|
|
333
|
+
(linkedTo.entityType === "behavior" && Array.isArray(entity.linkedBehaviorIds) && entity.linkedBehaviorIds.includes(linkedTo.id)) ||
|
|
334
|
+
(linkedTo.entityType === "belief_entry" && Array.isArray(entity.linkedBeliefIds) && entity.linkedBeliefIds.includes(linkedTo.id)) ||
|
|
335
|
+
(linkedTo.entityType === "mode_profile" && Array.isArray(entity.linkedModeIds) && entity.linkedModeIds.includes(linkedTo.id)) ||
|
|
336
|
+
(linkedTo.entityType === "trigger_report" && Array.isArray(entity.linkedReportIds) && entity.linkedReportIds.includes(linkedTo.id)));
|
|
315
337
|
case "note":
|
|
316
338
|
return (Array.isArray(entity.links) &&
|
|
317
339
|
entity.links.some((link) => typeof link === "object" &&
|
|
@@ -12,9 +12,15 @@ function startOfWeek(date) {
|
|
|
12
12
|
function dayKey(isoDate) {
|
|
13
13
|
return isoDate.slice(0, 10);
|
|
14
14
|
}
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
function isAlignedHabitCheckIn(habit, checkIn) {
|
|
16
|
+
return (habit.polarity === "positive" && checkIn.status === "done") || (habit.polarity === "negative" && checkIn.status === "missed");
|
|
17
|
+
}
|
|
18
|
+
function calculateStreak(tasks, habits, now) {
|
|
19
|
+
const completedDays = new Set([
|
|
20
|
+
...tasks
|
|
21
|
+
.flatMap((task) => (task.status === "done" && task.completedAt !== null ? [dayKey(task.completedAt)] : [])),
|
|
22
|
+
...habits.flatMap((habit) => habit.checkIns.filter((checkIn) => isAlignedHabitCheckIn(habit, checkIn)).map((checkIn) => checkIn.dateKey))
|
|
23
|
+
]);
|
|
18
24
|
if (completedDays.size === 0) {
|
|
19
25
|
return 0;
|
|
20
26
|
}
|
|
@@ -41,17 +47,27 @@ function latestCompletionForTasks(tasks) {
|
|
|
41
47
|
.flatMap((task) => (task.completedAt ? [task.completedAt] : []))
|
|
42
48
|
.sort((left, right) => Date.parse(right) - Date.parse(left))[0] ?? null;
|
|
43
49
|
}
|
|
44
|
-
|
|
50
|
+
function latestAlignedHabitAt(habits) {
|
|
51
|
+
return habits
|
|
52
|
+
.flatMap((habit) => habit.checkIns
|
|
53
|
+
.filter((checkIn) => isAlignedHabitCheckIn(habit, checkIn))
|
|
54
|
+
.map((checkIn) => checkIn.createdAt))
|
|
55
|
+
.sort((left, right) => Date.parse(right) - Date.parse(left))[0] ?? null;
|
|
56
|
+
}
|
|
57
|
+
export function buildGamificationProfile(goals, tasks, habits, now = new Date()) {
|
|
45
58
|
const weekStart = startOfWeek(now).toISOString();
|
|
46
59
|
const doneTasks = tasks.filter((task) => task.status === "done");
|
|
47
60
|
const totalXp = getTotalXp();
|
|
48
61
|
const weeklyXp = getWeeklyXp(weekStart);
|
|
49
62
|
const focusTasks = tasks.filter((task) => task.status === "focus" || task.status === "in_progress").length;
|
|
50
63
|
const overdueTasks = tasks.filter((task) => task.status !== "done" && task.dueDate !== null && task.dueDate < now.toISOString().slice(0, 10)).length;
|
|
64
|
+
const dueHabits = habits.filter((habit) => habit.dueToday).length;
|
|
65
|
+
const alignedHabitCheckIns = habits.flatMap((habit) => habit.checkIns.filter((checkIn) => isAlignedHabitCheckIn(habit, checkIn)));
|
|
66
|
+
const habitMomentum = habits.reduce((sum, habit) => sum + habit.streakCount * 3 + (habit.dueToday ? -4 : 2), 0);
|
|
51
67
|
const alignedDonePoints = doneTasks
|
|
52
68
|
.filter((task) => task.goalId !== null && task.tagIds.length > 0)
|
|
53
69
|
.reduce((sum, task) => sum + task.points, 0);
|
|
54
|
-
const streakDays = calculateStreak(tasks, now);
|
|
70
|
+
const streakDays = calculateStreak(tasks, habits, now);
|
|
55
71
|
const levelState = calculateLevel(totalXp);
|
|
56
72
|
const goalScores = goals
|
|
57
73
|
.map((goal) => ({
|
|
@@ -69,17 +85,20 @@ export function buildGamificationProfile(goals, tasks, now = new Date()) {
|
|
|
69
85
|
weeklyXp,
|
|
70
86
|
streakDays,
|
|
71
87
|
comboMultiplier: Number((1 + Math.min(0.75, streakDays * 0.05)).toFixed(2)),
|
|
72
|
-
momentumScore: Math.max(0, Math.min(100, Math.round(weeklyXp / 6 + alignedDonePoints / 20 + focusTasks * 5 - overdueTasks * 9))),
|
|
88
|
+
momentumScore: Math.max(0, Math.min(100, Math.round(weeklyXp / 6 + alignedDonePoints / 20 + focusTasks * 5 + alignedHabitCheckIns.length * 4 + habitMomentum - overdueTasks * 9 - dueHabits * 3))),
|
|
73
89
|
topGoalId: topGoal?.goalId ?? null,
|
|
74
90
|
topGoalTitle: topGoal?.goalTitle ?? null
|
|
75
91
|
});
|
|
76
92
|
}
|
|
77
|
-
export function buildAchievementSignals(goals, tasks, now = new Date()) {
|
|
78
|
-
const profile = buildGamificationProfile(goals, tasks, now);
|
|
93
|
+
export function buildAchievementSignals(goals, tasks, habits, now = new Date()) {
|
|
94
|
+
const profile = buildGamificationProfile(goals, tasks, habits, now);
|
|
79
95
|
const doneTasks = tasks.filter((task) => task.status === "done");
|
|
80
96
|
const alignedDoneTasks = doneTasks.filter((task) => task.goalId !== null && task.tagIds.length > 0);
|
|
81
97
|
const focusTasks = tasks.filter((task) => task.status === "focus" || task.status === "in_progress");
|
|
82
98
|
const highValueGoals = goals.filter((goal) => doneTasks.some((task) => task.goalId === goal.id));
|
|
99
|
+
const alignedHabitCount = habits.reduce((sum, habit) => sum + habit.checkIns.filter((checkIn) => isAlignedHabitCheckIn(habit, checkIn)).length, 0);
|
|
100
|
+
const topHabitStreak = Math.max(0, ...habits.map((habit) => habit.streakCount));
|
|
101
|
+
const latestHabitWin = latestAlignedHabitAt(habits);
|
|
83
102
|
return [
|
|
84
103
|
{
|
|
85
104
|
id: "streak-operator",
|
|
@@ -125,15 +144,34 @@ export function buildAchievementSignals(goals, tasks, now = new Date()) {
|
|
|
125
144
|
progressLabel: `${Math.min(focusTasks.length, 1)}/1 live directives`,
|
|
126
145
|
unlocked: focusTasks.length > 0,
|
|
127
146
|
unlockedAt: focusTasks.length > 0 ? now.toISOString() : null
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: "habit-keeper",
|
|
150
|
+
title: "Habit Keeper",
|
|
151
|
+
summary: "Turn recurring behavior into visible operating evidence.",
|
|
152
|
+
tier: alignedHabitCount >= 12 ? "gold" : alignedHabitCount >= 6 ? "silver" : "bronze",
|
|
153
|
+
progressLabel: `${Math.min(alignedHabitCount, 12)}/12 aligned habit wins`,
|
|
154
|
+
unlocked: alignedHabitCount >= 12,
|
|
155
|
+
unlockedAt: alignedHabitCount >= 12 ? latestHabitWin : null
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: "ritual-pressure",
|
|
159
|
+
title: "Ritual Pressure",
|
|
160
|
+
summary: "Keep one habit alive long enough that it changes the texture of the week.",
|
|
161
|
+
tier: topHabitStreak >= 10 ? "gold" : "silver",
|
|
162
|
+
progressLabel: `${Math.min(topHabitStreak, 10)}/10 habit streak`,
|
|
163
|
+
unlocked: topHabitStreak >= 10,
|
|
164
|
+
unlockedAt: topHabitStreak >= 10 ? latestHabitWin : null
|
|
128
165
|
}
|
|
129
166
|
].map((achievement) => achievementSignalSchema.parse(achievement));
|
|
130
167
|
}
|
|
131
|
-
export function buildMilestoneRewards(goals, tasks, now = new Date()) {
|
|
132
|
-
const profile = buildGamificationProfile(goals, tasks, now);
|
|
168
|
+
export function buildMilestoneRewards(goals, tasks, habits, now = new Date()) {
|
|
169
|
+
const profile = buildGamificationProfile(goals, tasks, habits, now);
|
|
133
170
|
const doneTasks = tasks.filter((task) => task.status === "done");
|
|
134
171
|
const topGoal = profile.topGoalId ? goals.find((goal) => goal.id === profile.topGoalId) ?? null : null;
|
|
135
172
|
const topGoalXp = topGoal ? doneTasks.filter((task) => task.goalId === topGoal.id).reduce((sum, task) => sum + task.points, 0) : 0;
|
|
136
173
|
const completedToday = doneTasks.filter((task) => task.completedAt?.slice(0, 10) === now.toISOString().slice(0, 10)).length;
|
|
174
|
+
const alignedHabitCount = habits.reduce((sum, habit) => sum + habit.checkIns.filter((checkIn) => isAlignedHabitCheckIn(habit, checkIn)).length, 0);
|
|
137
175
|
return [
|
|
138
176
|
{
|
|
139
177
|
id: "next-level",
|
|
@@ -174,13 +212,23 @@ export function buildMilestoneRewards(goals, tasks, now = new Date()) {
|
|
|
174
212
|
current: topGoal ? topGoalXp : 0,
|
|
175
213
|
target: topGoal ? topGoal.targetPoints : 1,
|
|
176
214
|
completed: topGoal ? topGoalXp >= topGoal.targetPoints : false
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
id: "habit-mass",
|
|
218
|
+
title: "Habit mass threshold",
|
|
219
|
+
summary: "Make recurring behavior part of the same reward engine as tasks and projects.",
|
|
220
|
+
rewardLabel: "Consistency cache +75 xp",
|
|
221
|
+
progressLabel: `${Math.min(alignedHabitCount, 14)}/14 aligned habit check-ins`,
|
|
222
|
+
current: alignedHabitCount,
|
|
223
|
+
target: 14,
|
|
224
|
+
completed: alignedHabitCount >= 14
|
|
177
225
|
}
|
|
178
226
|
].map((reward) => milestoneRewardSchema.parse(reward));
|
|
179
227
|
}
|
|
180
|
-
export function buildXpMomentumPulse(goals, tasks, now = new Date()) {
|
|
181
|
-
const profile = buildGamificationProfile(goals, tasks, now);
|
|
182
|
-
const achievements = buildAchievementSignals(goals, tasks, now);
|
|
183
|
-
const milestoneRewards = buildMilestoneRewards(goals, tasks, now);
|
|
228
|
+
export function buildXpMomentumPulse(goals, tasks, habits, now = new Date()) {
|
|
229
|
+
const profile = buildGamificationProfile(goals, tasks, habits, now);
|
|
230
|
+
const achievements = buildAchievementSignals(goals, tasks, habits, now);
|
|
231
|
+
const milestoneRewards = buildMilestoneRewards(goals, tasks, habits, now);
|
|
184
232
|
const nextMilestone = milestoneRewards.find((reward) => !reward.completed) ?? milestoneRewards[0] ?? null;
|
|
185
233
|
const unlockedAchievements = achievements.filter((achievement) => achievement.unlocked).length;
|
|
186
234
|
const status = profile.momentumScore >= 80 ? "surging" : profile.momentumScore >= 60 ? "steady" : "recovering";
|
|
@@ -206,10 +254,10 @@ export function buildXpMomentumPulse(goals, tasks, now = new Date()) {
|
|
|
206
254
|
nextMilestoneLabel: nextMilestone?.rewardLabel ?? "Keep building visible momentum"
|
|
207
255
|
};
|
|
208
256
|
}
|
|
209
|
-
export function buildGamificationOverview(goals, tasks, now = new Date()) {
|
|
257
|
+
export function buildGamificationOverview(goals, tasks, habits, now = new Date()) {
|
|
210
258
|
return gamificationOverviewSchema.parse({
|
|
211
|
-
profile: buildGamificationProfile(goals, tasks, now),
|
|
212
|
-
achievements: buildAchievementSignals(goals, tasks, now),
|
|
213
|
-
milestoneRewards: buildMilestoneRewards(goals, tasks, now)
|
|
259
|
+
profile: buildGamificationProfile(goals, tasks, habits, now),
|
|
260
|
+
achievements: buildAchievementSignals(goals, tasks, habits, now),
|
|
261
|
+
milestoneRewards: buildMilestoneRewards(goals, tasks, habits, now)
|
|
214
262
|
});
|
|
215
263
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { listActivityEvents } from "../repositories/activity-events.js";
|
|
2
2
|
import { listInsights } from "../repositories/collaboration.js";
|
|
3
3
|
import { listGoals } from "../repositories/goals.js";
|
|
4
|
+
import { listHabits } from "../repositories/habits.js";
|
|
4
5
|
import { listTasks } from "../repositories/tasks.js";
|
|
5
6
|
import { getOverviewContext } from "./context.js";
|
|
6
7
|
import { buildGamificationProfile } from "./gamification.js";
|
|
@@ -33,7 +34,7 @@ function buildHeatmap(tasks, now) {
|
|
|
33
34
|
export function getInsightsPayload(now = new Date()) {
|
|
34
35
|
const goals = listGoals();
|
|
35
36
|
const tasks = listTasks();
|
|
36
|
-
const gamification = buildGamificationProfile(goals, tasks, now);
|
|
37
|
+
const gamification = buildGamificationProfile(goals, tasks, listHabits(), now);
|
|
37
38
|
const overview = getOverviewContext(now);
|
|
38
39
|
const activity = listActivityEvents({ limit: 60 });
|
|
39
40
|
const trends = Array.from({ length: 6 }, (_, offset) => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { listActivityEvents } from "../repositories/activity-events.js";
|
|
2
2
|
import { listGoals } from "../repositories/goals.js";
|
|
3
|
+
import { listHabits } from "../repositories/habits.js";
|
|
3
4
|
import { listTasks } from "../repositories/tasks.js";
|
|
4
5
|
import { buildGamificationProfile } from "./gamification.js";
|
|
5
6
|
import { weeklyReviewPayloadSchema } from "../types.js";
|
|
@@ -36,7 +37,7 @@ function dailyBuckets(tasks, start) {
|
|
|
36
37
|
export function getWeeklyReviewPayload(now = new Date()) {
|
|
37
38
|
const goals = listGoals();
|
|
38
39
|
const tasks = listTasks();
|
|
39
|
-
const gamification = buildGamificationProfile(goals, tasks, now);
|
|
40
|
+
const gamification = buildGamificationProfile(goals, tasks, listHabits(), now);
|
|
40
41
|
const weekStart = startOfWeek(now);
|
|
41
42
|
const weekEnd = addDays(weekStart, 6);
|
|
42
43
|
const weekTasks = tasks.filter((task) => task.updatedAt >= weekStart.toISOString() && task.updatedAt <= addDays(weekEnd, 1).toISOString());
|
package/dist/server/types.js
CHANGED
|
@@ -11,8 +11,13 @@ export const taskDueFilterSchema = z.enum(["overdue", "today", "week"]);
|
|
|
11
11
|
export const taskRunStatusSchema = z.enum(["active", "completed", "released", "timed_out"]);
|
|
12
12
|
export const taskTimerModeSchema = z.enum(["planned", "unlimited"]);
|
|
13
13
|
export const timeAccountingModeSchema = z.enum(["split", "parallel", "primary_only"]);
|
|
14
|
+
export const habitFrequencySchema = z.enum(["daily", "weekly"]);
|
|
15
|
+
export const habitPolaritySchema = z.enum(["positive", "negative"]);
|
|
16
|
+
export const habitStatusSchema = z.enum(["active", "paused", "archived"]);
|
|
17
|
+
export const habitCheckInStatusSchema = z.enum(["done", "missed"]);
|
|
14
18
|
export const activityEntityTypeSchema = z.enum([
|
|
15
19
|
"task",
|
|
20
|
+
"habit",
|
|
16
21
|
"goal",
|
|
17
22
|
"project",
|
|
18
23
|
"domain",
|
|
@@ -50,6 +55,7 @@ export const crudEntityTypeSchema = z.enum([
|
|
|
50
55
|
"goal",
|
|
51
56
|
"project",
|
|
52
57
|
"task",
|
|
58
|
+
"habit",
|
|
53
59
|
"tag",
|
|
54
60
|
"note",
|
|
55
61
|
"insight",
|
|
@@ -63,6 +69,22 @@ export const crudEntityTypeSchema = z.enum([
|
|
|
63
69
|
"emotion_definition",
|
|
64
70
|
"trigger_report"
|
|
65
71
|
]);
|
|
72
|
+
export const rewardableEntityTypeSchema = z.enum([
|
|
73
|
+
"system",
|
|
74
|
+
"goal",
|
|
75
|
+
"project",
|
|
76
|
+
"task",
|
|
77
|
+
"habit",
|
|
78
|
+
"tag",
|
|
79
|
+
"note",
|
|
80
|
+
"insight",
|
|
81
|
+
"psyche_value",
|
|
82
|
+
"behavior_pattern",
|
|
83
|
+
"behavior",
|
|
84
|
+
"belief_entry",
|
|
85
|
+
"mode_profile",
|
|
86
|
+
"trigger_report"
|
|
87
|
+
]);
|
|
66
88
|
export const deleteModeSchema = z.enum(["soft", "hard"]);
|
|
67
89
|
export const rewardRuleFamilySchema = z.enum([
|
|
68
90
|
"completion",
|
|
@@ -215,6 +237,48 @@ export const taskRunSchema = z.object({
|
|
|
215
237
|
timedOutAt: z.string().nullable(),
|
|
216
238
|
updatedAt: z.string()
|
|
217
239
|
});
|
|
240
|
+
export const habitCheckInSchema = z.object({
|
|
241
|
+
id: z.string(),
|
|
242
|
+
habitId: z.string(),
|
|
243
|
+
dateKey: z.string(),
|
|
244
|
+
status: habitCheckInStatusSchema,
|
|
245
|
+
note: z.string(),
|
|
246
|
+
deltaXp: z.number().int(),
|
|
247
|
+
createdAt: z.string(),
|
|
248
|
+
updatedAt: z.string()
|
|
249
|
+
});
|
|
250
|
+
export const habitSchema = z.object({
|
|
251
|
+
id: z.string(),
|
|
252
|
+
title: nonEmptyTrimmedString,
|
|
253
|
+
description: trimmedString,
|
|
254
|
+
status: habitStatusSchema,
|
|
255
|
+
polarity: habitPolaritySchema,
|
|
256
|
+
frequency: habitFrequencySchema,
|
|
257
|
+
targetCount: z.number().int().positive(),
|
|
258
|
+
weekDays: z.array(z.number().int().min(0).max(6)).default([]),
|
|
259
|
+
linkedGoalIds: uniqueStringArraySchema.default([]),
|
|
260
|
+
linkedProjectIds: uniqueStringArraySchema.default([]),
|
|
261
|
+
linkedTaskIds: uniqueStringArraySchema.default([]),
|
|
262
|
+
linkedValueIds: uniqueStringArraySchema.default([]),
|
|
263
|
+
linkedPatternIds: uniqueStringArraySchema.default([]),
|
|
264
|
+
linkedBehaviorIds: uniqueStringArraySchema.default([]),
|
|
265
|
+
linkedBeliefIds: uniqueStringArraySchema.default([]),
|
|
266
|
+
linkedModeIds: uniqueStringArraySchema.default([]),
|
|
267
|
+
linkedReportIds: uniqueStringArraySchema.default([]),
|
|
268
|
+
linkedBehaviorId: z.string().nullable(),
|
|
269
|
+
linkedBehaviorTitle: z.string().nullable(),
|
|
270
|
+
linkedBehaviorTitles: z.array(z.string()).default([]),
|
|
271
|
+
rewardXp: z.number().int().positive(),
|
|
272
|
+
penaltyXp: z.number().int().positive(),
|
|
273
|
+
createdAt: z.string(),
|
|
274
|
+
updatedAt: z.string(),
|
|
275
|
+
lastCheckInAt: z.string().nullable(),
|
|
276
|
+
lastCheckInStatus: habitCheckInStatusSchema.nullable(),
|
|
277
|
+
streakCount: z.number().int().nonnegative(),
|
|
278
|
+
completionRate: z.number().min(0).max(100),
|
|
279
|
+
dueToday: z.boolean(),
|
|
280
|
+
checkIns: z.array(habitCheckInSchema).default([])
|
|
281
|
+
});
|
|
218
282
|
export const activityEventSchema = z.object({
|
|
219
283
|
id: z.string(),
|
|
220
284
|
entityType: activityEntityTypeSchema,
|
|
@@ -314,6 +378,7 @@ export const dashboardPayloadSchema = z.object({
|
|
|
314
378
|
goals: z.array(dashboardGoalSchema),
|
|
315
379
|
projects: z.array(projectSummarySchema),
|
|
316
380
|
tasks: z.array(taskSchema),
|
|
381
|
+
habits: z.array(habitSchema),
|
|
317
382
|
tags: z.array(tagSchema),
|
|
318
383
|
suggestedTags: z.array(tagSchema),
|
|
319
384
|
owners: z.array(z.string()),
|
|
@@ -354,6 +419,7 @@ export const overviewContextSchema = z.object({
|
|
|
354
419
|
projects: z.array(projectSummarySchema),
|
|
355
420
|
activeGoals: z.array(dashboardGoalSchema),
|
|
356
421
|
topTasks: z.array(taskSchema),
|
|
422
|
+
dueHabits: z.array(habitSchema),
|
|
357
423
|
recentEvidence: z.array(activityEventSchema),
|
|
358
424
|
achievements: z.array(achievementSignalSchema),
|
|
359
425
|
domainBalance: z.array(contextDomainBalanceSchema),
|
|
@@ -381,8 +447,10 @@ export const todayContextSchema = z.object({
|
|
|
381
447
|
sessionLabel: z.string()
|
|
382
448
|
}),
|
|
383
449
|
timeline: z.array(todayTimelineBucketSchema),
|
|
450
|
+
dueHabits: z.array(habitSchema),
|
|
384
451
|
dailyQuests: z.array(todayQuestSchema),
|
|
385
452
|
milestoneRewards: z.array(milestoneRewardSchema),
|
|
453
|
+
recentHabitRewards: z.array(z.lazy(() => rewardLedgerEventSchema)).default([]),
|
|
386
454
|
momentum: z.object({
|
|
387
455
|
streakDays: z.number().int().nonnegative(),
|
|
388
456
|
momentumScore: z.number().int().min(0).max(100),
|
|
@@ -656,6 +724,7 @@ export const operatorContextPayloadSchema = z.object({
|
|
|
656
724
|
generatedAt: z.string(),
|
|
657
725
|
activeProjects: z.array(projectSummarySchema),
|
|
658
726
|
focusTasks: z.array(taskSchema),
|
|
727
|
+
dueHabits: z.array(habitSchema),
|
|
659
728
|
currentBoard: z.object({
|
|
660
729
|
backlog: z.array(taskSchema),
|
|
661
730
|
focus: z.array(taskSchema),
|
|
@@ -675,7 +744,7 @@ export const updateRewardRuleSchema = z.object({
|
|
|
675
744
|
config: z.record(z.string(), rewardConfigValueSchema).optional()
|
|
676
745
|
});
|
|
677
746
|
export const createManualRewardGrantSchema = z.object({
|
|
678
|
-
entityType:
|
|
747
|
+
entityType: rewardableEntityTypeSchema,
|
|
679
748
|
entityId: nonEmptyTrimmedString,
|
|
680
749
|
deltaXp: z.number().int().refine((value) => value !== 0, {
|
|
681
750
|
message: "deltaXp must not be zero"
|
|
@@ -777,6 +846,12 @@ export const projectListQuerySchema = z.object({
|
|
|
777
846
|
status: projectStatusSchema.optional(),
|
|
778
847
|
limit: z.coerce.number().int().positive().max(100).optional()
|
|
779
848
|
});
|
|
849
|
+
export const habitListQuerySchema = z.object({
|
|
850
|
+
status: habitStatusSchema.optional(),
|
|
851
|
+
polarity: habitPolaritySchema.optional(),
|
|
852
|
+
dueToday: z.coerce.boolean().optional(),
|
|
853
|
+
limit: z.coerce.number().int().positive().max(100).optional()
|
|
854
|
+
});
|
|
780
855
|
export const createGoalSchema = z.object({
|
|
781
856
|
title: nonEmptyTrimmedString,
|
|
782
857
|
description: trimmedString.default(""),
|
|
@@ -822,6 +897,65 @@ export const taskMutationShape = {
|
|
|
822
897
|
notes: z.array(nestedCreateNoteSchema).default([])
|
|
823
898
|
};
|
|
824
899
|
export const createTaskSchema = z.object(taskMutationShape);
|
|
900
|
+
const habitMutationShape = {
|
|
901
|
+
title: nonEmptyTrimmedString,
|
|
902
|
+
description: trimmedString.default(""),
|
|
903
|
+
status: habitStatusSchema.default("active"),
|
|
904
|
+
polarity: habitPolaritySchema.default("positive"),
|
|
905
|
+
frequency: habitFrequencySchema.default("daily"),
|
|
906
|
+
targetCount: z.number().int().min(1).max(14).default(1),
|
|
907
|
+
weekDays: z.array(z.number().int().min(0).max(6)).max(7).default([]),
|
|
908
|
+
linkedGoalIds: uniqueStringArraySchema.default([]),
|
|
909
|
+
linkedProjectIds: uniqueStringArraySchema.default([]),
|
|
910
|
+
linkedTaskIds: uniqueStringArraySchema.default([]),
|
|
911
|
+
linkedValueIds: uniqueStringArraySchema.default([]),
|
|
912
|
+
linkedPatternIds: uniqueStringArraySchema.default([]),
|
|
913
|
+
linkedBehaviorIds: uniqueStringArraySchema.default([]),
|
|
914
|
+
linkedBeliefIds: uniqueStringArraySchema.default([]),
|
|
915
|
+
linkedModeIds: uniqueStringArraySchema.default([]),
|
|
916
|
+
linkedReportIds: uniqueStringArraySchema.default([]),
|
|
917
|
+
linkedBehaviorId: nonEmptyTrimmedString.nullable().default(null),
|
|
918
|
+
rewardXp: z.number().int().min(1).max(100).default(12),
|
|
919
|
+
penaltyXp: z.number().int().min(1).max(100).default(8)
|
|
920
|
+
};
|
|
921
|
+
export const createHabitSchema = z.object(habitMutationShape).superRefine((value, context) => {
|
|
922
|
+
if (value.frequency === "weekly" && value.weekDays.length === 0) {
|
|
923
|
+
context.addIssue({
|
|
924
|
+
code: z.ZodIssueCode.custom,
|
|
925
|
+
path: ["weekDays"],
|
|
926
|
+
message: "Select at least one weekday for weekly habits"
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
export const updateHabitSchema = z.object({
|
|
931
|
+
title: nonEmptyTrimmedString.optional(),
|
|
932
|
+
description: trimmedString.optional(),
|
|
933
|
+
status: habitStatusSchema.optional(),
|
|
934
|
+
polarity: habitPolaritySchema.optional(),
|
|
935
|
+
frequency: habitFrequencySchema.optional(),
|
|
936
|
+
targetCount: z.number().int().min(1).max(14).optional(),
|
|
937
|
+
weekDays: z.array(z.number().int().min(0).max(6)).max(7).optional(),
|
|
938
|
+
linkedGoalIds: uniqueStringArraySchema.optional(),
|
|
939
|
+
linkedProjectIds: uniqueStringArraySchema.optional(),
|
|
940
|
+
linkedTaskIds: uniqueStringArraySchema.optional(),
|
|
941
|
+
linkedValueIds: uniqueStringArraySchema.optional(),
|
|
942
|
+
linkedPatternIds: uniqueStringArraySchema.optional(),
|
|
943
|
+
linkedBehaviorIds: uniqueStringArraySchema.optional(),
|
|
944
|
+
linkedBeliefIds: uniqueStringArraySchema.optional(),
|
|
945
|
+
linkedModeIds: uniqueStringArraySchema.optional(),
|
|
946
|
+
linkedReportIds: uniqueStringArraySchema.optional(),
|
|
947
|
+
linkedBehaviorId: nonEmptyTrimmedString.nullable().optional(),
|
|
948
|
+
rewardXp: z.number().int().min(1).max(100).optional(),
|
|
949
|
+
penaltyXp: z.number().int().min(1).max(100).optional()
|
|
950
|
+
}).superRefine((value, context) => {
|
|
951
|
+
if (value.frequency === "weekly" && value.weekDays !== undefined && value.weekDays.length === 0) {
|
|
952
|
+
context.addIssue({
|
|
953
|
+
code: z.ZodIssueCode.custom,
|
|
954
|
+
path: ["weekDays"],
|
|
955
|
+
message: "Select at least one weekday for weekly habits"
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
});
|
|
825
959
|
export const updateTaskSchema = z.object({
|
|
826
960
|
title: nonEmptyTrimmedString.optional(),
|
|
827
961
|
description: trimmedString.optional(),
|
|
@@ -880,6 +1014,11 @@ export const taskRunFinishSchema = z.object({
|
|
|
880
1014
|
export const taskRunFocusSchema = z.object({
|
|
881
1015
|
actor: nonEmptyTrimmedString.optional()
|
|
882
1016
|
});
|
|
1017
|
+
export const createHabitCheckInSchema = z.object({
|
|
1018
|
+
dateKey: dateOnlySchema.default(new Date().toISOString().slice(0, 10)),
|
|
1019
|
+
status: habitCheckInStatusSchema,
|
|
1020
|
+
note: trimmedString.default("")
|
|
1021
|
+
});
|
|
883
1022
|
export const updateSettingsSchema = z.object({
|
|
884
1023
|
profile: z
|
|
885
1024
|
.object({
|
package/openclaw.plugin.json
CHANGED