hafez 1.3.0 → 1.4.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 +67 -9
- package/dist/apply.d.ts +30 -0
- package/dist/apply.js +582 -0
- package/dist/batch-ops.d.ts +296 -13
- package/dist/batch-ops.js +186 -62
- package/dist/cli/args.d.ts +5 -1
- package/dist/cli/args.js +6 -2
- package/dist/cli/commands.d.ts +24 -0
- package/dist/cli/commands.js +197 -115
- package/dist/cli/help-agent.js +24 -0
- package/dist/cli/index.d.ts +41 -0
- package/dist/cli/index.js +150 -130
- package/dist/cli/upgrade.d.ts +42 -0
- package/dist/cli/upgrade.js +305 -0
- package/dist/contracts.d.ts +49 -6
- package/dist/contracts.js +104 -23
- package/dist/dates.d.ts +10 -0
- package/dist/dates.js +27 -0
- package/dist/db.d.ts +27 -2
- package/dist/db.js +309 -33
- package/dist/document.d.ts +97 -88
- package/dist/document.js +512 -382
- package/dist/export-okf.js +41 -15
- package/dist/git.js +165 -48
- package/dist/index.d.ts +2 -2
- package/dist/index.js +212 -727
- package/dist/knowledge-index.d.ts +23 -1
- package/dist/knowledge-index.js +62 -6
- package/dist/merge.d.ts +1 -1
- package/dist/merge.js +96 -64
- package/dist/schema.js +4 -4
- package/dist/templates.d.ts +6 -0
- package/dist/templates.js +27 -27
- package/dist/types.d.ts +38 -11
- package/dist/vault.d.ts +18 -0
- package/dist/vault.js +50 -4
- package/package.json +1 -1
- package/skill/SKILL.md +51 -110
- package/dist/query.d.ts +0 -24
- package/dist/query.js +0 -169
package/dist/vault.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/vault.ts
|
|
2
2
|
import matter from 'gray-matter';
|
|
3
3
|
import yaml from 'js-yaml';
|
|
4
|
-
import { readFileSync, readdirSync } from 'fs';
|
|
4
|
+
import { readFileSync, readdirSync, existsSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
6
|
import { HafezError } from './types.js';
|
|
7
7
|
// Prevent gray-matter from auto-coercing YAML date strings into JS Date objects
|
|
@@ -69,15 +69,50 @@ export function resolveFilePath(vaultPath, slug, kind) {
|
|
|
69
69
|
}
|
|
70
70
|
return join(vaultPath, KIND_DIRS[kind], `${slug}.md`);
|
|
71
71
|
}
|
|
72
|
+
/** The file a slug names, or null — entities are resolved before knowledge,
|
|
73
|
+
* the one resolution order every operation sees. */
|
|
74
|
+
export function findVaultFile(vaultPath, slug) {
|
|
75
|
+
const entity = resolveFilePath(vaultPath, slug, 'entity');
|
|
76
|
+
if (existsSync(entity))
|
|
77
|
+
return entity;
|
|
78
|
+
const knowledge = resolveFilePath(vaultPath, slug, 'knowledge');
|
|
79
|
+
if (existsSync(knowledge))
|
|
80
|
+
return knowledge;
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
/** Does this slug name a vault file? An invalid slug definitionally doesn't
|
|
84
|
+
* exist — reference checks (create parent/related, link targets, validate)
|
|
85
|
+
* must report "does not exist", not crash. validate() in particular exists to
|
|
86
|
+
* REPORT malformed refs. */
|
|
87
|
+
export function vaultFileExists(vaultPath, slug) {
|
|
88
|
+
try {
|
|
89
|
+
return findVaultFile(vaultPath, slug) !== null;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
72
95
|
// Separator-agnostic: OS paths use '/' or '\' depending on platform, and a
|
|
73
96
|
// POSIX-only check silently classifies every entity as knowledge on Windows
|
|
74
97
|
// (all updates then fail on kind rules). Exported pure so win32 paths are
|
|
75
98
|
// unit-testable from any platform.
|
|
99
|
+
// The DEEPEST kind-dir occurrence wins: the in-vault dir is always deeper
|
|
100
|
+
// than any ancestor, so a vault living under a directory named 'entities'
|
|
101
|
+
// (e.g. ~/data/entities/myvault) can't shadow its knowledge/ files.
|
|
76
102
|
export function kindFromPath(filePath) {
|
|
77
|
-
|
|
103
|
+
const lastMatch = (dir) => {
|
|
104
|
+
let idx = -1;
|
|
105
|
+
for (const m of filePath.matchAll(new RegExp(`[/\\\\]${dir}[/\\\\]`, 'g')))
|
|
106
|
+
idx = m.index;
|
|
107
|
+
return idx;
|
|
108
|
+
};
|
|
109
|
+
return lastMatch(KIND_DIRS.entity) > lastMatch(KIND_DIRS.knowledge) ? 'entity' : 'knowledge';
|
|
78
110
|
}
|
|
79
|
-
|
|
80
|
-
|
|
111
|
+
/** Fields that must always be arrays when present (may be scalar in
|
|
112
|
+
* hand-edited YAML). Parsing coerces them here; merge.ts unions them on
|
|
113
|
+
* conflict — one list, so a new list-valued field cannot be normalized on
|
|
114
|
+
* read and then overwritten wholesale on merge. */
|
|
115
|
+
export const ARRAY_FIELDS = ['related', 'tags', 'domain'];
|
|
81
116
|
function normalizeFrontmatter(fm) {
|
|
82
117
|
for (const field of ARRAY_FIELDS) {
|
|
83
118
|
if (field in fm && typeof fm[field] === 'string') {
|
|
@@ -96,6 +131,17 @@ export function vaultKindFromPath(filePath) {
|
|
|
96
131
|
return 'session';
|
|
97
132
|
return kindFromPath(filePath);
|
|
98
133
|
}
|
|
134
|
+
/** Total kind classification for a vault-relative content file (anything
|
|
135
|
+
* matching VAULT_FILE_RE) — derived from KIND_DIRS, so adding a vault dir
|
|
136
|
+
* can't silently fall a new kind through to the wrong one. Throws on paths
|
|
137
|
+
* outside the vault dirs; callers gate on VAULT_FILE_RE first. */
|
|
138
|
+
export function vaultKindFromRelPath(relPath) {
|
|
139
|
+
for (const [kind, dir] of Object.entries(KIND_DIRS)) {
|
|
140
|
+
if (relPath.startsWith(`${dir}/`))
|
|
141
|
+
return kind;
|
|
142
|
+
}
|
|
143
|
+
throw new HafezError('VALIDATION_FAILED', `Not a vault content path: ${relPath}`);
|
|
144
|
+
}
|
|
99
145
|
export function parseFilePath(path) {
|
|
100
146
|
const content = readFileSync(path, 'utf-8');
|
|
101
147
|
const { data, content: body } = matter(content, MATTER_OPTIONS);
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -1,133 +1,71 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hafez
|
|
3
3
|
description: >
|
|
4
|
-
Hafez vault — personal knowledge base of captures, entities, projects,
|
|
5
|
-
|
|
6
|
-
("what am I working on?", "
|
|
7
|
-
|
|
8
|
-
start to load context for identifiable
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
Hafez vault — personal knowledge base of captures, entities, projects, and
|
|
5
|
+
knowledge notes (subtypes: insight, plan). Use when the user mentions Hafez
|
|
6
|
+
(or types /hafez), asks what's in the vault ("what am I working on?", "what's
|
|
7
|
+
stale?"), wants to create or update an entity or knowledge note, at session
|
|
8
|
+
start to load context for an identifiable topic, at session end to record a
|
|
9
|
+
digest, when a casually mentioned project name might be a vault entity, or
|
|
10
|
+
when the user wants to update the tool itself ("update hafez", "upgrade
|
|
11
|
+
hafez", "is hafez outdated").
|
|
11
12
|
---
|
|
12
13
|
|
|
13
14
|
# Hafez — Vault Operations
|
|
14
15
|
|
|
15
|
-
All operations
|
|
16
|
+
All operations go through the `hafez` CLI — it owns reads, writes, validation, and git. Vault files are never edited directly.
|
|
16
17
|
|
|
17
18
|
If `hafez` is not installed, or `hafez init` reports no vault, stop and follow the setup in the project README (github.com/shrvn-r/hafez) before using this skill. If the vault exists but is empty, run `hafez onboard` and follow what it prints.
|
|
18
19
|
|
|
19
|
-
##
|
|
20
|
+
## Situation → Command
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
Quick reference for common operations:
|
|
22
|
+
`hafez help --agent` is the complete, always-current API reference (all commands, flags, batch schema, enum values). This table covers the common situations; consult `help --agent` for anything beyond it.
|
|
24
23
|
|
|
25
24
|
| Situation | Command |
|
|
26
25
|
|-----------|---------|
|
|
27
|
-
| Read an entity | `hafez read <slug>` |
|
|
28
|
-
|
|
|
26
|
+
| Read an entity (incl. a project the user mentions) | `hafez read <slug>` |
|
|
27
|
+
| "What am I working on?" / active projects | `hafez query --filter active` |
|
|
29
28
|
| Stale items | `hafez query --filter stale` |
|
|
30
|
-
| Full-text search | `hafez search "<terms>"` |
|
|
31
|
-
| Search knowledge only | `hafez search "<terms>" --kind knowledge` |
|
|
32
|
-
| Create entity | `hafez create entity "<name>" --type project [--purpose "text"] [--description "text"] [--resource "uri"] [--brief "text"] [--add-action "task"]` |
|
|
33
|
-
| Quick capture | `hafez capture "<name>"` |
|
|
29
|
+
| Full-text search | `hafez search "<terms>"` (`--kind knowledge` to narrow) |
|
|
34
30
|
| List inbox captures | `hafez query --filter capture` |
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
31
|
+
| Children of a project | `hafez query --parent <slug>` |
|
|
32
|
+
| Related entities/knowledge | `hafez query --related-to <slug>` |
|
|
33
|
+
| New workstream | `hafez create entity "<name>" --type project [--brief "context"] [--add-action "task"]` (full flag list in `help --agent`; `--purpose` is project-only) |
|
|
34
|
+
| New plan | `hafez create knowledge "<name>" --subtype plan [--status active\|done]` (plans have Goal/Steps/Dependencies — no `--synthesis`; `--status` is plan-only) |
|
|
35
|
+
| Quick thought, no classification yet | `hafez capture "<name>"` |
|
|
39
36
|
| Promote | `hafez promote <slug> <target>` (capture→entity/project/knowledge, entity→project) |
|
|
40
|
-
|
|
|
41
|
-
| Add
|
|
42
|
-
|
|
|
43
|
-
|
|
|
37
|
+
| Set handoff context / summary / URI | `hafez update <slug> --brief "text"` / `--description "summary"` / `--resource "https://..."` |
|
|
38
|
+
| Add / complete a next action | `hafez update <slug> --add-action "task"` / `--complete-action "substring"` |
|
|
39
|
+
| Significant milestone (deployed, merged, bug found) | `hafez update <slug> --log "progress: summary" --agent claude` |
|
|
40
|
+
| Link entities | `hafez link <slug> <target> --relation related` (or `parent` when a thread belongs to a project) |
|
|
41
|
+
| Revise a knowledge insight | `hafez update <slug> --synthesis "text"` (`--insight` is an alias) |
|
|
42
|
+
| Back knowledge with evidence / a source | `hafez update <slug> --add-evidence "text"` / `--add-source "url"` |
|
|
44
43
|
| Digest session → batch | `echo '{...}' \| hafez digest \| hafez batch` (no pipes? `--file` on both) |
|
|
45
44
|
|
|
46
|
-
Note: `--insight` is an alias for `--synthesis`.
|
|
47
|
-
|
|
48
45
|
## Session Start — Context Loading
|
|
49
46
|
|
|
50
|
-
|
|
47
|
+
Load context only when a specific entity slug or topic is identifiable from the user's first message:
|
|
51
48
|
|
|
52
49
|
1. Run `hafez read <slug>` to load entity context
|
|
53
50
|
2. If the entity has a `parent`, also read the parent at frontmatter depth: `hafez read <parent> --depth frontmatter`
|
|
54
51
|
3. Search for related knowledge (`hafez search "<topic>" --kind knowledge`) and read any relevant note: `hafez read <knowledge-slug>`
|
|
55
52
|
4. Present a brief context line: "Loaded context for {name} (status: {status}, next: {next_action})"
|
|
56
53
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
## Mid-Session — Working With the Vault
|
|
60
|
-
|
|
61
|
-
| Situation | Action |
|
|
62
|
-
|-----------|--------|
|
|
63
|
-
| User asks "what am I working on?" | `hafez query --filter active` |
|
|
64
|
-
| User asks about stale items | `hafez query --filter stale` |
|
|
65
|
-
| User mentions a project by name | `hafez read <slug>` |
|
|
66
|
-
| New workstream emerges | `hafez create entity "<name>" --type project [--brief "context"] [--add-action "task"]` |
|
|
67
|
-
| Quick thought, no classification | `hafez capture "<name>"` |
|
|
68
|
-
| Promote capture to entity/project | `hafez promote <slug> entity` |
|
|
69
|
-
| Insight worth preserving | Search first (`hafez search "<topic>"`), then create or update |
|
|
70
|
-
| Entity relates to another | `hafez link <slug> <target> --relation related` |
|
|
71
|
-
| Thread belongs to a project | `hafez link <slug> <parent> --relation parent` |
|
|
72
|
-
| Knowledge needs new synthesis | `hafez update <slug> --synthesis "text"` |
|
|
73
|
-
| Knowledge backed by new evidence | `hafez update <slug> --add-evidence "text"` |
|
|
74
|
-
| Knowledge has a source to cite | `hafez update <slug> --add-source "url"` |
|
|
75
|
-
| Need children of a project | `hafez query --parent <slug>` |
|
|
76
|
-
| Need related entities/knowledge | `hafez query --related-to <slug>` |
|
|
77
|
-
| Search for something | `hafez search "<terms>"` |
|
|
78
|
-
| Set handoff context | `hafez update <slug> --brief "context"` |
|
|
79
|
-
| Set one-line description | `hafez update <slug> --description "summary"` |
|
|
80
|
-
| Set resource URI (entity) | `hafez update <slug> --resource "https://..."` |
|
|
81
|
-
| Add a next action | `hafez update <slug> --add-action "task"` |
|
|
82
|
-
| Complete a next action | `hafez update <slug> --complete-action "task"` |
|
|
83
|
-
| Rebuild stale index | `hafez index rebuild` |
|
|
84
|
-
| Pull latest from remote | `hafez sync` |
|
|
85
|
-
|
|
86
|
-
### Ingest Reflex — Before Creating New Knowledge
|
|
87
|
-
|
|
88
|
-
Before creating a new knowledge note, always check if one already exists:
|
|
89
|
-
|
|
90
|
-
```bash
|
|
91
|
-
hafez search "<topic>"
|
|
92
|
-
hafez search "<topic>" --kind knowledge
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
If a related note exists, update it rather than creating a duplicate. Use `--synthesis` to revise the main insight text, `--add-evidence` to append supporting evidence, or `--add-source` to cite a new source.
|
|
96
|
-
|
|
97
|
-
Only create a new note when no existing knowledge covers the topic.
|
|
98
|
-
|
|
99
|
-
### Wiki Links in Knowledge Notes
|
|
100
|
-
|
|
101
|
-
When writing or updating knowledge note prose (brief, synthesis, insight body), use `[[wiki links]]` to reference related entities and knowledge naturally. This makes the vault navigable as a wiki:
|
|
102
|
-
|
|
103
|
-
- Reference related projects: `[[project-slug]]`
|
|
104
|
-
- Reference other knowledge: `[[knowledge-slug]]`
|
|
105
|
-
- Reference people or areas: `[[entity-slug]]`
|
|
106
|
-
|
|
107
|
-
The CLI does not enforce link syntax — write them naturally in the prose content you pass to `--synthesis`, `--brief`, or `--insight`.
|
|
54
|
+
## Search-First Ingest
|
|
108
55
|
|
|
109
|
-
|
|
56
|
+
A new knowledge note is a last resort. When an insight emerges or a source (document, conversation, research) is being processed:
|
|
110
57
|
|
|
111
|
-
|
|
58
|
+
1. Search for existing coverage: `hafez search "<topic>"`, narrowing with `--kind knowledge`.
|
|
59
|
+
2. A note covers the topic → update it: `--synthesis` to revise the main insight, `--add-evidence` to append support, `--add-source` to cite.
|
|
60
|
+
3. Nothing covers it → create it linked from birth (`hafez create knowledge "<name>" --related slug1,slug2`), then write the body via `--synthesis`.
|
|
112
61
|
|
|
113
|
-
|
|
114
|
-
# 1. Search for existing coverage
|
|
115
|
-
hafez search "<topic>" --kind knowledge
|
|
116
|
-
|
|
117
|
-
# 2a. If exists — update it
|
|
118
|
-
hafez update <slug> --synthesis "Revised insight" --add-source "url"
|
|
119
|
-
hafez update <slug> --add-evidence "New evidence from source"
|
|
120
|
-
|
|
121
|
-
# 2b. If new — create it
|
|
122
|
-
hafez create knowledge "<name>" --related slug1,slug2
|
|
62
|
+
Write `[[wiki links]]` in any knowledge prose (`--brief`, `--synthesis`) to reference related projects, entities, and notes — the CLI does not enforce link syntax; write them naturally in the text. This is what keeps the vault navigable as a wiki.
|
|
123
63
|
|
|
124
|
-
|
|
125
|
-
hafez update <slug> --synthesis "Insight text referencing [[related-slug]]"
|
|
126
|
-
```
|
|
64
|
+
**Knowledge boundary:** before writing an insight down, ask where it belongs. Useful beyond the current session or repo → vault knowledge note. Specific to one repo's code or workflow → that repo's own docs (CLAUDE.md, README, comments), not the vault.
|
|
127
65
|
|
|
128
66
|
## Batch Operations
|
|
129
67
|
|
|
130
|
-
**Rule: Use `hafez batch` when making 2+ mutating calls.** Individual commands auto-commit after each call (and push, when the vault has a remote). Running multiple individual commands back-to-back causes git push races on synced vaults. Batch does all operations in one commit + one push.
|
|
68
|
+
**Rule: Use `hafez batch` when making 2+ mutating calls.** Individual commands auto-commit after each call (and push, when the vault has a remote). Running multiple individual commands back-to-back causes git push races on synced vaults. Batch does all operations in one commit + one push. For a single operation, individual commands are fine — they auto-sync.
|
|
131
69
|
|
|
132
70
|
```bash
|
|
133
71
|
echo '[
|
|
@@ -147,8 +85,6 @@ On shells that can't pipe stdin to native executables (Windows PowerShell 5.1),
|
|
|
147
85
|
|
|
148
86
|
**Slug contract.** Slugs derive deterministically from names: lowercase, non-alphanumeric runs become single `-` (`"Auth Refactor!"` → `auth-refactor`). Batch executes sequentially, so later ops can reference the slugs of earlier creates in the same payload — create-then-link works in one batch. Collisions hard-error, so a wrong prediction can't touch the wrong document; `hafez batch --dry-run` reports the derived slug for every create op if you want to verify first.
|
|
149
87
|
|
|
150
|
-
For a single operation, individual commands are fine — they auto-sync.
|
|
151
|
-
|
|
152
88
|
## Session End — Digest
|
|
153
89
|
|
|
154
90
|
At the end of a working session (or a natural breakpoint), record what happened:
|
|
@@ -173,24 +109,29 @@ Digest adds a session log entry to each touched entity and creates a session not
|
|
|
173
109
|
|
|
174
110
|
Digest itself is pure — it only prints batch JSON; `hafez batch` is what writes. If you are unsure the slugs exist, run `hafez digest` alone first: unknown slugs warn on stderr (they are omitted from the output). Fix `entities_touched`, regenerate, and pipe to batch **once** — re-running the full pipe after a successful batch creates a duplicate session note.
|
|
175
111
|
|
|
176
|
-
**Mid-session updates and the digest coexist.** Point-in-time updates (`--status done`, `--complete-action`, brief changes) happen as they happen —
|
|
112
|
+
**Mid-session updates and the digest coexist.** Point-in-time updates (`--status done`, `--complete-action`, brief changes) happen as they happen — digest adds the overall narrative on top. Digest builds the session summary itself, so the session's closing log entry comes from digest; reserve hand-written `--log` for significant mid-session milestones (deployed, merged, discovered a bug).
|
|
177
113
|
|
|
178
|
-
|
|
114
|
+
Session end is also when knowledge gets written: review what was learned and run Search-First Ingest on anything worth keeping. Knowledge notes are the primary durable output of sessions — not a side effect.
|
|
179
115
|
|
|
180
|
-
|
|
116
|
+
## Periodic Lint
|
|
181
117
|
|
|
182
|
-
|
|
183
|
-
1. At session start: search for knowledge relevant to the topic
|
|
184
|
-
2. During work: when insights emerge, search before creating — update existing notes first
|
|
185
|
-
3. Use `[[wiki links]]` in knowledge prose to connect related concepts
|
|
186
|
-
4. At session end: review what was learned and update or create knowledge notes
|
|
118
|
+
Run at session end or on demand:
|
|
187
119
|
|
|
188
|
-
**Periodic lint** (run at session end or on demand):
|
|
189
120
|
```bash
|
|
190
121
|
hafez validate
|
|
191
|
-
hafez query --kind knowledge --
|
|
122
|
+
hafez query --kind knowledge --sort-by last_reinforced --sort-order asc --limit 20
|
|
192
123
|
```
|
|
193
|
-
Surface orphaned notes (no related links) and stale knowledge for review or archival.
|
|
194
124
|
|
|
195
|
-
|
|
125
|
+
Surface orphaned notes (no related links) and the least-recently-reinforced knowledge for review or archival. (`--filter` is entity status — knowledge notes have none, so the query command rejects it under `--kind knowledge`.)
|
|
126
|
+
|
|
127
|
+
## Updating Hafez Itself
|
|
128
|
+
|
|
129
|
+
"update hafez" means the **tool**. `hafez update` is an entity update — a different thing entirely. Don't confuse them.
|
|
130
|
+
|
|
131
|
+
1. Run `hafez upgrade`. It checks and prints; it never installs.
|
|
132
|
+
2. Show the user the printed commands **verbatim** and wait for an explicit yes. They install code and overwrite the user's own agent instructions file — never run them unattended, and never run the tarball alternative alongside the npm install.
|
|
133
|
+
3. Run the approved steps, then re-run `hafez upgrade` to verify.
|
|
134
|
+
|
|
135
|
+
If `hafez upgrade` fails with the literal prefix `Unknown command:`, the installed hafez predates it. Install the current version, re-copy the bundled skill, then run `hafez upgrade` — the README's Updating section (github.com/shrvn-r/hafez) has the exact commands.
|
|
196
136
|
|
|
137
|
+
`hafez upgrade` also prints the current integration snippet. Compare it against the Hafez block in the user's global instructions file and offer to reconcile differences — don't rewrite their file unasked.
|
package/dist/query.d.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { QueryResult, KnowledgeQueryResult, EntityQueryOpts, KnowledgeQueryOpts, UnifiedResult } from './types.js';
|
|
2
|
-
import type { HafezIndex } from './db.js';
|
|
3
|
-
export declare function queryEntities(index: HafezIndex, opts?: EntityQueryOpts): {
|
|
4
|
-
items: QueryResult[];
|
|
5
|
-
total: number;
|
|
6
|
-
};
|
|
7
|
-
export declare function queryChildren(index: HafezIndex, parentSlug: string): {
|
|
8
|
-
items: QueryResult[];
|
|
9
|
-
total: number;
|
|
10
|
-
};
|
|
11
|
-
export declare function queryRelatedTo(index: HafezIndex, slug: string, relation?: string): {
|
|
12
|
-
items: UnifiedResult[];
|
|
13
|
-
total: number;
|
|
14
|
-
};
|
|
15
|
-
export declare function queryKnowledge(index: HafezIndex, opts?: KnowledgeQueryOpts): {
|
|
16
|
-
items: KnowledgeQueryResult[];
|
|
17
|
-
total: number;
|
|
18
|
-
};
|
|
19
|
-
export declare function queryUnified(index: HafezIndex, opts?: EntityQueryOpts & {
|
|
20
|
-
kind?: 'entity' | 'knowledge' | 'all';
|
|
21
|
-
}): {
|
|
22
|
-
items: UnifiedResult[];
|
|
23
|
-
total: number;
|
|
24
|
-
};
|
package/dist/query.js
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { HafezError } from './types.js';
|
|
2
|
-
function toQueryResult(slug, row) {
|
|
3
|
-
return {
|
|
4
|
-
slug,
|
|
5
|
-
name: row.name,
|
|
6
|
-
type: row.type,
|
|
7
|
-
status: row.status,
|
|
8
|
-
description: row.description ?? null,
|
|
9
|
-
resource: row.resource ?? null,
|
|
10
|
-
domain: row.domain ?? [],
|
|
11
|
-
last_touched: row.last_touched,
|
|
12
|
-
next_action: row.next_action ?? null,
|
|
13
|
-
next_action_count: row.next_action_count ?? 0,
|
|
14
|
-
parent: row.parent ?? null,
|
|
15
|
-
related: row.related ?? [],
|
|
16
|
-
staleness_days: row.staleness_days ?? null,
|
|
17
|
-
tags: row.tags ?? [],
|
|
18
|
-
brief: row.brief ?? null,
|
|
19
|
-
session_log_count: row.session_log_count ?? 0,
|
|
20
|
-
last_session_date: row.last_session_date ?? null,
|
|
21
|
-
last_session_type: row.last_session_type ?? null,
|
|
22
|
-
last_session_summary: row.last_session_summary ?? null,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
function toKnowledgeResult(slug, row) {
|
|
26
|
-
return {
|
|
27
|
-
slug,
|
|
28
|
-
name: row.name,
|
|
29
|
-
description: row.description ?? null,
|
|
30
|
-
domain: row.domain ?? [],
|
|
31
|
-
confidence: row.confidence ?? 'observation',
|
|
32
|
-
related: row.related ?? [],
|
|
33
|
-
reinforcement_count: row.reinforcement_count ?? 0,
|
|
34
|
-
last_reinforced: row.last_reinforced ?? null,
|
|
35
|
-
last_touched: row.last_touched ?? null,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
const STATUS_FILTERS = new Set(['active', 'paused', 'done']);
|
|
39
|
-
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
40
|
-
function validateDateOpts(opts) {
|
|
41
|
-
const fields = ['since', 'before', 'createdSince', 'createdBefore'];
|
|
42
|
-
for (const f of fields) {
|
|
43
|
-
if (opts?.[f] !== undefined && !ISO_DATE.test(opts[f])) {
|
|
44
|
-
throw new HafezError('VALIDATION_FAILED', `Invalid date format for ${f}: "${opts[f]}". Must be YYYY-MM-DD.`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
export function queryEntities(index, opts) {
|
|
49
|
-
index.syncIfStale();
|
|
50
|
-
validateDateOpts(opts);
|
|
51
|
-
const qopts = { kind: 'entity' };
|
|
52
|
-
if (opts?.filter && STATUS_FILTERS.has(opts.filter))
|
|
53
|
-
qopts.status = opts.filter;
|
|
54
|
-
if (opts?.filter === 'stale')
|
|
55
|
-
qopts.filter = 'stale';
|
|
56
|
-
if (opts?.filter === 'capture')
|
|
57
|
-
qopts.filter = 'capture';
|
|
58
|
-
if (opts?.type)
|
|
59
|
-
qopts.type = opts.type;
|
|
60
|
-
if (opts?.parent)
|
|
61
|
-
qopts.parent = opts.parent;
|
|
62
|
-
if (opts?.domain)
|
|
63
|
-
qopts.domain = opts.domain;
|
|
64
|
-
if (opts?.since)
|
|
65
|
-
qopts.since = opts.since;
|
|
66
|
-
if (opts?.before)
|
|
67
|
-
qopts.before = opts.before;
|
|
68
|
-
if (opts?.createdSince)
|
|
69
|
-
qopts.createdSince = opts.createdSince;
|
|
70
|
-
if (opts?.createdBefore)
|
|
71
|
-
qopts.createdBefore = opts.createdBefore;
|
|
72
|
-
if (opts?.tags)
|
|
73
|
-
qopts.tags = opts.tags;
|
|
74
|
-
if (opts?.sort_by)
|
|
75
|
-
qopts.sort_by = opts.sort_by;
|
|
76
|
-
if (opts?.sort_order)
|
|
77
|
-
qopts.sort_order = opts.sort_order;
|
|
78
|
-
if (opts?.limit != null)
|
|
79
|
-
qopts.limit = opts.limit;
|
|
80
|
-
if (opts?.offset != null)
|
|
81
|
-
qopts.offset = opts.offset;
|
|
82
|
-
const { items, total } = index.queryItems(qopts);
|
|
83
|
-
return { items: items.map(i => toQueryResult(i.slug, i)), total };
|
|
84
|
-
}
|
|
85
|
-
export function queryChildren(index, parentSlug) {
|
|
86
|
-
return queryEntities(index, { parent: parentSlug });
|
|
87
|
-
}
|
|
88
|
-
export function queryRelatedTo(index, slug, relation) {
|
|
89
|
-
index.syncIfStale();
|
|
90
|
-
const { items, total } = index.queryItems({ relatedTo: slug, relation });
|
|
91
|
-
return {
|
|
92
|
-
items: items.map(i => i.kind === 'entity'
|
|
93
|
-
? { ...toQueryResult(i.slug, i), kind: 'entity' }
|
|
94
|
-
: { ...toKnowledgeResult(i.slug, i), kind: 'knowledge' }),
|
|
95
|
-
total,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
export function queryKnowledge(index, opts) {
|
|
99
|
-
index.syncIfStale();
|
|
100
|
-
const qopts = { kind: 'knowledge' };
|
|
101
|
-
if (opts?.domain)
|
|
102
|
-
qopts.domain = opts.domain;
|
|
103
|
-
if (opts?.confidence)
|
|
104
|
-
qopts.confidence = opts.confidence;
|
|
105
|
-
if (opts?.subtype)
|
|
106
|
-
qopts.subtype = opts.subtype;
|
|
107
|
-
if (opts?.tags)
|
|
108
|
-
qopts.tags = opts.tags;
|
|
109
|
-
if (opts?.sort_by)
|
|
110
|
-
qopts.sort_by = opts.sort_by;
|
|
111
|
-
if (opts?.sort_order)
|
|
112
|
-
qopts.sort_order = opts.sort_order;
|
|
113
|
-
if (opts?.limit != null)
|
|
114
|
-
qopts.limit = opts.limit;
|
|
115
|
-
if (opts?.offset != null)
|
|
116
|
-
qopts.offset = opts.offset;
|
|
117
|
-
const { items, total } = index.queryItems(qopts);
|
|
118
|
-
return { items: items.map(i => toKnowledgeResult(i.slug, i)), total };
|
|
119
|
-
}
|
|
120
|
-
export function queryUnified(index, opts) {
|
|
121
|
-
index.syncIfStale();
|
|
122
|
-
validateDateOpts(opts);
|
|
123
|
-
const qopts = {};
|
|
124
|
-
if (opts?.kind && opts.kind !== 'all')
|
|
125
|
-
qopts.kind = opts.kind;
|
|
126
|
-
// When kind is 'all' or unset, no kind filter — both entities and knowledge are returned
|
|
127
|
-
if (opts?.filter && STATUS_FILTERS.has(opts.filter))
|
|
128
|
-
qopts.status = opts.filter;
|
|
129
|
-
if (opts?.filter === 'stale')
|
|
130
|
-
qopts.filter = 'stale';
|
|
131
|
-
if (opts?.filter === 'capture')
|
|
132
|
-
qopts.filter = 'capture';
|
|
133
|
-
if (opts?.type)
|
|
134
|
-
qopts.type = opts.type;
|
|
135
|
-
if (opts?.parent)
|
|
136
|
-
qopts.parent = opts.parent;
|
|
137
|
-
if (opts?.domain)
|
|
138
|
-
qopts.domain = opts.domain;
|
|
139
|
-
if (opts?.confidence)
|
|
140
|
-
qopts.confidence = opts.confidence;
|
|
141
|
-
if (opts?.since)
|
|
142
|
-
qopts.since = opts.since;
|
|
143
|
-
if (opts?.before)
|
|
144
|
-
qopts.before = opts.before;
|
|
145
|
-
if (opts?.createdSince)
|
|
146
|
-
qopts.createdSince = opts.createdSince;
|
|
147
|
-
if (opts?.createdBefore)
|
|
148
|
-
qopts.createdBefore = opts.createdBefore;
|
|
149
|
-
if (opts?.tags)
|
|
150
|
-
qopts.tags = opts.tags;
|
|
151
|
-
if (opts?.sort_by)
|
|
152
|
-
qopts.sort_by = opts.sort_by;
|
|
153
|
-
if (opts?.sort_order)
|
|
154
|
-
qopts.sort_order = opts.sort_order;
|
|
155
|
-
if (opts?.limit !== undefined)
|
|
156
|
-
qopts.limit = opts.limit;
|
|
157
|
-
if (opts?.offset !== undefined)
|
|
158
|
-
qopts.offset = opts.offset;
|
|
159
|
-
const result = index.queryItems(qopts);
|
|
160
|
-
const items = result.items.map(i => {
|
|
161
|
-
if (i.kind === 'entity') {
|
|
162
|
-
return { ...toQueryResult(i.slug, i), kind: 'entity' };
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
return { ...toKnowledgeResult(i.slug, i), kind: 'knowledge' };
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
return { items, total: result.total };
|
|
169
|
-
}
|