things-api 0.11.0 → 0.13.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/README.md +16 -5
- package/dist/audit/schema.d.ts +7 -0
- package/dist/audit/schema.js.map +1 -1
- package/dist/cli/canonical-ref.d.ts +21 -0
- package/dist/cli/canonical-ref.js +23 -0
- package/dist/cli/canonical-ref.js.map +1 -0
- package/dist/cli/commands/area.d.ts +18 -11
- package/dist/cli/commands/area.js +81 -77
- package/dist/cli/commands/area.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +1 -2
- package/dist/cli/commands/doctor.js +11 -1
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/install-skill.d.ts +19 -4
- package/dist/cli/commands/install-skill.js +130 -52
- package/dist/cli/commands/install-skill.js.map +1 -1
- package/dist/cli/commands/mcp.js +21 -1
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/project.d.ts +1 -0
- package/dist/cli/commands/project.js +92 -48
- package/dist/cli/commands/project.js.map +1 -1
- package/dist/cli/commands/reads.d.ts +5 -1
- package/dist/cli/commands/reads.js +110 -31
- package/dist/cli/commands/reads.js.map +1 -1
- package/dist/cli/commands/setup.js +11 -4
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/show.js +75 -36
- package/dist/cli/commands/show.js.map +1 -1
- package/dist/cli/commands/todo.js +2 -2
- package/dist/cli/commands/todo.js.map +1 -1
- package/dist/cli/commands/writes.js +795 -144
- package/dist/cli/commands/writes.js.map +1 -1
- package/dist/cli/did-you-mean.d.ts +14 -3
- package/dist/cli/did-you-mean.js +44 -7
- package/dist/cli/did-you-mean.js.map +1 -1
- package/dist/cli/dry-run.d.ts +55 -0
- package/dist/cli/dry-run.js +92 -0
- package/dist/cli/dry-run.js.map +1 -0
- package/dist/cli/glyphs.d.ts +16 -2
- package/dist/cli/glyphs.js +60 -12
- package/dist/cli/glyphs.js.map +1 -1
- package/dist/cli/help.js +69 -13
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/main.js +8 -1
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/move-hint.d.ts +3 -2
- package/dist/cli/move-hint.js +2 -2
- package/dist/cli/move-hint.js.map +1 -1
- package/dist/cli/read-driver.d.ts +53 -5
- package/dist/cli/read-driver.js +82 -15
- package/dist/cli/read-driver.js.map +1 -1
- package/dist/cli/ref-render.d.ts +14 -0
- package/dist/cli/ref-render.js +9 -0
- package/dist/cli/ref-render.js.map +1 -0
- package/dist/cli/render.d.ts +40 -14
- package/dist/cli/render.js +128 -55
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/resolve-invocation.d.ts +7 -5
- package/dist/cli/resolve-invocation.js +16 -7
- package/dist/cli/resolve-invocation.js.map +1 -1
- package/dist/cli/skill-check.d.ts +12 -6
- package/dist/cli/skill-check.js +31 -14
- package/dist/cli/skill-check.js.map +1 -1
- package/dist/cli/skill.d.ts +8 -0
- package/dist/cli/skill.js +10 -0
- package/dist/cli/skill.js.map +1 -1
- package/dist/cli/verb-hint.js +1 -1
- package/dist/cli/verb-hint.js.map +1 -1
- package/dist/client.d.ts +158 -26
- package/dist/client.js +254 -38
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +66 -2
- package/dist/config.js +120 -12
- package/dist/config.js.map +1 -1
- package/dist/contracts.d.ts +187 -28
- package/dist/contracts.js +22 -1
- package/dist/contracts.js.map +1 -1
- package/dist/db/baselines/db-v26.js +4 -1
- package/dist/db/baselines/db-v26.js.map +1 -1
- package/dist/diagnose.d.ts +34 -2
- package/dist/diagnose.js +36 -1
- package/dist/diagnose.js.map +1 -1
- package/dist/index.d.ts +21 -8
- package/dist/index.js +26 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts +9 -0
- package/dist/mcp/server.js +790 -322
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/entities.d.ts +57 -4
- package/dist/model/entities.js.map +1 -1
- package/dist/model/mappers.d.ts +2 -0
- package/dist/model/mappers.js +44 -2
- package/dist/model/mappers.js.map +1 -1
- package/dist/model/recurrence.d.ts +8 -1
- package/dist/model/recurrence.js.map +1 -1
- package/dist/model/serialize.js +15 -3
- package/dist/model/serialize.js.map +1 -1
- package/dist/read/area-filter.d.ts +76 -0
- package/dist/read/area-filter.js +58 -0
- package/dist/read/area-filter.js.map +1 -0
- package/dist/read/area-view.d.ts +22 -11
- package/dist/read/area-view.js +54 -28
- package/dist/read/area-view.js.map +1 -1
- package/dist/read/detail.js +15 -8
- package/dist/read/detail.js.map +1 -1
- package/dist/read/predicates.d.ts +18 -0
- package/dist/read/predicates.js +19 -0
- package/dist/read/predicates.js.map +1 -1
- package/dist/read/project-view.d.ts +91 -15
- package/dist/read/project-view.js +135 -37
- package/dist/read/project-view.js.map +1 -1
- package/dist/read/pseudo-area.d.ts +29 -0
- package/dist/read/pseudo-area.js +27 -0
- package/dist/read/pseudo-area.js.map +1 -0
- package/dist/read/queries.d.ts +175 -19
- package/dist/read/queries.js +376 -32
- package/dist/read/queries.js.map +1 -1
- package/dist/read/scope.d.ts +126 -0
- package/dist/read/scope.js +161 -0
- package/dist/read/scope.js.map +1 -0
- package/dist/read/search-rank.d.ts +2 -5
- package/dist/read/shape.d.ts +181 -0
- package/dist/read/shape.js +737 -0
- package/dist/read/shape.js.map +1 -0
- package/dist/read/show-target.d.ts +9 -1
- package/dist/read/show-target.js +77 -17
- package/dist/read/show-target.js.map +1 -1
- package/dist/read/stage.d.ts +199 -0
- package/dist/read/stage.js +125 -0
- package/dist/read/stage.js.map +1 -0
- package/dist/read/truncation.d.ts +7 -12
- package/dist/read/truncation.js +56 -21
- package/dist/read/truncation.js.map +1 -1
- package/dist/read/views.d.ts +68 -16
- package/dist/read/views.js +206 -52
- package/dist/read/views.js.map +1 -1
- package/dist/surface-copy.d.ts +16 -0
- package/dist/surface-copy.js +16 -0
- package/dist/surface-copy.js.map +1 -1
- package/dist/write/batch.d.ts +49 -10
- package/dist/write/batch.js +423 -71
- package/dist/write/batch.js.map +1 -1
- package/dist/write/commands.js +268 -55
- package/dist/write/commands.js.map +1 -1
- package/dist/write/guards.d.ts +1 -1
- package/dist/write/guards.js +71 -10
- package/dist/write/guards.js.map +1 -1
- package/dist/write/heading.d.ts +10 -1
- package/dist/write/heading.js +35 -5
- package/dist/write/heading.js.map +1 -1
- package/dist/write/make-repeating-project.d.ts +2 -2
- package/dist/write/make-repeating-project.js +9 -9
- package/dist/write/make-repeating-project.js.map +1 -1
- package/dist/write/move.d.ts +134 -0
- package/dist/write/move.js +1874 -0
- package/dist/write/move.js.map +1 -0
- package/dist/write/operations.d.ts +105 -19
- package/dist/write/operations.js +56 -7
- package/dist/write/operations.js.map +1 -1
- package/dist/write/opid.d.ts +31 -0
- package/dist/write/opid.js +30 -0
- package/dist/write/opid.js.map +1 -0
- package/dist/write/pipeline.d.ts +70 -3
- package/dist/write/pipeline.js +198 -33
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/pre-state.d.ts +172 -8
- package/dist/write/pre-state.js +467 -25
- package/dist/write/pre-state.js.map +1 -1
- package/dist/write/reorder.d.ts +23 -3
- package/dist/write/reorder.js +2106 -86
- package/dist/write/reorder.js.map +1 -1
- package/dist/write/reversibility.js +19 -7
- package/dist/write/reversibility.js.map +1 -1
- package/dist/write/scope-guard.d.ts +31 -0
- package/dist/write/scope-guard.js +162 -0
- package/dist/write/scope-guard.js.map +1 -0
- package/dist/write/undo.js +120 -13
- package/dist/write/undo.js.map +1 -1
- package/dist/write/vectors/applescript.js +16 -7
- package/dist/write/vectors/applescript.js.map +1 -1
- package/dist/write/vectors/shortcuts.js +1 -1
- package/dist/write/vectors/shortcuts.js.map +1 -1
- package/dist/write/vectors/simulator.js +83 -27
- package/dist/write/vectors/simulator.js.map +1 -1
- package/dist/write/vectors/types.d.ts +11 -0
- package/dist/write/vectors/ui-certification.d.ts +3 -3
- package/dist/write/vectors/ui-certification.js +41 -5
- package/dist/write/vectors/ui-certification.js.map +1 -1
- package/dist/write/vectors/ui-drag.d.ts +21 -1
- package/dist/write/vectors/ui-drag.js +138 -27
- package/dist/write/vectors/ui-drag.js.map +1 -1
- package/dist/write/vectors/ui-recipes.d.ts +21 -1
- package/dist/write/vectors/ui-recipes.js +166 -7
- package/dist/write/vectors/ui-recipes.js.map +1 -1
- package/dist/write/vectors/ui.d.ts +25 -1
- package/dist/write/vectors/ui.js +142 -33
- package/dist/write/vectors/ui.js.map +1 -1
- package/dist/write/verify/delta.d.ts +17 -1
- package/dist/write/verify/delta.js +106 -29
- package/dist/write/verify/delta.js.map +1 -1
- package/package.json +7 -1
- package/schema/envelope.schema.json +397 -0
- package/skills/things-cli/SKILL.md +36 -17
- package/skills/things-cli/references/banner.md +35 -0
- package/skills/things-cli/references/contracts.md +32 -9
- package/skills/things-cli/references/errors.md +57 -0
- package/skills/things-cli/references/gui.md +1 -1
- package/skills/things-cli/references/model.md +18 -7
- package/skills/things-cli/references/ordering.md +88 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Container-scoped sandbox — the ONE membership relation, shared by reads,
|
|
3
|
+
* writes, and reference resolution. Generalizes the `--area` post-filter
|
|
4
|
+
* (`area-filter.ts`) from "one area" to "one container" (an area OR a project),
|
|
5
|
+
* and adds the leak-critical SQL fragments the single-row / candidate paths and
|
|
6
|
+
* the write ancestry check share so an out-of-scope item is indistinguishable
|
|
7
|
+
* from a nonexistent one (the no-oracle guarantee — see
|
|
8
|
+
* docs/design/container-scope.md).
|
|
9
|
+
*
|
|
10
|
+
* A scope is resolved ONCE at `openThings()` and pinned for the client's life
|
|
11
|
+
* (never re-resolved per call — a deleted container becomes a safe empty jail,
|
|
12
|
+
* a recreated same-named one never silently re-binds). Every read reaches the
|
|
13
|
+
* consumer through `client.read.*` and every write through `runMutation`, so the
|
|
14
|
+
* two forms below (the entity predicate `inScopeItem` and the SQL fragments) are
|
|
15
|
+
* the only two places the relation is expressed.
|
|
16
|
+
*/
|
|
17
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
18
|
+
import type { AnyTask } from "../model/entities.ts";
|
|
19
|
+
import { ReferenceResolutionError } from "./queries.ts";
|
|
20
|
+
import type { ListItem, SidebarSection, TodayView } from "./views.ts";
|
|
21
|
+
import type { IsoDate } from "../model/dates.ts";
|
|
22
|
+
/** Where a scope declaration came from — surfaced as `meta.scope.source`. */
|
|
23
|
+
export type ScopeSource = "flag" | "env" | "config";
|
|
24
|
+
/**
|
|
25
|
+
* A resolved container scope: pinned at open, carried on the client. `kind`
|
|
26
|
+
* distinguishes an area jail from a project jail; `uuid`/`title` name the
|
|
27
|
+
* container; `areaUuid` is the containing area (the scope area itself for an
|
|
28
|
+
* area scope; the project's own area — or null for an area-less project — for a
|
|
29
|
+
* project scope), used to scope the `areas` view and area-ref resolution.
|
|
30
|
+
*/
|
|
31
|
+
export interface ResolvedScope {
|
|
32
|
+
kind: "area" | "project";
|
|
33
|
+
uuid: string;
|
|
34
|
+
title: string;
|
|
35
|
+
source: ScopeSource;
|
|
36
|
+
/** The relevant area context (area scope: itself; project scope: the project's area, null if area-less). */
|
|
37
|
+
areaUuid: string | null;
|
|
38
|
+
}
|
|
39
|
+
/** The `meta.scope` wire shape (a subset of {@link ResolvedScope}). */
|
|
40
|
+
export interface ScopeMeta {
|
|
41
|
+
kind: "area" | "project";
|
|
42
|
+
uuid: string;
|
|
43
|
+
title: string;
|
|
44
|
+
source: ScopeSource;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Fail-closed if a requested scope is unresolvable — `things mcp --scope <bogus>`
|
|
48
|
+
* refuses to start, a CLI invocation errors as usage — so the daemon never runs
|
|
49
|
+
* unscoped when a scope was asked for. A subclass of {@link ReferenceResolutionError}
|
|
50
|
+
* so existing usage-class handlers treat it uniformly.
|
|
51
|
+
*/
|
|
52
|
+
export declare class ScopeResolutionError extends ReferenceResolutionError {
|
|
53
|
+
constructor(message: string, ref: string);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Resolve a scope ref (uuid, uuid-prefix, or unique area/project name) to a
|
|
57
|
+
* pinned {@link ResolvedScope}. Areas win over same-named projects (mirroring
|
|
58
|
+
* `classifyShowTarget`). A to-do / heading / tag ref is a usage error — only an
|
|
59
|
+
* area or a project can be a container. Fail-closed: an unresolvable ref throws
|
|
60
|
+
* {@link ScopeResolutionError}.
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveScope(db: DatabaseSync, ref: string, source: ScopeSource): ResolvedScope;
|
|
63
|
+
/** The `meta.scope` projection of a resolved scope (drops the internal `areaUuid`). */
|
|
64
|
+
export declare function scopeMeta(scope: ResolvedScope): ScopeMeta;
|
|
65
|
+
/**
|
|
66
|
+
* The entity-level membership predicate — the SAME relation as
|
|
67
|
+
* {@link scopeMembershipSql}, for an already-shaped view row. Area scope: the
|
|
68
|
+
* row's EFFECTIVE area is the scope (its `area` Ref, which queries.ts already
|
|
69
|
+
* resolves transitively). Project scope: the row IS the project, is a direct
|
|
70
|
+
* child, or is nested under one of the project's headings.
|
|
71
|
+
*/
|
|
72
|
+
export declare function inScopeItem(item: ListItem | AnyTask, scope: ResolvedScope): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* The SQL fragment on alias `t` — the single membership source the leak-critical
|
|
75
|
+
* query paths AND the write ancestry check share. Area: the effective-area
|
|
76
|
+
* COALESCE equals the scope. Project: the row is the project, its direct child,
|
|
77
|
+
* or a heading-nested child.
|
|
78
|
+
*/
|
|
79
|
+
export declare function scopeMembershipSql(scope: ResolvedScope): {
|
|
80
|
+
where: string;
|
|
81
|
+
binds: string[];
|
|
82
|
+
};
|
|
83
|
+
/** True when the given uuid is an in-scope TMTask row (single-row leak paths, write parity). */
|
|
84
|
+
export declare function isUuidInScope(db: DatabaseSync, uuid: string, scope: ResolvedScope): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* The membership clause for {@link resolveTaskUuidPrefix} (alias `t`): the same
|
|
87
|
+
* fragment as {@link scopeMembershipSql}, so an out-of-scope uuid resolves to
|
|
88
|
+
* "not found" through the identical code path a nonexistent one does.
|
|
89
|
+
*/
|
|
90
|
+
export declare function taskMembershipClause(scope: ResolvedScope): {
|
|
91
|
+
where: string;
|
|
92
|
+
binds: string[];
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* The membership clause for {@link resolveNamedRef} over TMTask projects
|
|
96
|
+
* (UNqualified columns — that resolver aliases nothing). A project carries its
|
|
97
|
+
* area directly (no inheritance), so area scope compares `area`; project scope
|
|
98
|
+
* pins the one project uuid.
|
|
99
|
+
*/
|
|
100
|
+
export declare function namedProjectClause(scope: ResolvedScope): {
|
|
101
|
+
where: string;
|
|
102
|
+
binds: string[];
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* The membership clause for {@link resolveNamedRef} over TMArea (UNqualified).
|
|
106
|
+
* Area scope: the one scope area. Project scope: the project's own area (an
|
|
107
|
+
* area-less project has none, so nothing resolves — `0`).
|
|
108
|
+
*/
|
|
109
|
+
export declare function namedAreaClause(scope: ResolvedScope): {
|
|
110
|
+
where: string;
|
|
111
|
+
binds: string[];
|
|
112
|
+
};
|
|
113
|
+
/** Filter a flat view (upcoming/logbook/trash/search/changes) to in-scope rows. */
|
|
114
|
+
export declare function filterListByScope<T extends ListItem>(items: T[], scope: ResolvedScope): T[];
|
|
115
|
+
/**
|
|
116
|
+
* Filter the grouped sidebar catalogue (anytime/someday) to in-scope rows: each
|
|
117
|
+
* section's items are filtered and empty sections drop, so the per-block caps
|
|
118
|
+
* size the survivors.
|
|
119
|
+
*/
|
|
120
|
+
export declare function filterSectionsByScope(sections: SidebarSection[], scope: ResolvedScope): SidebarSection[];
|
|
121
|
+
/**
|
|
122
|
+
* Filter the today view to in-scope rows, RECOMPUTING the counts over the
|
|
123
|
+
* surviving OPEN members so they never count a dropped (out-of-scope) row — the
|
|
124
|
+
* same treatment `filterTodayByArea` gives the area filter.
|
|
125
|
+
*/
|
|
126
|
+
export declare function filterTodayByScope(view: TodayView, scope: ResolvedScope, todayIso: IsoDate): TodayView;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { EFFECTIVE_AREA, ReferenceResolutionError, resolveAreaUuid, resolveProjectUuid, } from "./queries.js";
|
|
2
|
+
/**
|
|
3
|
+
* Fail-closed if a requested scope is unresolvable — `things mcp --scope <bogus>`
|
|
4
|
+
* refuses to start, a CLI invocation errors as usage — so the daemon never runs
|
|
5
|
+
* unscoped when a scope was asked for. A subclass of {@link ReferenceResolutionError}
|
|
6
|
+
* so existing usage-class handlers treat it uniformly.
|
|
7
|
+
*/
|
|
8
|
+
export class ScopeResolutionError extends ReferenceResolutionError {
|
|
9
|
+
constructor(message, ref) {
|
|
10
|
+
super(message, { code: "not-found", ref });
|
|
11
|
+
this.name = "ScopeResolutionError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Resolve a scope ref (uuid, uuid-prefix, or unique area/project name) to a
|
|
16
|
+
* pinned {@link ResolvedScope}. Areas win over same-named projects (mirroring
|
|
17
|
+
* `classifyShowTarget`). A to-do / heading / tag ref is a usage error — only an
|
|
18
|
+
* area or a project can be a container. Fail-closed: an unresolvable ref throws
|
|
19
|
+
* {@link ScopeResolutionError}.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveScope(db, ref, source) {
|
|
22
|
+
// Area first (an area outranks a same-named project, like the show router).
|
|
23
|
+
try {
|
|
24
|
+
const uuid = resolveAreaUuid(db, ref, { prefixTier: false });
|
|
25
|
+
const row = db.prepare("SELECT title FROM TMArea WHERE uuid = ?").get(uuid);
|
|
26
|
+
return { kind: "area", uuid, title: row?.title ?? "", source, areaUuid: uuid };
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
// An ambiguous area name is a real conflict — surface it. A plain not-found
|
|
30
|
+
// falls through to project resolution.
|
|
31
|
+
if (err instanceof RangeError && err.message.includes("matches")) {
|
|
32
|
+
throw new ScopeResolutionError(`scope "${ref}" is ambiguous — ${err.message}`, ref);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const uuid = resolveProjectUuid(db, ref, { prefixTier: false });
|
|
37
|
+
const row = db.prepare("SELECT title, area FROM TMTask WHERE uuid = ?").get(uuid);
|
|
38
|
+
return {
|
|
39
|
+
kind: "project",
|
|
40
|
+
uuid,
|
|
41
|
+
title: row?.title ?? "",
|
|
42
|
+
source,
|
|
43
|
+
areaUuid: row?.area ?? null,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
if (err instanceof RangeError && err.message.includes("matches")) {
|
|
48
|
+
throw new ScopeResolutionError(`scope "${ref}" is ambiguous — ${err.message}`, ref);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
throw new ScopeResolutionError(`scope "${ref}" matches no area or project — a scope must name a container (only areas and ` +
|
|
52
|
+
"projects can be a container; to-dos, headings, and tags cannot)", ref);
|
|
53
|
+
}
|
|
54
|
+
/** The `meta.scope` projection of a resolved scope (drops the internal `areaUuid`). */
|
|
55
|
+
export function scopeMeta(scope) {
|
|
56
|
+
return { kind: scope.kind, uuid: scope.uuid, title: scope.title, source: scope.source };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The entity-level membership predicate — the SAME relation as
|
|
60
|
+
* {@link scopeMembershipSql}, for an already-shaped view row. Area scope: the
|
|
61
|
+
* row's EFFECTIVE area is the scope (its `area` Ref, which queries.ts already
|
|
62
|
+
* resolves transitively). Project scope: the row IS the project, is a direct
|
|
63
|
+
* child, or is nested under one of the project's headings.
|
|
64
|
+
*/
|
|
65
|
+
export function inScopeItem(item, scope) {
|
|
66
|
+
if (scope.kind === "area") {
|
|
67
|
+
const area = "area" in item ? item.area : null;
|
|
68
|
+
return area != null && area.uuid === scope.uuid;
|
|
69
|
+
}
|
|
70
|
+
const p = scope.uuid;
|
|
71
|
+
if (item.uuid === p)
|
|
72
|
+
return true;
|
|
73
|
+
const project = "project" in item ? item.project : null;
|
|
74
|
+
if (project != null && project.uuid === p)
|
|
75
|
+
return true;
|
|
76
|
+
const hp = "headingProject" in item ? item.headingProject : undefined;
|
|
77
|
+
if (hp != null && hp.uuid === p)
|
|
78
|
+
return true;
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The SQL fragment on alias `t` — the single membership source the leak-critical
|
|
83
|
+
* query paths AND the write ancestry check share. Area: the effective-area
|
|
84
|
+
* COALESCE equals the scope. Project: the row is the project, its direct child,
|
|
85
|
+
* or a heading-nested child.
|
|
86
|
+
*/
|
|
87
|
+
export function scopeMembershipSql(scope) {
|
|
88
|
+
if (scope.kind === "area") {
|
|
89
|
+
return { where: `${EFFECTIVE_AREA} = ?`, binds: [scope.uuid] };
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
where: "(t.uuid = ? OR t.project = ? OR t.heading IN " +
|
|
93
|
+
"(SELECT uuid FROM TMTask WHERE type = 2 AND project = ?))",
|
|
94
|
+
binds: [scope.uuid, scope.uuid, scope.uuid],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/** True when the given uuid is an in-scope TMTask row (single-row leak paths, write parity). */
|
|
98
|
+
export function isUuidInScope(db, uuid, scope) {
|
|
99
|
+
const mem = scopeMembershipSql(scope);
|
|
100
|
+
const row = db
|
|
101
|
+
.prepare(`SELECT 1 AS ok FROM TMTask t WHERE t.uuid = ? AND ${mem.where} LIMIT 1`)
|
|
102
|
+
.get(uuid, ...mem.binds);
|
|
103
|
+
return row !== undefined;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The membership clause for {@link resolveTaskUuidPrefix} (alias `t`): the same
|
|
107
|
+
* fragment as {@link scopeMembershipSql}, so an out-of-scope uuid resolves to
|
|
108
|
+
* "not found" through the identical code path a nonexistent one does.
|
|
109
|
+
*/
|
|
110
|
+
export function taskMembershipClause(scope) {
|
|
111
|
+
return scopeMembershipSql(scope);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The membership clause for {@link resolveNamedRef} over TMTask projects
|
|
115
|
+
* (UNqualified columns — that resolver aliases nothing). A project carries its
|
|
116
|
+
* area directly (no inheritance), so area scope compares `area`; project scope
|
|
117
|
+
* pins the one project uuid.
|
|
118
|
+
*/
|
|
119
|
+
export function namedProjectClause(scope) {
|
|
120
|
+
if (scope.kind === "area")
|
|
121
|
+
return { where: "area = ?", binds: [scope.uuid] };
|
|
122
|
+
return { where: "uuid = ?", binds: [scope.uuid] };
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The membership clause for {@link resolveNamedRef} over TMArea (UNqualified).
|
|
126
|
+
* Area scope: the one scope area. Project scope: the project's own area (an
|
|
127
|
+
* area-less project has none, so nothing resolves — `0`).
|
|
128
|
+
*/
|
|
129
|
+
export function namedAreaClause(scope) {
|
|
130
|
+
if (scope.kind === "area")
|
|
131
|
+
return { where: "uuid = ?", binds: [scope.uuid] };
|
|
132
|
+
return scope.areaUuid === null
|
|
133
|
+
? { where: "0", binds: [] }
|
|
134
|
+
: { where: "uuid = ?", binds: [scope.areaUuid] };
|
|
135
|
+
}
|
|
136
|
+
/** Filter a flat view (upcoming/logbook/trash/search/changes) to in-scope rows. */
|
|
137
|
+
export function filterListByScope(items, scope) {
|
|
138
|
+
return items.filter((i) => inScopeItem(i, scope));
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Filter the grouped sidebar catalogue (anytime/someday) to in-scope rows: each
|
|
142
|
+
* section's items are filtered and empty sections drop, so the per-block caps
|
|
143
|
+
* size the survivors.
|
|
144
|
+
*/
|
|
145
|
+
export function filterSectionsByScope(sections, scope) {
|
|
146
|
+
return sections
|
|
147
|
+
.map((s) => ({ area: s.area, items: s.items.filter((i) => inScopeItem(i, scope)) }))
|
|
148
|
+
.filter((s) => s.items.length > 0);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Filter the today view to in-scope rows, RECOMPUTING the counts over the
|
|
152
|
+
* surviving OPEN members so they never count a dropped (out-of-scope) row — the
|
|
153
|
+
* same treatment `filterTodayByArea` gives the area filter.
|
|
154
|
+
*/
|
|
155
|
+
export function filterTodayByScope(view, scope, todayIso) {
|
|
156
|
+
const items = view.items.filter((i) => inScopeItem(i, scope));
|
|
157
|
+
const open = items.filter((i) => i.status === "open");
|
|
158
|
+
const dueOrOverdue = open.filter((i) => i.deadline !== null && i.deadline <= todayIso).length;
|
|
159
|
+
return { items, counts: { dueOrOverdue, other: open.length - dueOrOverdue } };
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope.js","sourceRoot":"","sources":["../../src/read/scope.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,12 +18,9 @@ import type { ListItem } from "./views.ts";
|
|
|
18
18
|
export type MatchField = "title" | "notes" | "heading" | "checklist";
|
|
19
19
|
export interface SearchMatch {
|
|
20
20
|
item: ListItem;
|
|
21
|
+
/** The RANK field (strongest match by rank order); the displayed provenance
|
|
22
|
+
* annotation rides the item as `match` and follows its own precedence. */
|
|
21
23
|
field: MatchField;
|
|
22
|
-
/** Present when `field === "heading"`: the heading whose title matched. */
|
|
23
|
-
matchedVia?: {
|
|
24
|
-
kind: "heading";
|
|
25
|
-
title: string;
|
|
26
|
-
};
|
|
27
24
|
}
|
|
28
25
|
export declare function fieldRank(field: MatchField): number;
|
|
29
26
|
/** Containers (projects, areas) sort above to-dos. */
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The read-payload SHAPING transform: the token-economy rules R6 and R7, the
|
|
3
|
+
* universal item-DTO reshapes (R9), and the R10 lifecycle taxonomy — applied at
|
|
4
|
+
* the JSON emit boundary of the read surfaces (the CLI `--json` read envelope,
|
|
5
|
+
* src/cli/read-driver.ts, and the MCP read tool results, src/mcp/server.ts), the
|
|
6
|
+
* same boundaries omit-empty runs at. Shaping runs BEFORE omit-empty. The
|
|
7
|
+
* human-render path keeps the full, unshaped entities, so this is JSON-only.
|
|
8
|
+
*
|
|
9
|
+
* Both R6/R7 are deterministic BY VIEW KIND / SECTION — the emitter knows whether
|
|
10
|
+
* it is inside a single-container view, a lifecycle bucket, or a mixed list —
|
|
11
|
+
* never a per-item heuristic.
|
|
12
|
+
*
|
|
13
|
+
* ## R10 — the `stage` lifecycle taxonomy (every tier, every kind incl. detail)
|
|
14
|
+
* The three former wire fields `start` / `logged` / `trashed` are DELETED from
|
|
15
|
+
* every item and replaced by ONE derived `stage` ∈ `inbox | upcoming | anytime |
|
|
16
|
+
* someday | logbook | trash` (src/read/stage.ts `deriveStage`, the single pure
|
|
17
|
+
* derivation reused by the card bucketing so `stage` can never disagree with the
|
|
18
|
+
* bucket a view puts an item in). Today/evening membership is a SEPARATE
|
|
19
|
+
* presence-keyed axis — `today: true` / `evening: true` (evening implies today) —
|
|
20
|
+
* derived in the mapper with the Today view's own two-arm predicate.
|
|
21
|
+
* - `stage` is DROPPED only where the enclosing node PROVABLY states it — the R6
|
|
22
|
+
* rule (drop only what the node provably says). That is the stage-PURE flat
|
|
23
|
+
* views (inbox, `anytime`, someday, logbook, trash) and the stage-named card
|
|
24
|
+
* sub-buckets (anytime/upcoming/someday/logbook/trash, which the bucketer splits
|
|
25
|
+
* BY stage). The `anytime` catalogue is stage-PURE (R10.2): every member is an
|
|
26
|
+
* Anytime-view row (ANYTIME_SELF) — undated-active, arrived-active, or arrived
|
|
27
|
+
* someday-scheduled — and an ARRIVED dated row derives `anytime` (Upcoming is
|
|
28
|
+
* STRICTLY FUTURE, UPC1), so the field is redundant there.
|
|
29
|
+
* `stage` is KEPT on the stage-MIXED or derived surfaces: the `upcoming` view
|
|
30
|
+
* carries deadline-forecast stage-`anytime`/`someday` rows alongside its
|
|
31
|
+
* future-dated stage-`upcoming` ones — dropping `stage` there would delete
|
|
32
|
+
* non-redundant information — plus `today` (mixes upcoming + anytime-deadline
|
|
33
|
+
* rows), search, changes, the projects/areas listings, the card NODE, and
|
|
34
|
+
* detail.
|
|
35
|
+
* - the former `todaySection` field is RETIRED from the wire entirely (R10.1):
|
|
36
|
+
* `todaySection: "evening"` merely duplicated the `evening: true` marker. It
|
|
37
|
+
* remains an internal entity field (the human render and the write-verify delta
|
|
38
|
+
* still read it); shaping deletes it from the JSON copy.
|
|
39
|
+
*
|
|
40
|
+
* ## R12 — `when`, the derived TIME-AXIS position (replaces startDate + markers)
|
|
41
|
+
* Today/evening membership and the scheduled/projected date collapse onto ONE
|
|
42
|
+
* derived, presence-keyed field `when` (src/read/stage.ts `deriveWhen`): `"today"`
|
|
43
|
+
* / `"evening"` (Today-view membership, from the SAME `today`/`evening` markers the
|
|
44
|
+
* mapper stamps — never re-derived, so `when` can never disagree with the star), a
|
|
45
|
+
* FUTURE ISO date (a strictly-future scheduled row, or a template's projected next
|
|
46
|
+
* occurrence), or absent (unscheduled + not in Today; also an unprojected template
|
|
47
|
+
* and every logged/trashed row). The doctrine line: **`stage` enumerates the
|
|
48
|
+
* sidebar BUCKETS an item lives in; `when` enumerates its TIME POSITIONS (today |
|
|
49
|
+
* evening | a future date).** Someday is deliberately NOT a `when` value (it is a
|
|
50
|
+
* bucket → stage). Tier/drop rules:
|
|
51
|
+
* - the former `today`/`evening` marker KEYS are DELETED from the wire on EVERY
|
|
52
|
+
* tier — `when` carries the fact; the markers stay internal (they feed `when`).
|
|
53
|
+
* - the raw `startDate` is DELETED in COMPACT (a list needs the position, not the
|
|
54
|
+
* substrate) and KEPT in FULL/DETAIL beside `when` — different facts: `startDate`
|
|
55
|
+
* = what is stored, `when` = where it sits.
|
|
56
|
+
* - a template's `repeating.nextOccurrence` is GONE from the wire — `when` replaces
|
|
57
|
+
* it (same fact, one word); an unprojected template has no `when`.
|
|
58
|
+
* - `when` is KEPT on every row it is present on. (The read-shape doctrine
|
|
59
|
+
* flattened the card `upcoming` date-groups into the project-/area-view flat
|
|
60
|
+
* `items[]`, so no enclosing node states the position anymore.) It rides the
|
|
61
|
+
* today view's flat `items[]` (Today-proper vs This-Evening), the project-/area-
|
|
62
|
+
* view `items[]`, and the flat `upcoming`/`anytime`/`inbox`/`someday` catalogues,
|
|
63
|
+
* search, changes (a deadline-pulled row reads `when: "today"` in the mixed
|
|
64
|
+
* search/changes surfaces, informatively; note R13 re-files it to stage `anytime`
|
|
65
|
+
* and the flat inbox/someday views now EXCLUDE it — it appears in the `anytime`
|
|
66
|
+
* catalogue instead, `when: "today"` kept, stage dropped as pure).
|
|
67
|
+
*
|
|
68
|
+
* ## R13 — provisional Today members + GUI-faithful pulled-row membership
|
|
69
|
+
* BANNER1 / BANNER1b (docs/lab/banner1-research.md). Two coupled facts:
|
|
70
|
+
* - **`provisional: true`** — a presence-keyed marker on every Today member the GUI
|
|
71
|
+
* pips / counts in the "You have N new to-dos" banner: a Today member NOT yet
|
|
72
|
+
* materialized (`start != active OR startDate IS NULL`, BANNER1 L1). Derived from
|
|
73
|
+
* the SAME today/evening markers + fields the stage/`when` axes use (never
|
|
74
|
+
* re-derived). Emitted on EVERY tier, NEVER dropped (the banner is not a section,
|
|
75
|
+
* so no node implies it). Absent on non-Today rows and on materialized ones.
|
|
76
|
+
* Read-only: the app clears it by materializing the row on banner-OK, a GUI-only
|
|
77
|
+
* side effect our read cannot perform (watchers beware).
|
|
78
|
+
* - **stage `anytime` for a deadline pull** — a due-deadline pull re-files an undated
|
|
79
|
+
* Inbox/Someday row into Anytime (deriveStage step 2½, L-A). So EVERY Today member
|
|
80
|
+
* derives stage `anytime`, and the today view's flat `items[]` is stage-PURE →
|
|
81
|
+
* `stage` is DROPPED there (TODAY_ITEM_DROP) while `when` is KEPT. The flat
|
|
82
|
+
* someday/inbox views EXCLUDE pulled rows and the anytime view INCLUDES them
|
|
83
|
+
* (src/read/views.ts + predicates.ts DEADLINE_PULLED) — GUI fidelity.
|
|
84
|
+
*
|
|
85
|
+
* ## Universal item-DTO reshapes (R9 — EVERY tier, EVERY read kind incl. detail)
|
|
86
|
+
* - **checklist nesting** — flat counts → presence-keyed `checklist: {open,total}`.
|
|
87
|
+
* - **todos counts** — a project's flat leaf-action counts → presence-keyed
|
|
88
|
+
* `todos: {open, total}` (omit when total 0).
|
|
89
|
+
* - **repeating template/instance split (R11)** — the wire drops the
|
|
90
|
+
* `isTemplate`/`isInstance` discriminators; key presence carries the fact. A
|
|
91
|
+
* TEMPLATE keeps a nested `repeating: {paused?, deadlined?, rule?,
|
|
92
|
+
* latestInstance?}` — the series object (rule config + backward pointer +
|
|
93
|
+
* state flags); presence MEANS template. The forward pointer `nextOccurrence`
|
|
94
|
+
* moved to the top-level `when` (R12 — a template's projected date IS its time
|
|
95
|
+
* position); `latestInstance` is detail-only (SL1). An INSTANCE keeps a flat
|
|
96
|
+
* `instanceOf: <templateUuid>` and no `repeating`. A plain row keeps neither.
|
|
97
|
+
* See {@link reshapeRepeatingWire}.
|
|
98
|
+
* - **string tags** — `tags`/`inheritedTags` become plain arrays of names.
|
|
99
|
+
* - **one project key** — a headed item's owning project (formerly
|
|
100
|
+
* `headingProject`) is merged into `project`; `headingProject` never appears.
|
|
101
|
+
*
|
|
102
|
+
* ## R6 — no-redundant-ancestry (both tiers)
|
|
103
|
+
* project-view children drop `project`+`area` (heading-group members also drop
|
|
104
|
+
* `heading`); area-view children/project-cards drop `area`; anytime/someday
|
|
105
|
+
* section items drop `area`. Mixed lists keep every ref. (In the COMPACT tier the
|
|
106
|
+
* `heading` ref is additionally dropped everywhere — R7.)
|
|
107
|
+
*
|
|
108
|
+
* ## R7 — named detail tiers (compact | full)
|
|
109
|
+
* List contexts default to COMPACT; `detail`/`show` and `--full` / `full:true`
|
|
110
|
+
* use FULL. Compact drops `created`/`modified`, the full `notes` string (a
|
|
111
|
+
* presence-keyed `hasNotes: true` marks a row with notes), and the `heading` ref;
|
|
112
|
+
* `status` is omitted when `open`. FULL keeps them but still applies R6, the
|
|
113
|
+
* universal reshapes, and R10.
|
|
114
|
+
*/
|
|
115
|
+
import { type Stage, type When } from "./stage.ts";
|
|
116
|
+
/** The container-ref kinds whose bare title is round-trip-tested for uuid promotion. */
|
|
117
|
+
export type RefKind = "area" | "project" | "heading";
|
|
118
|
+
/**
|
|
119
|
+
* The emit-side promotion oracle: does a container ref's bare TITLE round-trip
|
|
120
|
+
* through its own resolver, in its own scope, back to THIS entity? Built by
|
|
121
|
+
* {@link makeRefPromoter} (src/read/queries.ts) over the live DB — it runs the
|
|
122
|
+
* REAL resolution path for the kind (areas = all areas; projects = the live+open
|
|
123
|
+
* write-target pool, uuid-prefix tier first; headings = within `projectUuid`)
|
|
124
|
+
* and returns true only when the sole resolution is this uuid; not-found or
|
|
125
|
+
* ambiguous is false. Memoized per (kind, title, scope) within one response
|
|
126
|
+
* emission. When a promoter is absent (a DB-less unit shaping), the default
|
|
127
|
+
* assumes every title round-trips — bare titles, no uuid siblings.
|
|
128
|
+
*/
|
|
129
|
+
export interface RefPromoter {
|
|
130
|
+
roundTrips(kind: RefKind, title: string, entityUuid: string, projectUuid?: string): boolean;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Apply the universal reshapes + R6 + R7 + R10 to a read payload for one view
|
|
134
|
+
* `kind`. `full` forces the FULL tier (R7 default-pruning off, everything else
|
|
135
|
+
* applied); an unrecognized kind passes through unchanged. The input is never
|
|
136
|
+
* mutated (shallow copies throughout), so the human-render path keeps the full
|
|
137
|
+
* entities.
|
|
138
|
+
*/
|
|
139
|
+
export declare function shapeReadPayload(kind: string, data: unknown, full: boolean, promoter?: RefPromoter): unknown;
|
|
140
|
+
/** The disambiguation-candidate kinds an error may list (a subset of the entity kinds). */
|
|
141
|
+
export type CandidateType = "to-do" | "project" | "heading" | "area" | "tag";
|
|
142
|
+
/**
|
|
143
|
+
* The ONE fixed error-candidate shape — the presence-keyed disambiguation DTO a
|
|
144
|
+
* not-found / ambiguous resolution lists under `error.detail.candidates` (and the
|
|
145
|
+
* did-you-mean fallback under the same key). Deliberately minimal and INVARIANT
|
|
146
|
+
* across request flags: `--full` / `--all` / etc. NEVER widen it, because an
|
|
147
|
+
* error payload is the most determinism-critical surface. A candidate carries
|
|
148
|
+
* ONLY material that helps a caller pick the right entity, drawn from the SAME
|
|
149
|
+
* single-source derivations the read wire uses — never the raw internal entity
|
|
150
|
+
* (no counts, no notes, no dates, no null-stuffed keys):
|
|
151
|
+
* - `uuid` / `title` — always present.
|
|
152
|
+
* - `type` — names the kind, EXCEPT to-do: absent `type` = to-do (present for
|
|
153
|
+
* `project` / `heading` / `area` / `tag`), the same convention the item wire uses.
|
|
154
|
+
* - `area` / `project` — container hint as a TITLE string, present only when set.
|
|
155
|
+
* - `stage` / `when` — the R10/R12 lifecycle words, present only for a to-do /
|
|
156
|
+
* project candidate whose source row carries the materialized lifecycle fields
|
|
157
|
+
* (a thin uuid+title resolver row carries neither — presence-keyed, so absent).
|
|
158
|
+
* A trashed / logged candidate needs no boolean: `stage` already reads `"trash"`
|
|
159
|
+
* / `"logbook"` for it — the same vocabulary the wire uses.
|
|
160
|
+
*/
|
|
161
|
+
export interface CandidateRef {
|
|
162
|
+
uuid: string;
|
|
163
|
+
title: string;
|
|
164
|
+
/** The kind — omitted for a to-do (absent `type` = to-do), present otherwise. */
|
|
165
|
+
type?: CandidateType;
|
|
166
|
+
area?: string;
|
|
167
|
+
project?: string;
|
|
168
|
+
stage?: Stage;
|
|
169
|
+
when?: When;
|
|
170
|
+
}
|
|
171
|
+
/** The fixed cap on a listed candidate array; the error `message` states the total when it overflows. */
|
|
172
|
+
export declare const CANDIDATE_CAP = 8;
|
|
173
|
+
/**
|
|
174
|
+
* Project ONE entity — a materialized to-do/project/area/heading, or a thin
|
|
175
|
+
* `{uuid, title}` resolver row — to the fixed {@link CandidateRef}. The SINGLE
|
|
176
|
+
* source every error-candidate emit flows through (the did-you-mean fallback and
|
|
177
|
+
* every not-found/ambiguous resolver), so the candidate shape can never vary by
|
|
178
|
+
* site. Reuses the wire's own {@link stageOf}/{@link whenOf} derivations — the
|
|
179
|
+
* lifecycle words are never re-derived here.
|
|
180
|
+
*/
|
|
181
|
+
export declare function candidateRef(type: CandidateType, src: unknown): CandidateRef;
|