hafez 1.2.0 → 1.3.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/dist/batch-ops.d.ts +187 -0
- package/dist/batch-ops.js +322 -0
- package/dist/cli/args.d.ts +15 -0
- package/dist/cli/args.js +49 -0
- package/dist/cli/batch-errors.js +6 -3
- package/dist/cli/commands.d.ts +0 -108
- package/dist/cli/commands.js +154 -252
- package/dist/cli/help-agent.d.ts +0 -5
- package/dist/cli/help-agent.js +20 -5
- package/dist/cli/index.js +45 -65
- package/dist/cli/resolve-vault.js +2 -1
- package/dist/cli/schema-introspect.d.ts +1 -1
- package/dist/cli/schema-introspect.js +6 -22
- package/dist/cli.js +0 -0
- package/dist/contracts.d.ts +6 -0
- package/dist/contracts.js +8 -2
- package/dist/db.js +14 -28
- package/dist/digest.js +2 -0
- package/dist/document.d.ts +108 -0
- package/dist/{sections.js → document.js} +173 -22
- package/dist/export-okf.js +10 -22
- package/dist/git.d.ts +7 -8
- package/dist/git.js +39 -21
- package/dist/index.d.ts +3 -4
- package/dist/index.js +419 -517
- package/dist/knowledge-index.js +9 -7
- package/dist/merge.js +13 -25
- package/dist/query.d.ts +1 -1
- package/dist/query.js +3 -1
- package/dist/types.d.ts +48 -1
- package/dist/vault.d.ts +33 -6
- package/dist/vault.js +65 -8
- package/package.json +2 -2
- package/dist/parser.d.ts +0 -18
- package/dist/parser.js +0 -68
- package/dist/sections.d.ts +0 -54
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { BatchOperation, CreateEntityFields, CreateKnowledgeFields, SessionLogEntry, UpdateFields } from './types.js';
|
|
3
|
+
export declare const SessionLogSchema: z.ZodObject<{
|
|
4
|
+
type: z.ZodEnum<{
|
|
5
|
+
[x: string]: string;
|
|
6
|
+
}>;
|
|
7
|
+
summary: z.ZodString;
|
|
8
|
+
body: z.ZodOptional<z.ZodString>;
|
|
9
|
+
agent: z.ZodString;
|
|
10
|
+
}, z.core.$strict>;
|
|
11
|
+
export declare const UpdateFieldsSchema: z.ZodObject<{
|
|
12
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
13
|
+
[x: string]: string;
|
|
14
|
+
}>>;
|
|
15
|
+
current_state: z.ZodOptional<z.ZodString>;
|
|
16
|
+
session_log: z.ZodOptional<z.ZodObject<{
|
|
17
|
+
type: z.ZodEnum<{
|
|
18
|
+
[x: string]: string;
|
|
19
|
+
}>;
|
|
20
|
+
summary: z.ZodString;
|
|
21
|
+
body: z.ZodOptional<z.ZodString>;
|
|
22
|
+
agent: z.ZodString;
|
|
23
|
+
}, z.core.$strict>>;
|
|
24
|
+
brief: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
25
|
+
add_action: z.ZodOptional<z.ZodString>;
|
|
26
|
+
add_actions: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
27
|
+
complete_action: z.ZodOptional<z.ZodString>;
|
|
28
|
+
remove_action: z.ZodOptional<z.ZodString>;
|
|
29
|
+
clear_actions: z.ZodOptional<z.ZodBoolean>;
|
|
30
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
31
|
+
resource: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
32
|
+
domain: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
33
|
+
confidence: z.ZodOptional<z.ZodEnum<{
|
|
34
|
+
[x: string]: string;
|
|
35
|
+
}>>;
|
|
36
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
37
|
+
related: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
38
|
+
synthesis: z.ZodOptional<z.ZodString>;
|
|
39
|
+
insight: z.ZodOptional<z.ZodString>;
|
|
40
|
+
add_evidence: z.ZodOptional<z.ZodString>;
|
|
41
|
+
add_source: z.ZodOptional<z.ZodString>;
|
|
42
|
+
}, z.core.$strict>;
|
|
43
|
+
export declare const CreateEntityFieldsSchema: z.ZodObject<{
|
|
44
|
+
type: z.ZodEnum<{
|
|
45
|
+
[x: string]: string;
|
|
46
|
+
}>;
|
|
47
|
+
purpose: z.ZodOptional<z.ZodString>;
|
|
48
|
+
description: z.ZodOptional<z.ZodString>;
|
|
49
|
+
resource: z.ZodOptional<z.ZodString>;
|
|
50
|
+
domain: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
51
|
+
parent: z.ZodOptional<z.ZodString>;
|
|
52
|
+
related: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
53
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
54
|
+
brief: z.ZodOptional<z.ZodString>;
|
|
55
|
+
add_action: z.ZodOptional<z.ZodString>;
|
|
56
|
+
add_actions: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
57
|
+
}, z.core.$strict>;
|
|
58
|
+
export declare const CreateKnowledgeFieldsSchema: z.ZodObject<{
|
|
59
|
+
subtype: z.ZodOptional<z.ZodEnum<{
|
|
60
|
+
[x: string]: string;
|
|
61
|
+
}>>;
|
|
62
|
+
synthesis: z.ZodOptional<z.ZodString>;
|
|
63
|
+
insight: z.ZodOptional<z.ZodString>;
|
|
64
|
+
description: z.ZodOptional<z.ZodString>;
|
|
65
|
+
domain: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
66
|
+
related: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
67
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
68
|
+
'session-date': z.ZodOptional<z.ZodString>;
|
|
69
|
+
}, z.core.$strict>;
|
|
70
|
+
export declare const CreateSessionFieldsSchema: z.ZodObject<{
|
|
71
|
+
synthesis: z.ZodOptional<z.ZodString>;
|
|
72
|
+
insight: z.ZodOptional<z.ZodString>;
|
|
73
|
+
related: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
74
|
+
'session-date': z.ZodOptional<z.ZodString>;
|
|
75
|
+
}, z.core.$strict>;
|
|
76
|
+
/** What `digest` may put in a session-create op — typechecked against the
|
|
77
|
+
* schema so the two can never silently diverge again. */
|
|
78
|
+
export type CreateSessionFieldsInput = z.infer<typeof CreateSessionFieldsSchema>;
|
|
79
|
+
export declare const BatchOperationSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
80
|
+
op: z.ZodLiteral<"update">;
|
|
81
|
+
slug: z.ZodString;
|
|
82
|
+
fields: z.ZodObject<{
|
|
83
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
84
|
+
[x: string]: string;
|
|
85
|
+
}>>;
|
|
86
|
+
current_state: z.ZodOptional<z.ZodString>;
|
|
87
|
+
session_log: z.ZodOptional<z.ZodObject<{
|
|
88
|
+
type: z.ZodEnum<{
|
|
89
|
+
[x: string]: string;
|
|
90
|
+
}>;
|
|
91
|
+
summary: z.ZodString;
|
|
92
|
+
body: z.ZodOptional<z.ZodString>;
|
|
93
|
+
agent: z.ZodString;
|
|
94
|
+
}, z.core.$strict>>;
|
|
95
|
+
brief: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
96
|
+
add_action: z.ZodOptional<z.ZodString>;
|
|
97
|
+
add_actions: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
98
|
+
complete_action: z.ZodOptional<z.ZodString>;
|
|
99
|
+
remove_action: z.ZodOptional<z.ZodString>;
|
|
100
|
+
clear_actions: z.ZodOptional<z.ZodBoolean>;
|
|
101
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
102
|
+
resource: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
103
|
+
domain: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
104
|
+
confidence: z.ZodOptional<z.ZodEnum<{
|
|
105
|
+
[x: string]: string;
|
|
106
|
+
}>>;
|
|
107
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
108
|
+
related: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
109
|
+
synthesis: z.ZodOptional<z.ZodString>;
|
|
110
|
+
insight: z.ZodOptional<z.ZodString>;
|
|
111
|
+
add_evidence: z.ZodOptional<z.ZodString>;
|
|
112
|
+
add_source: z.ZodOptional<z.ZodString>;
|
|
113
|
+
}, z.core.$strict>;
|
|
114
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
115
|
+
op: z.ZodLiteral<"create">;
|
|
116
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
117
|
+
op: z.ZodLiteral<"capture">;
|
|
118
|
+
name: z.ZodString;
|
|
119
|
+
notes: z.ZodOptional<z.ZodString>;
|
|
120
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
121
|
+
op: z.ZodLiteral<"link">;
|
|
122
|
+
slug: z.ZodString;
|
|
123
|
+
target: z.ZodString;
|
|
124
|
+
relation: z.ZodEnum<{
|
|
125
|
+
parent: "parent";
|
|
126
|
+
related: "related";
|
|
127
|
+
}>;
|
|
128
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
129
|
+
op: z.ZodLiteral<"unlink">;
|
|
130
|
+
slug: z.ZodString;
|
|
131
|
+
target: z.ZodString;
|
|
132
|
+
relation: z.ZodEnum<{
|
|
133
|
+
parent: "parent";
|
|
134
|
+
related: "related";
|
|
135
|
+
}>;
|
|
136
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
137
|
+
op: z.ZodLiteral<"promote">;
|
|
138
|
+
slug: z.ZodString;
|
|
139
|
+
target: z.ZodEnum<{
|
|
140
|
+
entity: "entity";
|
|
141
|
+
project: "project";
|
|
142
|
+
knowledge: "knowledge";
|
|
143
|
+
}>;
|
|
144
|
+
}, z.core.$strict>], "op">;
|
|
145
|
+
/** Vault context handed to each spec's semantic check by validateBatchCore.
|
|
146
|
+
* Simulation-aware: creates earlier in the batch count as existing. */
|
|
147
|
+
export interface OpCheckContext {
|
|
148
|
+
resolve(slug: string): {
|
|
149
|
+
kind: 'entity' | 'knowledge';
|
|
150
|
+
type: string;
|
|
151
|
+
} | null;
|
|
152
|
+
existsAnywhere(slug: string): boolean;
|
|
153
|
+
sessionExists(slug: string): boolean;
|
|
154
|
+
simulate(slug: string, info: {
|
|
155
|
+
kind: 'entity' | 'knowledge';
|
|
156
|
+
type: string;
|
|
157
|
+
}): void;
|
|
158
|
+
simulateSession(slug: string): void;
|
|
159
|
+
validateKindFields(kind: 'entity' | 'knowledge', fields: UpdateFields): void;
|
|
160
|
+
validateSessionLogEntry(entry: SessionLogEntry): string[];
|
|
161
|
+
buildEntityFrontmatter(name: string, fields: CreateEntityFields): void;
|
|
162
|
+
buildKnowledgeFrontmatter(name: string, fields: CreateKnowledgeFields): void;
|
|
163
|
+
}
|
|
164
|
+
export interface OpCheckResult {
|
|
165
|
+
slug: string;
|
|
166
|
+
errors: string[];
|
|
167
|
+
created?: boolean;
|
|
168
|
+
warning?: string;
|
|
169
|
+
}
|
|
170
|
+
export interface OpSpec {
|
|
171
|
+
/** Stable identifier used by `schema <name>` and the op catalog. */
|
|
172
|
+
name: string;
|
|
173
|
+
op: BatchOperation['op'];
|
|
174
|
+
kind?: 'entity' | 'knowledge' | 'session';
|
|
175
|
+
schema: z.ZodType;
|
|
176
|
+
description: string;
|
|
177
|
+
/** Optional fields force-included in the generated example payload. */
|
|
178
|
+
exampleInclude: string[];
|
|
179
|
+
check(op: any, ctx: OpCheckContext): OpCheckResult;
|
|
180
|
+
}
|
|
181
|
+
export declare const OP_SPECS: OpSpec[];
|
|
182
|
+
/** Find the spec for a (shape-valid) operation. */
|
|
183
|
+
export declare function specFor(op: BatchOperation): OpSpec;
|
|
184
|
+
/** Op names for error text ("expected one of […]") — derived, never hardcoded. */
|
|
185
|
+
export declare function validOpNames(): string[];
|
|
186
|
+
/** Create kinds for error text — derived from the table. */
|
|
187
|
+
export declare function validCreateKinds(): string[];
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
// src/batch-ops.ts
|
|
2
|
+
// The Op Spec (see CONTEXT.md): one table defining every batch operation —
|
|
3
|
+
// shape (Zod), semantic check, description, and example hints — from which
|
|
4
|
+
// validation, error text, and schema introspection all derive. Adding an op
|
|
5
|
+
// means adding one entry here.
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { ENTITY_TYPES, ENTITY_STATUSES, SESSION_LOG_TYPES, CONFIDENCE_LEVELS, VALID_SUBTYPES, getContract, } from './contracts.js';
|
|
8
|
+
import { slugify } from './vault.js';
|
|
9
|
+
import { HafezError } from './types.js';
|
|
10
|
+
// --- Shapes ---
|
|
11
|
+
// All enums are built from contracts.ts tuples so help-renderer, file validators,
|
|
12
|
+
// and CLI-input validator share a single source of truth.
|
|
13
|
+
// Every z.object(...) is .strict() — unknown keys become `unrecognized_keys` issues
|
|
14
|
+
// (not silently dropped), which is load-bearing for the error-formatter's did-you-mean
|
|
15
|
+
// hints and for catching typos like `{"kind": "parent"}` on a link op.
|
|
16
|
+
export const SessionLogSchema = z.object({
|
|
17
|
+
type: z.enum([...SESSION_LOG_TYPES]),
|
|
18
|
+
summary: z.string(),
|
|
19
|
+
body: z.string().optional(),
|
|
20
|
+
agent: z.string(),
|
|
21
|
+
}).strict();
|
|
22
|
+
export const UpdateFieldsSchema = z.object({
|
|
23
|
+
status: z.enum([...ENTITY_STATUSES]).optional(),
|
|
24
|
+
current_state: z.string().optional(),
|
|
25
|
+
session_log: SessionLogSchema.optional(),
|
|
26
|
+
brief: z.string().nullable().optional(),
|
|
27
|
+
add_action: z.string().optional(),
|
|
28
|
+
add_actions: z.array(z.string()).optional(),
|
|
29
|
+
complete_action: z.string().optional(),
|
|
30
|
+
remove_action: z.string().optional(),
|
|
31
|
+
clear_actions: z.boolean().optional(),
|
|
32
|
+
// Knowledge metadata
|
|
33
|
+
description: z.string().nullable().optional(),
|
|
34
|
+
resource: z.string().nullable().optional(),
|
|
35
|
+
domain: z.array(z.string()).optional(),
|
|
36
|
+
confidence: z.enum([...CONFIDENCE_LEVELS]).optional(),
|
|
37
|
+
tags: z.array(z.string()).optional(),
|
|
38
|
+
related: z.array(z.string()).optional(),
|
|
39
|
+
synthesis: z.string().optional(),
|
|
40
|
+
insight: z.string().optional(), // alias for synthesis (normalized in parseBatchInput)
|
|
41
|
+
add_evidence: z.string().optional(),
|
|
42
|
+
add_source: z.string().optional(),
|
|
43
|
+
}).strict();
|
|
44
|
+
export const CreateEntityFieldsSchema = z.object({
|
|
45
|
+
type: z.enum([...ENTITY_TYPES]),
|
|
46
|
+
purpose: z.string().optional(),
|
|
47
|
+
description: z.string().optional(),
|
|
48
|
+
resource: z.string().optional(),
|
|
49
|
+
domain: z.array(z.string()).optional(),
|
|
50
|
+
parent: z.string().optional(),
|
|
51
|
+
related: z.array(z.string()).optional(),
|
|
52
|
+
tags: z.array(z.string()).optional(),
|
|
53
|
+
brief: z.string().optional(),
|
|
54
|
+
add_action: z.string().optional(),
|
|
55
|
+
add_actions: z.array(z.string()).optional(),
|
|
56
|
+
}).strict();
|
|
57
|
+
export const CreateKnowledgeFieldsSchema = z.object({
|
|
58
|
+
subtype: z.enum([...VALID_SUBTYPES]).optional(),
|
|
59
|
+
synthesis: z.string().optional(),
|
|
60
|
+
insight: z.string().optional(),
|
|
61
|
+
description: z.string().optional(),
|
|
62
|
+
domain: z.array(z.string()).optional(),
|
|
63
|
+
related: z.array(z.string()).optional(),
|
|
64
|
+
tags: z.array(z.string()).optional(),
|
|
65
|
+
'session-date': z.string().optional(),
|
|
66
|
+
}).strict();
|
|
67
|
+
export const CreateSessionFieldsSchema = z.object({
|
|
68
|
+
synthesis: z.string().optional(),
|
|
69
|
+
insight: z.string().optional(),
|
|
70
|
+
related: z.array(z.string()).optional(),
|
|
71
|
+
'session-date': z.string().optional(),
|
|
72
|
+
}).strict();
|
|
73
|
+
const CreateEntityBranch = z.object({
|
|
74
|
+
op: z.literal('create'),
|
|
75
|
+
kind: z.literal('entity'),
|
|
76
|
+
name: z.string(),
|
|
77
|
+
fields: CreateEntityFieldsSchema,
|
|
78
|
+
}).strict();
|
|
79
|
+
const CreateKnowledgeBranch = z.object({
|
|
80
|
+
op: z.literal('create'),
|
|
81
|
+
kind: z.literal('knowledge'),
|
|
82
|
+
name: z.string(),
|
|
83
|
+
fields: CreateKnowledgeFieldsSchema.optional(),
|
|
84
|
+
}).strict();
|
|
85
|
+
const CreateSessionBranch = z.object({
|
|
86
|
+
op: z.literal('create'),
|
|
87
|
+
kind: z.literal('session'),
|
|
88
|
+
name: z.string(),
|
|
89
|
+
fields: CreateSessionFieldsSchema.optional(),
|
|
90
|
+
}).strict();
|
|
91
|
+
const UpdateBranch = z.object({
|
|
92
|
+
op: z.literal('update'),
|
|
93
|
+
slug: z.string(),
|
|
94
|
+
fields: UpdateFieldsSchema,
|
|
95
|
+
}).strict();
|
|
96
|
+
const CaptureBranch = z.object({
|
|
97
|
+
op: z.literal('capture'),
|
|
98
|
+
name: z.string(),
|
|
99
|
+
notes: z.string().optional(),
|
|
100
|
+
}).strict();
|
|
101
|
+
const LinkBranch = z.object({
|
|
102
|
+
op: z.literal('link'),
|
|
103
|
+
slug: z.string(),
|
|
104
|
+
target: z.string(),
|
|
105
|
+
relation: z.enum(['parent', 'related']),
|
|
106
|
+
}).strict();
|
|
107
|
+
const UnlinkBranch = z.object({
|
|
108
|
+
op: z.literal('unlink'),
|
|
109
|
+
slug: z.string(),
|
|
110
|
+
target: z.string(),
|
|
111
|
+
relation: z.enum(['parent', 'related']),
|
|
112
|
+
}).strict();
|
|
113
|
+
const PromoteBranch = z.object({
|
|
114
|
+
op: z.literal('promote'),
|
|
115
|
+
slug: z.string(),
|
|
116
|
+
target: z.enum(['entity', 'project', 'knowledge']),
|
|
117
|
+
}).strict();
|
|
118
|
+
// Nested discriminated union: outer on `op`, inner on `kind` for create branch.
|
|
119
|
+
// A flat `discriminatedUnion('op', [...])` cannot work because three branches share
|
|
120
|
+
// op=create — Zod requires unique discriminator values per branch and throws at
|
|
121
|
+
// schema construction time if violated.
|
|
122
|
+
const CreateBranch = z.discriminatedUnion('kind', [
|
|
123
|
+
CreateEntityBranch,
|
|
124
|
+
CreateKnowledgeBranch,
|
|
125
|
+
CreateSessionBranch,
|
|
126
|
+
]);
|
|
127
|
+
export const BatchOperationSchema = z.discriminatedUnion('op', [
|
|
128
|
+
UpdateBranch,
|
|
129
|
+
// create is a nested discriminatedUnion on `kind` — see CreateBranch above.
|
|
130
|
+
// discriminatedUnion accepts a ZodDiscriminatedUnion as a branch via option-flattening.
|
|
131
|
+
CreateBranch,
|
|
132
|
+
CaptureBranch,
|
|
133
|
+
LinkBranch,
|
|
134
|
+
UnlinkBranch,
|
|
135
|
+
PromoteBranch,
|
|
136
|
+
]);
|
|
137
|
+
function pushErr(errors, err) {
|
|
138
|
+
const e = err;
|
|
139
|
+
errors.push(e.details?.length ? `${e.message}: ${e.details.join('; ')}` : e.message);
|
|
140
|
+
}
|
|
141
|
+
export const OP_SPECS = [
|
|
142
|
+
{
|
|
143
|
+
name: 'update',
|
|
144
|
+
op: 'update',
|
|
145
|
+
schema: UpdateBranch,
|
|
146
|
+
description: 'Update an entity or knowledge note (status, brief, next actions, session log, synthesis, …).',
|
|
147
|
+
exampleInclude: ['status'],
|
|
148
|
+
check(op, ctx) {
|
|
149
|
+
const errors = [];
|
|
150
|
+
const target = ctx.resolve(op.slug);
|
|
151
|
+
if (!target)
|
|
152
|
+
errors.push(`'${op.slug}' not found`);
|
|
153
|
+
else {
|
|
154
|
+
try {
|
|
155
|
+
ctx.validateKindFields(target.kind, op.fields);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
pushErr(errors, err);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (op.fields.session_log) {
|
|
162
|
+
const logErrors = ctx.validateSessionLogEntry(op.fields.session_log);
|
|
163
|
+
if (logErrors.length > 0)
|
|
164
|
+
errors.push(`Invalid session log entry: ${logErrors.join('; ')}`);
|
|
165
|
+
}
|
|
166
|
+
if (op.fields.related) {
|
|
167
|
+
for (const r of op.fields.related) {
|
|
168
|
+
if (!ctx.existsAnywhere(r))
|
|
169
|
+
errors.push(`Related slug '${r}' does not exist`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return { slug: op.slug, errors };
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: 'create-entity',
|
|
177
|
+
op: 'create',
|
|
178
|
+
kind: 'entity',
|
|
179
|
+
schema: CreateEntityBranch,
|
|
180
|
+
description: 'Create a new entity (capture / entity / project).',
|
|
181
|
+
exampleInclude: ['type'],
|
|
182
|
+
check(op, ctx) {
|
|
183
|
+
const errors = [];
|
|
184
|
+
const slug = slugify(op.name);
|
|
185
|
+
if (ctx.existsAnywhere(slug))
|
|
186
|
+
errors.push(`Slug '${slug}' already exists`);
|
|
187
|
+
try {
|
|
188
|
+
ctx.buildEntityFrontmatter(op.name, op.fields);
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
pushErr(errors, err);
|
|
192
|
+
}
|
|
193
|
+
ctx.simulate(slug, { kind: 'entity', type: op.fields.type });
|
|
194
|
+
return { slug, errors, created: errors.length === 0 };
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: 'create-knowledge',
|
|
199
|
+
op: 'create',
|
|
200
|
+
kind: 'knowledge',
|
|
201
|
+
schema: CreateKnowledgeBranch,
|
|
202
|
+
description: 'Create a new knowledge note (insight / plan subtype).',
|
|
203
|
+
exampleInclude: ['subtype', 'synthesis'],
|
|
204
|
+
check(op, ctx) {
|
|
205
|
+
const errors = [];
|
|
206
|
+
const slug = slugify(op.name);
|
|
207
|
+
if (ctx.existsAnywhere(slug))
|
|
208
|
+
errors.push(`Slug '${slug}' already exists`);
|
|
209
|
+
try {
|
|
210
|
+
ctx.buildKnowledgeFrontmatter(op.name, (op.fields || {}));
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
pushErr(errors, err);
|
|
214
|
+
}
|
|
215
|
+
ctx.simulate(slug, { kind: 'knowledge', type: 'knowledge' });
|
|
216
|
+
return { slug, errors, created: errors.length === 0 };
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: 'create-session',
|
|
221
|
+
op: 'create',
|
|
222
|
+
kind: 'session',
|
|
223
|
+
schema: CreateSessionBranch,
|
|
224
|
+
description: 'Create a new session note linked to an entity.',
|
|
225
|
+
exampleInclude: ['synthesis'],
|
|
226
|
+
check(op, ctx) {
|
|
227
|
+
const slug = slugify(op.name);
|
|
228
|
+
if (ctx.existsAnywhere(slug) || ctx.sessionExists(slug)) {
|
|
229
|
+
return { slug, errors: [], warning: `session '${slug}' already exists — op will be skipped (batch continues)` };
|
|
230
|
+
}
|
|
231
|
+
ctx.simulateSession(slug);
|
|
232
|
+
return { slug, errors: [], created: true };
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
name: 'capture',
|
|
237
|
+
op: 'capture',
|
|
238
|
+
schema: CaptureBranch,
|
|
239
|
+
description: 'Quick capture — a raw inbox item with no structure required.',
|
|
240
|
+
exampleInclude: ['notes'],
|
|
241
|
+
check(op, ctx) {
|
|
242
|
+
const errors = [];
|
|
243
|
+
const slug = slugify(op.name);
|
|
244
|
+
if (ctx.existsAnywhere(slug))
|
|
245
|
+
errors.push(`Slug '${slug}' already exists`);
|
|
246
|
+
ctx.simulate(slug, { kind: 'entity', type: 'capture' });
|
|
247
|
+
return { slug, errors, created: errors.length === 0 };
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: 'link',
|
|
252
|
+
op: 'link',
|
|
253
|
+
schema: LinkBranch,
|
|
254
|
+
description: 'Add a parent or related link between two entities.',
|
|
255
|
+
exampleInclude: [],
|
|
256
|
+
check(op, ctx) {
|
|
257
|
+
const errors = [];
|
|
258
|
+
if (!ctx.resolve(op.slug))
|
|
259
|
+
errors.push(`'${op.slug}' not found`);
|
|
260
|
+
if (!ctx.existsAnywhere(op.target))
|
|
261
|
+
errors.push(`Target '${op.target}' does not exist`);
|
|
262
|
+
return { slug: op.slug, errors };
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: 'unlink',
|
|
267
|
+
op: 'unlink',
|
|
268
|
+
schema: UnlinkBranch,
|
|
269
|
+
description: 'Remove a parent or related link between two entities.',
|
|
270
|
+
exampleInclude: [],
|
|
271
|
+
check(op, ctx) {
|
|
272
|
+
const errors = [];
|
|
273
|
+
if (!ctx.resolve(op.slug))
|
|
274
|
+
errors.push(`'${op.slug}' not found`);
|
|
275
|
+
return { slug: op.slug, errors };
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
name: 'promote',
|
|
280
|
+
op: 'promote',
|
|
281
|
+
schema: PromoteBranch,
|
|
282
|
+
description: 'Promote a capture to entity/project/knowledge, or an entity to project.',
|
|
283
|
+
exampleInclude: [],
|
|
284
|
+
check(op, ctx) {
|
|
285
|
+
const errors = [];
|
|
286
|
+
const cur = ctx.resolve(op.slug);
|
|
287
|
+
if (!cur)
|
|
288
|
+
errors.push(`'${op.slug}' not found`);
|
|
289
|
+
else {
|
|
290
|
+
const currentType = cur.kind === 'knowledge' ? 'knowledge' : cur.type;
|
|
291
|
+
try {
|
|
292
|
+
const contract = getContract(currentType);
|
|
293
|
+
if (!contract.canPromoteTo.includes(op.target)) {
|
|
294
|
+
errors.push(`Cannot promote ${currentType} to ${op.target}. Valid targets: ${contract.canPromoteTo.join(', ') || 'none (terminal type)'}`);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
ctx.simulate(op.slug, op.target === 'knowledge' ? { kind: 'knowledge', type: 'knowledge' } : { kind: 'entity', type: op.target });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
catch (err) {
|
|
301
|
+
pushErr(errors, err);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return { slug: op.slug, errors };
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
];
|
|
308
|
+
/** Find the spec for a (shape-valid) operation. */
|
|
309
|
+
export function specFor(op) {
|
|
310
|
+
const spec = OP_SPECS.find(s => s.op === op.op && (s.kind === undefined || s.kind === op.kind));
|
|
311
|
+
if (!spec)
|
|
312
|
+
throw new HafezError('VALIDATION_FAILED', `Unknown batch op: ${op.op}`);
|
|
313
|
+
return spec;
|
|
314
|
+
}
|
|
315
|
+
/** Op names for error text ("expected one of […]") — derived, never hardcoded. */
|
|
316
|
+
export function validOpNames() {
|
|
317
|
+
return [...new Set(OP_SPECS.map(s => s.op))];
|
|
318
|
+
}
|
|
319
|
+
/** Create kinds for error text — derived from the table. */
|
|
320
|
+
export function validCreateKinds() {
|
|
321
|
+
return OP_SPECS.filter(s => s.op === 'create' && s.kind).map(s => s.kind);
|
|
322
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Bad invocation (unknown flag, missing value, missing positional).
|
|
2
|
+
* Distinct from crashes: the error adapter maps it to exit 2. */
|
|
3
|
+
export declare class UsageError extends Error {
|
|
4
|
+
}
|
|
5
|
+
export type FlagType = 'string' | 'boolean';
|
|
6
|
+
/** Flag names WITHOUT the leading dashes, e.g. { 'add-action': 'string' }. */
|
|
7
|
+
export type FlagSpec = Record<string, FlagType>;
|
|
8
|
+
export interface ParsedArgs {
|
|
9
|
+
flags: Record<string, string | boolean | undefined>;
|
|
10
|
+
positionals: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function parseArgs(args: string[], spec: FlagSpec, usage: string): ParsedArgs;
|
|
13
|
+
/** Comma-separated list flag → trimmed array (undefined stays undefined). */
|
|
14
|
+
export declare function splitList(value: string | boolean | undefined): string[] | undefined;
|
|
15
|
+
export declare function str(value: string | boolean | undefined): string | undefined;
|
package/dist/cli/args.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// src/cli/args.ts
|
|
2
|
+
// The one flag parser behind every CLI command. Each command declares its
|
|
3
|
+
// flags; parseArgs enforces the declaration — unknown flags and missing
|
|
4
|
+
// values are UsageErrors instead of silently ignored tokens (the getFlag era
|
|
5
|
+
// needed a documented workaround because values stayed in the positional
|
|
6
|
+
// stream).
|
|
7
|
+
/** Bad invocation (unknown flag, missing value, missing positional).
|
|
8
|
+
* Distinct from crashes: the error adapter maps it to exit 2. */
|
|
9
|
+
export class UsageError extends Error {
|
|
10
|
+
}
|
|
11
|
+
export function parseArgs(args, spec, usage) {
|
|
12
|
+
const flags = {};
|
|
13
|
+
const positionals = [];
|
|
14
|
+
for (let i = 0; i < args.length; i++) {
|
|
15
|
+
const token = args[i];
|
|
16
|
+
if (token.startsWith('--')) {
|
|
17
|
+
const name = token.slice(2);
|
|
18
|
+
const type = spec[name];
|
|
19
|
+
if (!type)
|
|
20
|
+
throw new UsageError(`Unknown flag --${name}\n${usage}`);
|
|
21
|
+
if (type === 'boolean') {
|
|
22
|
+
flags[name] = true;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const value = args[i + 1];
|
|
26
|
+
// A declared flag in value position means the value was omitted
|
|
27
|
+
// (`--brief --clear-actions`) — consuming it would both corrupt the
|
|
28
|
+
// string and silently drop the swallowed flag.
|
|
29
|
+
if (value === undefined || (value.startsWith('--') && value.slice(2) in spec)) {
|
|
30
|
+
throw new UsageError(`--${name} requires a value\n${usage}`);
|
|
31
|
+
}
|
|
32
|
+
flags[name] = value;
|
|
33
|
+
i++;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
positionals.push(token);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { flags, positionals };
|
|
40
|
+
}
|
|
41
|
+
/** Comma-separated list flag → trimmed array (undefined stays undefined). */
|
|
42
|
+
export function splitList(value) {
|
|
43
|
+
if (typeof value !== 'string')
|
|
44
|
+
return undefined;
|
|
45
|
+
return value.split(',').map(s => s.trim());
|
|
46
|
+
}
|
|
47
|
+
export function str(value) {
|
|
48
|
+
return typeof value === 'string' ? value : undefined;
|
|
49
|
+
}
|
package/dist/cli/batch-errors.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// without those, the `unrecognized_keys` and `invalid_union_discriminator`
|
|
10
10
|
// paths are unreachable.
|
|
11
11
|
import { getOpSchema, listOps } from './schema-introspect.js';
|
|
12
|
+
import { validOpNames, validCreateKinds } from '../batch-ops.js';
|
|
12
13
|
// --- did-you-mean (cheap Levenshtein) ---
|
|
13
14
|
export function levenshtein(a, b) {
|
|
14
15
|
if (a === b)
|
|
@@ -91,7 +92,8 @@ export function opNameOf(raw) {
|
|
|
91
92
|
return null;
|
|
92
93
|
if (op === 'create') {
|
|
93
94
|
const kind = raw.kind;
|
|
94
|
-
|
|
95
|
+
const names = new Set(listOps().map(o => o.name));
|
|
96
|
+
if (typeof kind === 'string' && names.has(`create-${kind}`))
|
|
95
97
|
return `create-${kind}`;
|
|
96
98
|
return 'create';
|
|
97
99
|
}
|
|
@@ -126,7 +128,8 @@ function toStr(v) {
|
|
|
126
128
|
return String(v);
|
|
127
129
|
}
|
|
128
130
|
}
|
|
129
|
-
|
|
131
|
+
// Derived from the Op Spec table — never hardcoded here.
|
|
132
|
+
const VALID_OPS = validOpNames();
|
|
130
133
|
/**
|
|
131
134
|
* Format a single Zod issue into a readable block. Caller supplies the op
|
|
132
135
|
* index and the raw op JSON so the block can reference the right `hafez
|
|
@@ -160,7 +163,7 @@ export function formatIssue(issue, opIndex, raw) {
|
|
|
160
163
|
return [
|
|
161
164
|
header,
|
|
162
165
|
` field: kind`,
|
|
163
|
-
` issue: unknown kind ${got} (expected one of [
|
|
166
|
+
` issue: unknown kind ${got} (expected one of [${validCreateKinds().join(', ')}])`,
|
|
164
167
|
` ${tip}`,
|
|
165
168
|
].join('\n');
|
|
166
169
|
}
|