@redjay/threads-core 1.1.0 → 1.2.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.
- package/LICENSE +21 -21
- package/README.md +42 -42
- package/dist/domain/agenda.d.ts +31 -0
- package/dist/domain/agenda.d.ts.map +1 -0
- package/dist/domain/agenda.js +60 -0
- package/dist/domain/agenda.js.map +1 -0
- package/dist/domain/batch.d.ts +67 -0
- package/dist/domain/batch.d.ts.map +1 -0
- package/dist/domain/batch.js +151 -0
- package/dist/domain/batch.js.map +1 -0
- package/dist/domain/clone.d.ts +31 -0
- package/dist/domain/clone.d.ts.map +1 -0
- package/dist/domain/clone.js +69 -0
- package/dist/domain/clone.js.map +1 -0
- package/dist/domain/index.d.ts +12 -0
- package/dist/domain/index.d.ts.map +1 -0
- package/dist/domain/index.js +30 -0
- package/dist/domain/index.js.map +1 -0
- package/dist/domain/merge.d.ts +25 -0
- package/dist/domain/merge.d.ts.map +1 -0
- package/dist/domain/merge.js +47 -0
- package/dist/domain/merge.js.map +1 -0
- package/dist/domain/overview.d.ts +41 -0
- package/dist/domain/overview.d.ts.map +1 -0
- package/dist/domain/overview.js +95 -0
- package/dist/domain/overview.js.map +1 -0
- package/dist/domain/progress.d.ts +28 -0
- package/dist/domain/progress.d.ts.map +1 -0
- package/dist/domain/progress.js +45 -0
- package/dist/domain/progress.js.map +1 -0
- package/dist/domain/scoring.d.ts +40 -0
- package/dist/domain/scoring.d.ts.map +1 -0
- package/dist/domain/scoring.js +64 -0
- package/dist/domain/scoring.js.map +1 -0
- package/dist/domain/search.d.ts +31 -0
- package/dist/domain/search.d.ts.map +1 -0
- package/dist/domain/search.js +59 -0
- package/dist/domain/search.js.map +1 -0
- package/dist/domain/sorting.d.ts +17 -0
- package/dist/domain/sorting.d.ts.map +1 -0
- package/dist/domain/sorting.js +32 -0
- package/dist/domain/sorting.js.map +1 -0
- package/dist/domain/time.d.ts +78 -0
- package/dist/domain/time.d.ts.map +1 -0
- package/dist/domain/time.js +146 -0
- package/dist/domain/time.js.map +1 -0
- package/dist/domain/timeline.d.ts +43 -0
- package/dist/domain/timeline.d.ts.map +1 -0
- package/dist/domain/timeline.js +65 -0
- package/dist/domain/timeline.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +62 -54
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Thread merging — progress, details, tags, dependencies.
|
|
4
|
+
*
|
|
5
|
+
* Pure functions — no storage dependencies.
|
|
6
|
+
* All functions take source and target arrays, return merged result.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.mergeProgress = mergeProgress;
|
|
10
|
+
exports.mergeDetails = mergeDetails;
|
|
11
|
+
exports.mergeTags = mergeTags;
|
|
12
|
+
exports.mergeDependencies = mergeDependencies;
|
|
13
|
+
/**
|
|
14
|
+
* Merge progress entries from target and source, sorted by timestamp.
|
|
15
|
+
*/
|
|
16
|
+
function mergeProgress(target, source) {
|
|
17
|
+
return [...target, ...source].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Merge details entries from target and source, sorted by timestamp.
|
|
21
|
+
*/
|
|
22
|
+
function mergeDetails(target, source) {
|
|
23
|
+
return [...target, ...source].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Merge tags as a union (no duplicates).
|
|
27
|
+
*/
|
|
28
|
+
function mergeTags(target, source) {
|
|
29
|
+
const set = new Set([...target, ...source]);
|
|
30
|
+
return Array.from(set);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Merge dependencies as a union by threadId.
|
|
34
|
+
* If both have the same threadId, target's version wins.
|
|
35
|
+
*/
|
|
36
|
+
function mergeDependencies(target, source) {
|
|
37
|
+
const byThreadId = new Map();
|
|
38
|
+
// Add source first so target can override
|
|
39
|
+
for (const dep of source) {
|
|
40
|
+
byThreadId.set(dep.threadId, dep);
|
|
41
|
+
}
|
|
42
|
+
for (const dep of target) {
|
|
43
|
+
byThreadId.set(dep.threadId, dep);
|
|
44
|
+
}
|
|
45
|
+
return Array.from(byThreadId.values());
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=merge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge.js","sourceRoot":"","sources":["../../src/domain/merge.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;AAOH,sCAIC;AAKD,oCAIC;AAKD,8BAGC;AAMD,8CAYC;AA1CD;;GAEG;AACH,SAAgB,aAAa,CAAC,MAAuB,EAAE,MAAuB;IAC5E,OAAO,CAAC,GAAG,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,IAAI,CAChC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAC5E,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAgB,YAAY,CAAC,MAAsB,EAAE,MAAsB;IACzE,OAAO,CAAC,GAAG,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,IAAI,CAChC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAC5E,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAgB,SAAS,CAAC,MAAgB,EAAE,MAAgB;IAC1D,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC;IAC5C,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED;;;GAGG;AACH,SAAgB,iBAAiB,CAAC,MAAoB,EAAE,MAAoB;IAC1E,MAAM,UAAU,GAAG,IAAI,GAAG,EAAsB,CAAC;IAEjD,0CAA0C;IAC1C,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard overview aggregation.
|
|
3
|
+
*
|
|
4
|
+
* Pure function — takes threads/groups arrays, returns computed overview data.
|
|
5
|
+
* Extracted from the inline logic in overview command's .action() handler.
|
|
6
|
+
*/
|
|
7
|
+
import { Thread, Group } from '../models/types';
|
|
8
|
+
/** Thread with recent activity metadata. */
|
|
9
|
+
export interface RecentThread {
|
|
10
|
+
thread: Thread;
|
|
11
|
+
updateCount: number;
|
|
12
|
+
lastNote: string;
|
|
13
|
+
}
|
|
14
|
+
/** Computed overview sections. */
|
|
15
|
+
export interface OverviewSections {
|
|
16
|
+
hotThreads: Thread[];
|
|
17
|
+
recentThreads: RecentThread[];
|
|
18
|
+
coldThreads: Thread[];
|
|
19
|
+
statsByGroup: Map<string, {
|
|
20
|
+
count: number;
|
|
21
|
+
hot: number;
|
|
22
|
+
warm: number;
|
|
23
|
+
}>;
|
|
24
|
+
ungroupedStats: {
|
|
25
|
+
count: number;
|
|
26
|
+
hot: number;
|
|
27
|
+
warm: number;
|
|
28
|
+
};
|
|
29
|
+
statusCounts: Record<string, number>;
|
|
30
|
+
groupMap: Map<string, Group>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Compute the full dashboard overview from raw data.
|
|
34
|
+
*
|
|
35
|
+
* @param threads - All threads
|
|
36
|
+
* @param groups - All groups
|
|
37
|
+
* @param daysThreshold - Days threshold for recent/cold classification (default 7)
|
|
38
|
+
* @param now - Current time (injectable for testing)
|
|
39
|
+
*/
|
|
40
|
+
export declare function computeOverview(threads: Thread[], groups: Group[], daysThreshold?: number, now?: Date): OverviewSections;
|
|
41
|
+
//# sourceMappingURL=overview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overview.d.ts","sourceRoot":"","sources":["../../src/domain/overview.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAGhD,4CAA4C;AAC5C,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,kCAAkC;AAClC,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxE,cAAc,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7D,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EAAE,EACjB,MAAM,EAAE,KAAK,EAAE,EACf,aAAa,GAAE,MAAU,EACzB,GAAG,GAAE,IAAiB,GACrB,gBAAgB,CA6ElB"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Dashboard overview aggregation.
|
|
4
|
+
*
|
|
5
|
+
* Pure function — takes threads/groups arrays, returns computed overview data.
|
|
6
|
+
* Extracted from the inline logic in overview command's .action() handler.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.computeOverview = computeOverview;
|
|
10
|
+
const time_1 = require("./time");
|
|
11
|
+
/**
|
|
12
|
+
* Compute the full dashboard overview from raw data.
|
|
13
|
+
*
|
|
14
|
+
* @param threads - All threads
|
|
15
|
+
* @param groups - All groups
|
|
16
|
+
* @param daysThreshold - Days threshold for recent/cold classification (default 7)
|
|
17
|
+
* @param now - Current time (injectable for testing)
|
|
18
|
+
*/
|
|
19
|
+
function computeOverview(threads, groups, daysThreshold = 7, now = new Date()) {
|
|
20
|
+
// Build group lookup
|
|
21
|
+
const groupMap = new Map();
|
|
22
|
+
groups.forEach(g => groupMap.set(g.id, g));
|
|
23
|
+
// Filter out archived for most sections
|
|
24
|
+
const activeThreads = threads.filter(t => t.status !== 'archived');
|
|
25
|
+
// Hot threads
|
|
26
|
+
const hotThreads = activeThreads.filter(t => t.temperature === 'hot');
|
|
27
|
+
// Recent activity
|
|
28
|
+
const recentThreads = [];
|
|
29
|
+
activeThreads.forEach(t => {
|
|
30
|
+
const lastDate = (0, time_1.getLastProgressDate)(t);
|
|
31
|
+
if (lastDate && (0, time_1.daysSince)(lastDate, now) <= daysThreshold) {
|
|
32
|
+
const recentUpdates = t.progress.filter(p => (0, time_1.daysSince)(p.timestamp, now) <= daysThreshold);
|
|
33
|
+
const lastNote = (0, time_1.getLastProgressNote)(t) || '';
|
|
34
|
+
recentThreads.push({ thread: t, updateCount: recentUpdates.length, lastNote });
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
recentThreads.sort((a, b) => {
|
|
38
|
+
const aDate = a.thread.lastUpdated || a.thread.modified;
|
|
39
|
+
const bDate = b.thread.lastUpdated || b.thread.modified;
|
|
40
|
+
return new Date(bDate).getTime() - new Date(aDate).getTime();
|
|
41
|
+
});
|
|
42
|
+
// Going cold: active threads with no updates in N+ days
|
|
43
|
+
const coldThreads = activeThreads.filter(t => {
|
|
44
|
+
if (t.status !== 'active')
|
|
45
|
+
return false;
|
|
46
|
+
const lastDate = (0, time_1.getLastProgressDate)(t);
|
|
47
|
+
if (!lastDate) {
|
|
48
|
+
return (0, time_1.daysSince)(t.createdAt, now) > daysThreshold;
|
|
49
|
+
}
|
|
50
|
+
return (0, time_1.daysSince)(lastDate, now) > daysThreshold;
|
|
51
|
+
});
|
|
52
|
+
// Stats by group
|
|
53
|
+
const statsByGroup = new Map();
|
|
54
|
+
const ungroupedStats = { count: 0, hot: 0, warm: 0 };
|
|
55
|
+
activeThreads.forEach(t => {
|
|
56
|
+
const isHot = t.temperature === 'hot';
|
|
57
|
+
const isWarm = t.temperature === 'warm';
|
|
58
|
+
if (t.groupId) {
|
|
59
|
+
if (!statsByGroup.has(t.groupId)) {
|
|
60
|
+
statsByGroup.set(t.groupId, { count: 0, hot: 0, warm: 0 });
|
|
61
|
+
}
|
|
62
|
+
const stats = statsByGroup.get(t.groupId);
|
|
63
|
+
stats.count++;
|
|
64
|
+
if (isHot)
|
|
65
|
+
stats.hot++;
|
|
66
|
+
if (isWarm)
|
|
67
|
+
stats.warm++;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
ungroupedStats.count++;
|
|
71
|
+
if (isHot)
|
|
72
|
+
ungroupedStats.hot++;
|
|
73
|
+
if (isWarm)
|
|
74
|
+
ungroupedStats.warm++;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
// Status counts (across ALL threads, including archived)
|
|
78
|
+
const statusCounts = {
|
|
79
|
+
active: threads.filter(t => t.status === 'active').length,
|
|
80
|
+
paused: threads.filter(t => t.status === 'paused').length,
|
|
81
|
+
stopped: threads.filter(t => t.status === 'stopped').length,
|
|
82
|
+
completed: threads.filter(t => t.status === 'completed').length,
|
|
83
|
+
archived: threads.filter(t => t.status === 'archived').length,
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
hotThreads,
|
|
87
|
+
recentThreads,
|
|
88
|
+
coldThreads,
|
|
89
|
+
statsByGroup,
|
|
90
|
+
ungroupedStats,
|
|
91
|
+
statusCounts,
|
|
92
|
+
groupMap,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=overview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overview.js","sourceRoot":"","sources":["../../src/domain/overview.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;AA+BH,0CAkFC;AA9GD,iCAA6E;AAoB7E;;;;;;;GAOG;AACH,SAAgB,eAAe,CAC7B,OAAiB,EACjB,MAAe,EACf,gBAAwB,CAAC,EACzB,MAAY,IAAI,IAAI,EAAE;IAEtB,qBAAqB;IACrB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAiB,CAAC;IAC1C,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAE3C,wCAAwC;IACxC,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC;IAEnE,cAAc;IACd,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,KAAK,CAAC,CAAC;IAEtE,kBAAkB;IAClB,MAAM,aAAa,GAAmB,EAAE,CAAC;IACzC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;QACxB,MAAM,QAAQ,GAAG,IAAA,0BAAmB,EAAC,CAAC,CAAC,CAAC;QACxC,IAAI,QAAQ,IAAI,IAAA,gBAAS,EAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,aAAa,EAAE,CAAC;YAC1D,MAAM,aAAa,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,IAAA,gBAAS,EAAC,CAAC,CAAC,SAAS,EAAE,GAAG,CAAC,IAAI,aAAa,CAAC,CAAC;YAC3F,MAAM,QAAQ,GAAG,IAAA,0BAAmB,EAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC9C,aAAa,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,EAAE,aAAa,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QACjF,CAAC;IACH,CAAC,CAAC,CAAC;IACH,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC1B,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC,WAAW,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;QACxD,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC,WAAW,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;QACxD,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,wDAAwD;IACxD,MAAM,WAAW,GAAa,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;QACrD,IAAI,CAAC,CAAC,MAAM,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QACxC,MAAM,QAAQ,GAAG,IAAA,0BAAmB,EAAC,CAAC,CAAC,CAAC;QACxC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,IAAA,gBAAS,EAAC,CAAC,CAAC,SAAS,EAAE,GAAG,CAAC,GAAG,aAAa,CAAC;QACrD,CAAC;QACD,OAAO,IAAA,gBAAS,EAAC,QAAQ,EAAE,GAAG,CAAC,GAAG,aAAa,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,iBAAiB;IACjB,MAAM,YAAY,GAAG,IAAI,GAAG,EAAwD,CAAC;IACrF,MAAM,cAAc,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAErD,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;QACxB,MAAM,KAAK,GAAG,CAAC,CAAC,WAAW,KAAK,KAAK,CAAC;QACtC,MAAM,MAAM,GAAG,CAAC,CAAC,WAAW,KAAK,MAAM,CAAC;QACxC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;YACd,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;gBACjC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;YAC7D,CAAC;YACD,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAE,CAAC;YAC3C,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,IAAI,KAAK;gBAAE,KAAK,CAAC,GAAG,EAAE,CAAC;YACvB,IAAI,MAAM;gBAAE,KAAK,CAAC,IAAI,EAAE,CAAC;QAC3B,CAAC;aAAM,CAAC;YACN,cAAc,CAAC,KAAK,EAAE,CAAC;YACvB,IAAI,KAAK;gBAAE,cAAc,CAAC,GAAG,EAAE,CAAC;YAChC,IAAI,MAAM;gBAAE,cAAc,CAAC,IAAI,EAAE,CAAC;QACpC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,yDAAyD;IACzD,MAAM,YAAY,GAA2B;QAC3C,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,MAAM;QACzD,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,MAAM;QACzD,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,MAAM;QAC3D,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,MAAM;QAC/D,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,MAAM;KAC9D,CAAC;IAEF,OAAO;QACL,UAAU;QACV,aAAa;QACb,WAAW;QACX,YAAY;QACZ,cAAc;QACd,YAAY;QACZ,QAAQ;KACT,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progress entry creation and movement helpers.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions — ID generation uses crypto.randomUUID() (Node 20+).
|
|
5
|
+
*/
|
|
6
|
+
import { ProgressEntry } from '../models/types';
|
|
7
|
+
/**
|
|
8
|
+
* Create a new progress entry with generated ID and timestamp.
|
|
9
|
+
*
|
|
10
|
+
* @param note - Progress note text
|
|
11
|
+
* @param timestamp - Custom timestamp (defaults to now)
|
|
12
|
+
*/
|
|
13
|
+
export declare function createProgressEntry(note: string, timestamp?: Date): ProgressEntry;
|
|
14
|
+
/**
|
|
15
|
+
* Move progress entries from source to destination.
|
|
16
|
+
* Returns updated arrays for both source and destination.
|
|
17
|
+
*
|
|
18
|
+
* @param sourceProgress - Source thread's progress entries
|
|
19
|
+
* @param destProgress - Destination thread's progress entries
|
|
20
|
+
* @param count - Number of most recent entries to move
|
|
21
|
+
* @returns Updated source and destination arrays, plus moved entries
|
|
22
|
+
*/
|
|
23
|
+
export declare function moveProgressEntries(sourceProgress: ProgressEntry[], destProgress: ProgressEntry[], count: number): {
|
|
24
|
+
remaining: ProgressEntry[];
|
|
25
|
+
updated: ProgressEntry[];
|
|
26
|
+
moved: ProgressEntry[];
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=progress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress.d.ts","sourceRoot":"","sources":["../../src/domain/progress.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,SAAS,GAAE,IAAiB,GAC3B,aAAa,CAMf;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,cAAc,EAAE,aAAa,EAAE,EAC/B,YAAY,EAAE,aAAa,EAAE,EAC7B,KAAK,EAAE,MAAM,GACZ;IACD,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,KAAK,EAAE,aAAa,EAAE,CAAC;CACxB,CAmBA"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Progress entry creation and movement helpers.
|
|
4
|
+
*
|
|
5
|
+
* Pure functions — ID generation uses crypto.randomUUID() (Node 20+).
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.createProgressEntry = createProgressEntry;
|
|
9
|
+
exports.moveProgressEntries = moveProgressEntries;
|
|
10
|
+
/**
|
|
11
|
+
* Create a new progress entry with generated ID and timestamp.
|
|
12
|
+
*
|
|
13
|
+
* @param note - Progress note text
|
|
14
|
+
* @param timestamp - Custom timestamp (defaults to now)
|
|
15
|
+
*/
|
|
16
|
+
function createProgressEntry(note, timestamp = new Date()) {
|
|
17
|
+
return {
|
|
18
|
+
id: crypto.randomUUID(),
|
|
19
|
+
timestamp: timestamp.toISOString(),
|
|
20
|
+
note,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Move progress entries from source to destination.
|
|
25
|
+
* Returns updated arrays for both source and destination.
|
|
26
|
+
*
|
|
27
|
+
* @param sourceProgress - Source thread's progress entries
|
|
28
|
+
* @param destProgress - Destination thread's progress entries
|
|
29
|
+
* @param count - Number of most recent entries to move
|
|
30
|
+
* @returns Updated source and destination arrays, plus moved entries
|
|
31
|
+
*/
|
|
32
|
+
function moveProgressEntries(sourceProgress, destProgress, count) {
|
|
33
|
+
// Sort source by timestamp to get chronological order
|
|
34
|
+
const sorted = [...sourceProgress].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
35
|
+
const actualCount = Math.min(count, sorted.length);
|
|
36
|
+
if (actualCount === 0) {
|
|
37
|
+
return { remaining: sorted, updated: [...destProgress], moved: [] };
|
|
38
|
+
}
|
|
39
|
+
const moved = sorted.slice(-actualCount);
|
|
40
|
+
const remaining = sorted.slice(0, -actualCount);
|
|
41
|
+
// Merge moved entries into destination, sorted by timestamp
|
|
42
|
+
const updated = [...destProgress, ...moved].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
43
|
+
return { remaining, updated, moved };
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=progress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress.js","sourceRoot":"","sources":["../../src/domain/progress.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AAUH,kDASC;AAWD,kDA2BC;AArDD;;;;;GAKG;AACH,SAAgB,mBAAmB,CACjC,IAAY,EACZ,YAAkB,IAAI,IAAI,EAAE;IAE5B,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE;QACvB,SAAS,EAAE,SAAS,CAAC,WAAW,EAAE;QAClC,IAAI;KACL,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,mBAAmB,CACjC,cAA+B,EAC/B,YAA6B,EAC7B,KAAa;IAMb,sDAAsD;IACtD,MAAM,MAAM,GAAG,CAAC,GAAG,cAAc,CAAC,CAAC,IAAI,CACrC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAC5E,CAAC;IAEF,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACnD,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,YAAY,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IACtE,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,CAAC;IACzC,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC;IAEhD,4DAA4D;IAC5D,MAAM,OAAO,GAAG,CAAC,GAAG,YAAY,EAAE,GAAG,KAAK,CAAC,CAAC,IAAI,CAC9C,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAC5E,CAAC;IAEF,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thread scoring for next-action recommendations.
|
|
3
|
+
*
|
|
4
|
+
* Formula: (importance * 3) + (temperature * 2) + recency
|
|
5
|
+
* Pure functions — no storage dependencies.
|
|
6
|
+
*/
|
|
7
|
+
import { Thread, Temperature } from '../models/types';
|
|
8
|
+
/** Temperature → numeric weight (0-5 scale). */
|
|
9
|
+
export declare const temperatureScores: Record<Temperature, number>;
|
|
10
|
+
/** A thread with its computed priority scores. */
|
|
11
|
+
export interface ScoredThread {
|
|
12
|
+
thread: Thread;
|
|
13
|
+
totalScore: number;
|
|
14
|
+
importanceScore: number;
|
|
15
|
+
temperatureScore: number;
|
|
16
|
+
recencyScore: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Compute a recency score that decays over time.
|
|
20
|
+
* Recent activity scores higher; activity >30 days old approaches 0.
|
|
21
|
+
*
|
|
22
|
+
* Formula: 5 * (1 / (1 + days/7))
|
|
23
|
+
* At 0 days: ~5, at 7 days: ~2.5, at 30 days: ~0.9
|
|
24
|
+
*
|
|
25
|
+
* @param isoDate - ISO timestamp of last activity
|
|
26
|
+
* @param now - Current time (injectable for testing)
|
|
27
|
+
*/
|
|
28
|
+
export declare function recencyScore(isoDate: string, now?: Date): number;
|
|
29
|
+
/**
|
|
30
|
+
* Get the most recent activity date for a thread.
|
|
31
|
+
* Uses lastUpdated (derived from progress) with modified as fallback.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getMostRecentActivity(thread: Thread): string;
|
|
34
|
+
/**
|
|
35
|
+
* Calculate composite priority score for a thread.
|
|
36
|
+
*
|
|
37
|
+
* Weights: importance=3, temperature=2, recency=1
|
|
38
|
+
*/
|
|
39
|
+
export declare function scoreThread(thread: Thread, now?: Date): ScoredThread;
|
|
40
|
+
//# sourceMappingURL=scoring.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scoring.d.ts","sourceRoot":"","sources":["../../src/domain/scoring.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGtD,gDAAgD;AAChD,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAOzD,CAAC;AAEF,kDAAkD;AAClD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM,CAI5E;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,YAAY,CAehF"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Thread scoring for next-action recommendations.
|
|
4
|
+
*
|
|
5
|
+
* Formula: (importance * 3) + (temperature * 2) + recency
|
|
6
|
+
* Pure functions — no storage dependencies.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.temperatureScores = void 0;
|
|
10
|
+
exports.recencyScore = recencyScore;
|
|
11
|
+
exports.getMostRecentActivity = getMostRecentActivity;
|
|
12
|
+
exports.scoreThread = scoreThread;
|
|
13
|
+
const time_1 = require("./time");
|
|
14
|
+
/** Temperature → numeric weight (0-5 scale). */
|
|
15
|
+
exports.temperatureScores = {
|
|
16
|
+
frozen: 0,
|
|
17
|
+
freezing: 1,
|
|
18
|
+
cold: 2,
|
|
19
|
+
tepid: 3,
|
|
20
|
+
warm: 4,
|
|
21
|
+
hot: 5,
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Compute a recency score that decays over time.
|
|
25
|
+
* Recent activity scores higher; activity >30 days old approaches 0.
|
|
26
|
+
*
|
|
27
|
+
* Formula: 5 * (1 / (1 + days/7))
|
|
28
|
+
* At 0 days: ~5, at 7 days: ~2.5, at 30 days: ~0.9
|
|
29
|
+
*
|
|
30
|
+
* @param isoDate - ISO timestamp of last activity
|
|
31
|
+
* @param now - Current time (injectable for testing)
|
|
32
|
+
*/
|
|
33
|
+
function recencyScore(isoDate, now = new Date()) {
|
|
34
|
+
const hours = (0, time_1.hoursSince)(isoDate, now);
|
|
35
|
+
const days = hours / 24;
|
|
36
|
+
return 5 * (1 / (1 + days / 7));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get the most recent activity date for a thread.
|
|
40
|
+
* Uses lastUpdated (derived from progress) with modified as fallback.
|
|
41
|
+
*/
|
|
42
|
+
function getMostRecentActivity(thread) {
|
|
43
|
+
return thread.lastUpdated || thread.modified;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Calculate composite priority score for a thread.
|
|
47
|
+
*
|
|
48
|
+
* Weights: importance=3, temperature=2, recency=1
|
|
49
|
+
*/
|
|
50
|
+
function scoreThread(thread, now = new Date()) {
|
|
51
|
+
const impScore = thread.importance;
|
|
52
|
+
const tempScore = exports.temperatureScores[thread.temperature ?? 'frozen'];
|
|
53
|
+
const recentActivity = getMostRecentActivity(thread);
|
|
54
|
+
const recScore = recencyScore(recentActivity, now);
|
|
55
|
+
const total = (impScore * 3) + (tempScore * 2) + recScore;
|
|
56
|
+
return {
|
|
57
|
+
thread,
|
|
58
|
+
totalScore: total,
|
|
59
|
+
importanceScore: impScore,
|
|
60
|
+
temperatureScore: tempScore,
|
|
61
|
+
recencyScore: recScore,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=scoring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scoring.js","sourceRoot":"","sources":["../../src/domain/scoring.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAkCH,oCAIC;AAMD,sDAEC;AAOD,kCAeC;AAjED,iCAAoC;AAEpC,gDAAgD;AACnC,QAAA,iBAAiB,GAAgC;IAC5D,MAAM,EAAE,CAAC;IACT,QAAQ,EAAE,CAAC;IACX,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,GAAG,EAAE,CAAC;CACP,CAAC;AAWF;;;;;;;;;GASG;AACH,SAAgB,YAAY,CAAC,OAAe,EAAE,MAAY,IAAI,IAAI,EAAE;IAClE,MAAM,KAAK,GAAG,IAAA,iBAAU,EAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;IACxB,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;AAClC,CAAC;AAED;;;GAGG;AACH,SAAgB,qBAAqB,CAAC,MAAc;IAClD,OAAO,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,QAAQ,CAAC;AAC/C,CAAC;AAED;;;;GAIG;AACH,SAAgB,WAAW,CAAC,MAAc,EAAE,MAAY,IAAI,IAAI,EAAE;IAChE,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC;IACnC,MAAM,SAAS,GAAG,yBAAiB,CAAC,MAAM,CAAC,WAAW,IAAI,QAAQ,CAAC,CAAC;IACpE,MAAM,cAAc,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;IACrD,MAAM,QAAQ,GAAG,YAAY,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;IAEnD,MAAM,KAAK,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC;IAE1D,OAAO;QACL,MAAM;QACN,UAAU,EAAE,KAAK;QACjB,eAAe,EAAE,QAAQ;QACzB,gBAAgB,EAAE,SAAS;QAC3B,YAAY,EAAE,QAAQ;KACvB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full-text search across thread fields.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions — regex-based text matching with no storage dependencies.
|
|
5
|
+
*/
|
|
6
|
+
import { Thread } from '../models/types';
|
|
7
|
+
/** Search scope: which thread fields to search. */
|
|
8
|
+
export type SearchScope = 'name' | 'progress' | 'details' | 'tags' | 'all';
|
|
9
|
+
/** A single match position within text. */
|
|
10
|
+
export interface MatchContext {
|
|
11
|
+
scope: SearchScope;
|
|
12
|
+
text: string;
|
|
13
|
+
matchStart: number;
|
|
14
|
+
matchEnd: number;
|
|
15
|
+
}
|
|
16
|
+
/** A thread with its match contexts. */
|
|
17
|
+
export interface SearchMatch {
|
|
18
|
+
thread: Thread;
|
|
19
|
+
matches: MatchContext[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Find all occurrences of a pattern in text, returning match contexts.
|
|
23
|
+
*
|
|
24
|
+
* Uses substring indexOf (not regex) for simplicity and safety.
|
|
25
|
+
*/
|
|
26
|
+
export declare function findMatches(text: string, pattern: string, scope: SearchScope, caseSensitive: boolean): MatchContext[];
|
|
27
|
+
/**
|
|
28
|
+
* Search a single thread for matches across the specified scope(s).
|
|
29
|
+
*/
|
|
30
|
+
export declare function searchThread(thread: Thread, pattern: string, scope: SearchScope, caseSensitive: boolean): MatchContext[];
|
|
31
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/domain/search.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC,mDAAmD;AACnD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,KAAK,CAAC;AAE3E,2CAA2C;AAC3C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,WAAW,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wCAAwC;AACxC,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,WAAW,EAClB,aAAa,EAAE,OAAO,GACrB,YAAY,EAAE,CAoBhB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,WAAW,EAClB,aAAa,EAAE,OAAO,GACrB,YAAY,EAAE,CA0BhB"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Full-text search across thread fields.
|
|
4
|
+
*
|
|
5
|
+
* Pure functions — regex-based text matching with no storage dependencies.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.findMatches = findMatches;
|
|
9
|
+
exports.searchThread = searchThread;
|
|
10
|
+
/**
|
|
11
|
+
* Find all occurrences of a pattern in text, returning match contexts.
|
|
12
|
+
*
|
|
13
|
+
* Uses substring indexOf (not regex) for simplicity and safety.
|
|
14
|
+
*/
|
|
15
|
+
function findMatches(text, pattern, scope, caseSensitive) {
|
|
16
|
+
const results = [];
|
|
17
|
+
const searchText = caseSensitive ? text : text.toLowerCase();
|
|
18
|
+
const searchPattern = caseSensitive ? pattern : pattern.toLowerCase();
|
|
19
|
+
let pos = 0;
|
|
20
|
+
while (pos < searchText.length) {
|
|
21
|
+
const idx = searchText.indexOf(searchPattern, pos);
|
|
22
|
+
if (idx === -1)
|
|
23
|
+
break;
|
|
24
|
+
results.push({
|
|
25
|
+
scope,
|
|
26
|
+
text,
|
|
27
|
+
matchStart: idx,
|
|
28
|
+
matchEnd: idx + pattern.length,
|
|
29
|
+
});
|
|
30
|
+
pos = idx + 1;
|
|
31
|
+
}
|
|
32
|
+
return results;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Search a single thread for matches across the specified scope(s).
|
|
36
|
+
*/
|
|
37
|
+
function searchThread(thread, pattern, scope, caseSensitive) {
|
|
38
|
+
const matches = [];
|
|
39
|
+
if (scope === 'all' || scope === 'name') {
|
|
40
|
+
matches.push(...findMatches(thread.name, pattern, 'name', caseSensitive));
|
|
41
|
+
}
|
|
42
|
+
if (scope === 'all' || scope === 'progress') {
|
|
43
|
+
for (const entry of thread.progress || []) {
|
|
44
|
+
matches.push(...findMatches(entry.note, pattern, 'progress', caseSensitive));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (scope === 'all' || scope === 'details') {
|
|
48
|
+
for (const entry of thread.details || []) {
|
|
49
|
+
matches.push(...findMatches(entry.content, pattern, 'details', caseSensitive));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (scope === 'all' || scope === 'tags') {
|
|
53
|
+
for (const tag of thread.tags || []) {
|
|
54
|
+
matches.push(...findMatches(tag, pattern, 'tags', caseSensitive));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return matches;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/domain/search.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AA0BH,kCAyBC;AAKD,oCA+BC;AAlED;;;;GAIG;AACH,SAAgB,WAAW,CACzB,IAAY,EACZ,OAAe,EACf,KAAkB,EAClB,aAAsB;IAEtB,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,MAAM,UAAU,GAAG,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;IAC7D,MAAM,aAAa,GAAG,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;IAEtE,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,OAAO,GAAG,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;QACnD,IAAI,GAAG,KAAK,CAAC,CAAC;YAAE,MAAM;QAEtB,OAAO,CAAC,IAAI,CAAC;YACX,KAAK;YACL,IAAI;YACJ,UAAU,EAAE,GAAG;YACf,QAAQ,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM;SAC/B,CAAC,CAAC;QACH,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;IAChB,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,SAAgB,YAAY,CAC1B,MAAc,EACd,OAAe,EACf,KAAkB,EAClB,aAAsB;IAEtB,MAAM,OAAO,GAAmB,EAAE,CAAC;IAEnC,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;QACxC,OAAO,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;IAC5E,CAAC;IAED,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,UAAU,EAAE,CAAC;QAC5C,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;YAC1C,OAAO,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC;QAC/E,CAAC;IACH,CAAC;IAED,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC3C,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;YACzC,OAAO,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;IAED,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;QACxC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;YACpC,OAAO,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thread sorting functions.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions — no storage dependencies.
|
|
5
|
+
* Uses TEMPERATURE_ORDER from models for canonical ordering.
|
|
6
|
+
*/
|
|
7
|
+
import { Thread, Temperature } from '../models/types';
|
|
8
|
+
/** Temperature values considered "cold" for attention logic. */
|
|
9
|
+
export declare const COLD_TEMPS: Temperature[];
|
|
10
|
+
/**
|
|
11
|
+
* Sort threads by priority: temperature (hottest first), importance (highest first),
|
|
12
|
+
* then lastUpdated (most recent first).
|
|
13
|
+
*
|
|
14
|
+
* Returns a new array — does not mutate the input.
|
|
15
|
+
*/
|
|
16
|
+
export declare function sortByPriority(threads: Thread[]): Thread[];
|
|
17
|
+
//# sourceMappingURL=sorting.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sorting.d.ts","sourceRoot":"","sources":["../../src/domain/sorting.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGtD,gEAAgE;AAChE,eAAO,MAAM,UAAU,EAAE,WAAW,EAAmC,CAAC;AAExE;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAS1D"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Thread sorting functions.
|
|
4
|
+
*
|
|
5
|
+
* Pure functions — no storage dependencies.
|
|
6
|
+
* Uses TEMPERATURE_ORDER from models for canonical ordering.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.COLD_TEMPS = void 0;
|
|
10
|
+
exports.sortByPriority = sortByPriority;
|
|
11
|
+
const temperature_1 = require("../models/temperature");
|
|
12
|
+
/** Temperature values considered "cold" for attention logic. */
|
|
13
|
+
exports.COLD_TEMPS = ['cold', 'freezing', 'frozen'];
|
|
14
|
+
/**
|
|
15
|
+
* Sort threads by priority: temperature (hottest first), importance (highest first),
|
|
16
|
+
* then lastUpdated (most recent first).
|
|
17
|
+
*
|
|
18
|
+
* Returns a new array — does not mutate the input.
|
|
19
|
+
*/
|
|
20
|
+
function sortByPriority(threads) {
|
|
21
|
+
return [...threads].sort((a, b) => {
|
|
22
|
+
const tempDiff = temperature_1.TEMPERATURE_ORDER.indexOf(a.temperature ?? 'frozen') - temperature_1.TEMPERATURE_ORDER.indexOf(b.temperature ?? 'frozen');
|
|
23
|
+
if (tempDiff !== 0)
|
|
24
|
+
return tempDiff;
|
|
25
|
+
if (a.importance !== b.importance)
|
|
26
|
+
return b.importance - a.importance;
|
|
27
|
+
const aTime = new Date(a.lastUpdated || a.modified).getTime();
|
|
28
|
+
const bTime = new Date(b.lastUpdated || b.modified).getTime();
|
|
29
|
+
return bTime - aTime;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=sorting.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sorting.js","sourceRoot":"","sources":["../../src/domain/sorting.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAcH,wCASC;AApBD,uDAA0D;AAE1D,gEAAgE;AACnD,QAAA,UAAU,GAAkB,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;AAExE;;;;;GAKG;AACH,SAAgB,cAAc,CAAC,OAAiB;IAC9C,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAChC,MAAM,QAAQ,GAAG,+BAAiB,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,IAAI,QAAQ,CAAC,GAAG,+BAAiB,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,IAAI,QAAQ,CAAC,CAAC;QAC7H,IAAI,QAAQ,KAAK,CAAC;YAAE,OAAO,QAAQ,CAAC;QACpC,IAAI,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC,UAAU;YAAE,OAAO,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC;QACtE,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,CAAC;QAC9D,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,CAAC;QAC9D,OAAO,KAAK,GAAG,KAAK,CAAC;IACvB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Date/time utility functions for thread domain logic.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions — no storage or side-effect dependencies.
|
|
5
|
+
* Deduplicates copies from agenda.ts, overview.ts, next.ts.
|
|
6
|
+
*/
|
|
7
|
+
import { Thread } from '../models/types';
|
|
8
|
+
/**
|
|
9
|
+
* Calculate calendar days since a given ISO date string.
|
|
10
|
+
*
|
|
11
|
+
* @param isoDate - ISO 8601 timestamp
|
|
12
|
+
* @param now - Current time (injectable for testing)
|
|
13
|
+
* @returns Integer number of days elapsed
|
|
14
|
+
*/
|
|
15
|
+
export declare function daysSince(isoDate: string, now?: Date): number;
|
|
16
|
+
/**
|
|
17
|
+
* Calculate hours since a given ISO date string.
|
|
18
|
+
*
|
|
19
|
+
* @param isoDate - ISO 8601 timestamp
|
|
20
|
+
* @param now - Current time (injectable for testing)
|
|
21
|
+
* @returns Fractional hours elapsed
|
|
22
|
+
*/
|
|
23
|
+
export declare function hoursSince(isoDate: string, now?: Date): number;
|
|
24
|
+
/**
|
|
25
|
+
* Check if a date is today.
|
|
26
|
+
*
|
|
27
|
+
* @param isoDate - ISO 8601 timestamp
|
|
28
|
+
* @param now - Current time (injectable for testing)
|
|
29
|
+
*/
|
|
30
|
+
export declare function isToday(isoDate: string, now?: Date): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Check if a date is within the last N days (inclusive).
|
|
33
|
+
*
|
|
34
|
+
* @param isoDate - ISO 8601 timestamp
|
|
35
|
+
* @param days - Number of days to look back
|
|
36
|
+
* @param now - Current time (injectable for testing)
|
|
37
|
+
*/
|
|
38
|
+
export declare function isWithinDays(isoDate: string, days: number, now?: Date): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Format a relative time string for display.
|
|
41
|
+
*
|
|
42
|
+
* @returns "today", "yesterday", "3d ago", "2w ago", "1mo ago"
|
|
43
|
+
*/
|
|
44
|
+
export declare function formatRelativeTime(isoDate: string, now?: Date): string;
|
|
45
|
+
/**
|
|
46
|
+
* Get the most recent progress entry timestamp for a thread.
|
|
47
|
+
*
|
|
48
|
+
* @returns ISO timestamp string or null if no progress
|
|
49
|
+
*/
|
|
50
|
+
export declare function getLastProgressDate(thread: Thread): string | null;
|
|
51
|
+
/**
|
|
52
|
+
* Get the most recent progress note text for a thread.
|
|
53
|
+
*
|
|
54
|
+
* @returns Note string or null if no progress
|
|
55
|
+
*/
|
|
56
|
+
export declare function getLastProgressNote(thread: Thread): string | null;
|
|
57
|
+
/**
|
|
58
|
+
* Truncate a string to a max length with ellipsis.
|
|
59
|
+
*
|
|
60
|
+
* @param str - Input string
|
|
61
|
+
* @param maxLen - Maximum output length (including ellipsis)
|
|
62
|
+
* @returns Truncated string
|
|
63
|
+
*/
|
|
64
|
+
export declare function truncate(str: string, maxLen: number): string;
|
|
65
|
+
/**
|
|
66
|
+
* Parse a date string loosely. Supports ISO dates and natural language.
|
|
67
|
+
*
|
|
68
|
+
* @returns Date object or null if unparseable
|
|
69
|
+
*/
|
|
70
|
+
export declare function parseDate(dateStr: string): Date | null;
|
|
71
|
+
/**
|
|
72
|
+
* Parse a datetime string with natural language support.
|
|
73
|
+
* Supports: ISO dates, "yesterday", "N days ago".
|
|
74
|
+
*
|
|
75
|
+
* @returns Date object or null if unparseable
|
|
76
|
+
*/
|
|
77
|
+
export declare function parseDateTime(input: string, now?: Date): Date | null;
|
|
78
|
+
//# sourceMappingURL=time.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../src/domain/time.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM,CAIzE;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM,CAI1E;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO,CAGxE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO,CAE3F;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM,CAOlF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGjE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGjE;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAG5D;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAGtD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,IAAI,GAAG,IAAI,CAkBhF"}
|