@qnote/q-ai-note 1.0.13 → 1.0.14

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.
@@ -0,0 +1,826 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { v4 as uuidv4 } from 'uuid';
6
+ function findIntentTaxonomy(project, task) {
7
+ const intent = project.intents.find((row) => String(row.id) === String(task.intent_id));
8
+ return String(intent?.taxonomy || '');
9
+ }
10
+ function getTaskArtifactTypes(project, taskId) {
11
+ return project.artifacts
12
+ .filter((row) => String(row.task_id) === String(taskId))
13
+ .map((row) => String(row.type || '').trim())
14
+ .filter(Boolean);
15
+ }
16
+ const DEFAULT_DATA_DIR = path.join(os.homedir(), '.q-ai-note', 'data');
17
+ const FILE_NAME = 'aser-runtime-projects.json';
18
+ const DEFAULT_PROTOCOL_PRESET_ID = 'engineering_protocol_v1';
19
+ function getBuiltInProtocolFallback() {
20
+ return {
21
+ id: DEFAULT_PROTOCOL_PRESET_ID,
22
+ version: 1,
23
+ roles: [
24
+ { role_id: 'product_manager', intents: ['create_requirement', 'escalate_issue'] },
25
+ { role_id: 'architect', intents: ['design_system', 'clarify_design'] },
26
+ { role_id: 'developer', intents: ['implement_feature', 'report_issue', 'clarify_design'] },
27
+ { role_id: 'reviewer', intents: ['review_code', 'escalate_issue'] },
28
+ ],
29
+ intents: [
30
+ 'create_requirement',
31
+ 'design_system',
32
+ 'implement_feature',
33
+ 'review_code',
34
+ 'clarify_design',
35
+ 'report_issue',
36
+ 'escalate_issue',
37
+ ],
38
+ intent_rules: [
39
+ { intent: 'design_system', requires: ['create_requirement'] },
40
+ { intent: 'implement_feature', requires: ['design_system'] },
41
+ { intent: 'review_code', requires: ['implement_feature'] },
42
+ { intent: 'escalate_issue', requires: ['report_issue'] },
43
+ ],
44
+ evaluation_templates: [
45
+ {
46
+ intent: 'implement_feature',
47
+ acceptance_criteria: ['code implemented', 'tests pass', 'design consistent'],
48
+ review_required: true,
49
+ },
50
+ {
51
+ intent: 'design_system',
52
+ acceptance_criteria: ['design doc available', 'dependencies validated'],
53
+ review_required: true,
54
+ },
55
+ {
56
+ intent: 'create_requirement',
57
+ acceptance_criteria: ['requirement complete', 'scope clear'],
58
+ review_required: false,
59
+ },
60
+ {
61
+ intent: 'review_code',
62
+ acceptance_criteria: ['review completed'],
63
+ review_required: false,
64
+ },
65
+ {
66
+ intent: 'clarify_design',
67
+ acceptance_criteria: ['clarification documented'],
68
+ review_required: false,
69
+ },
70
+ {
71
+ intent: 'report_issue',
72
+ acceptance_criteria: ['issue recorded'],
73
+ review_required: false,
74
+ },
75
+ {
76
+ intent: 'escalate_issue',
77
+ acceptance_criteria: ['escalation acknowledged', 'owner assigned'],
78
+ review_required: true,
79
+ },
80
+ ],
81
+ quality_gates: [
82
+ {
83
+ gate_id: 'G1',
84
+ stage: 'create_requirement',
85
+ required_evidence: ['requirement_doc'],
86
+ pass_when: 'requirement_is_complete',
87
+ },
88
+ {
89
+ gate_id: 'G2',
90
+ stage: 'design_system',
91
+ required_evidence: ['design_doc'],
92
+ pass_when: 'design_review_passed',
93
+ },
94
+ {
95
+ gate_id: 'G3',
96
+ stage: 'implement_feature',
97
+ required_evidence: ['pr', 'test_report'],
98
+ pass_when: 'implementation_tests_passed',
99
+ },
100
+ {
101
+ gate_id: 'G4',
102
+ stage: 'review_code',
103
+ required_evidence: ['review_record'],
104
+ pass_when: 'review_accepted',
105
+ },
106
+ {
107
+ gate_id: 'G5',
108
+ stage: 'escalate_issue',
109
+ required_evidence: ['risk_summary'],
110
+ pass_when: 'escalation_acknowledged',
111
+ },
112
+ ],
113
+ loop_actions: [
114
+ {
115
+ id: 'L1',
116
+ taxonomy: 'clarify_design',
117
+ trigger_event_types: ['message_posted'],
118
+ required_links: ['parent'],
119
+ closure_evidence: ['clarification_note'],
120
+ },
121
+ {
122
+ id: 'L2',
123
+ taxonomy: 'report_issue',
124
+ trigger_event_types: ['issue_reported', 'task_blocked'],
125
+ required_links: ['parent'],
126
+ closure_evidence: ['issue_record'],
127
+ },
128
+ {
129
+ id: 'L3',
130
+ taxonomy: 'escalate_issue',
131
+ trigger_event_types: ['escalation_requested'],
132
+ required_links: ['parent', 'related'],
133
+ closure_evidence: ['risk_summary'],
134
+ },
135
+ ],
136
+ };
137
+ }
138
+ function cloneProtocol(protocol) {
139
+ return {
140
+ id: protocol.id,
141
+ version: protocol.version,
142
+ roles: protocol.roles.map((row) => ({ role_id: row.role_id, intents: [...row.intents] })),
143
+ intents: [...protocol.intents],
144
+ intent_rules: protocol.intent_rules.map((row) => ({ intent: row.intent, requires: [...row.requires] })),
145
+ evaluation_templates: protocol.evaluation_templates.map((row) => ({
146
+ intent: row.intent,
147
+ acceptance_criteria: [...row.acceptance_criteria],
148
+ review_required: row.review_required,
149
+ })),
150
+ quality_gates: Array.isArray(protocol.quality_gates)
151
+ ? protocol.quality_gates.map((row) => ({
152
+ gate_id: String(row.gate_id || ''),
153
+ stage: String(row.stage || ''),
154
+ required_evidence: Array.isArray(row.required_evidence)
155
+ ? row.required_evidence.map((value) => String(value || ''))
156
+ : [],
157
+ pass_when: String(row.pass_when || ''),
158
+ }))
159
+ : [],
160
+ loop_actions: Array.isArray(protocol.loop_actions)
161
+ ? protocol.loop_actions.map((row) => ({
162
+ id: String(row.id || ''),
163
+ taxonomy: String(row.taxonomy || ''),
164
+ trigger_event_types: Array.isArray(row.trigger_event_types)
165
+ ? row.trigger_event_types.map((value) => String(value || ''))
166
+ : [],
167
+ required_links: Array.isArray(row.required_links)
168
+ ? row.required_links.map((value) => String(value || ''))
169
+ : [],
170
+ closure_evidence: Array.isArray(row.closure_evidence)
171
+ ? row.closure_evidence.map((value) => String(value || ''))
172
+ : [],
173
+ }))
174
+ : [],
175
+ updated_at: protocol.updated_at,
176
+ };
177
+ }
178
+ function resolveProtocolPresetPath(presetId) {
179
+ const fileName = `${presetId}.json`;
180
+ const currentFileDir = path.dirname(fileURLToPath(import.meta.url));
181
+ const candidates = [
182
+ path.resolve(currentFileDir, 'presets', fileName),
183
+ path.resolve(process.cwd(), 'src', 'server', 'presets', fileName),
184
+ path.resolve(process.cwd(), 'dist', 'server', 'presets', fileName),
185
+ ];
186
+ const matched = candidates.find((candidate) => fs.existsSync(candidate));
187
+ return matched || null;
188
+ }
189
+ function readProtocolPresetOrFallback(presetId) {
190
+ const fallback = getBuiltInProtocolFallback();
191
+ const presetPath = resolveProtocolPresetPath(presetId);
192
+ if (!presetPath) {
193
+ return {
194
+ ...fallback,
195
+ updated_at: nowIso(),
196
+ };
197
+ }
198
+ try {
199
+ const text = fs.readFileSync(presetPath, 'utf-8');
200
+ const parsed = JSON.parse(text);
201
+ return {
202
+ id: String(parsed.id || fallback.id),
203
+ version: Number.isFinite(Number(parsed.version)) ? Number(parsed.version) : fallback.version,
204
+ roles: Array.isArray(parsed.roles) ? parsed.roles.map((row) => ({
205
+ role_id: row.role_id,
206
+ intents: Array.isArray(row.intents) ? row.intents.map((value) => String(value || '')) : [],
207
+ })) : fallback.roles,
208
+ intents: Array.isArray(parsed.intents) ? parsed.intents.map((value) => String(value || '')) : fallback.intents,
209
+ intent_rules: Array.isArray(parsed.intent_rules) ? parsed.intent_rules.map((row) => ({
210
+ intent: String(row.intent || ''),
211
+ requires: Array.isArray(row.requires) ? row.requires.map((value) => String(value || '')) : [],
212
+ })) : fallback.intent_rules,
213
+ evaluation_templates: Array.isArray(parsed.evaluation_templates) ? parsed.evaluation_templates.map((row) => ({
214
+ intent: String(row.intent || ''),
215
+ acceptance_criteria: Array.isArray(row.acceptance_criteria)
216
+ ? row.acceptance_criteria.map((value) => String(value || ''))
217
+ : [],
218
+ review_required: Boolean(row.review_required),
219
+ })) : fallback.evaluation_templates,
220
+ quality_gates: Array.isArray(parsed.quality_gates) ? parsed.quality_gates.map((row) => ({
221
+ gate_id: String(row.gate_id || ''),
222
+ stage: String(row.stage || ''),
223
+ required_evidence: Array.isArray(row.required_evidence)
224
+ ? row.required_evidence.map((value) => String(value || ''))
225
+ : [],
226
+ pass_when: String(row.pass_when || ''),
227
+ })) : fallback.quality_gates,
228
+ loop_actions: Array.isArray(parsed.loop_actions) ? parsed.loop_actions.map((row) => ({
229
+ id: String(row.id || ''),
230
+ taxonomy: String(row.taxonomy || ''),
231
+ trigger_event_types: Array.isArray(row.trigger_event_types)
232
+ ? row.trigger_event_types.map((value) => String(value || ''))
233
+ : [],
234
+ required_links: Array.isArray(row.required_links)
235
+ ? row.required_links.map((value) => String(value || ''))
236
+ : [],
237
+ closure_evidence: Array.isArray(row.closure_evidence)
238
+ ? row.closure_evidence.map((value) => String(value || ''))
239
+ : [],
240
+ })) : fallback.loop_actions,
241
+ updated_at: nowIso(),
242
+ };
243
+ }
244
+ catch {
245
+ return {
246
+ ...fallback,
247
+ updated_at: nowIso(),
248
+ };
249
+ }
250
+ }
251
+ const DEFAULT_PROTOCOL_TEMPLATE = readProtocolPresetOrFallback(DEFAULT_PROTOCOL_PRESET_ID);
252
+ const DEFAULT_INTENTS = [...DEFAULT_PROTOCOL_TEMPLATE.intents];
253
+ export function getDefaultProtocolPresetId() {
254
+ return DEFAULT_PROTOCOL_PRESET_ID;
255
+ }
256
+ export function getDefaultProtocolPreset() {
257
+ return cloneProtocol(DEFAULT_PROTOCOL_TEMPLATE);
258
+ }
259
+ export function getDefaultIntentTaxonomy() {
260
+ return [...DEFAULT_INTENTS];
261
+ }
262
+ function buildDefaultProtocol() {
263
+ const base = cloneProtocol(DEFAULT_PROTOCOL_TEMPLATE);
264
+ base.updated_at = nowIso();
265
+ return base;
266
+ }
267
+ function nowIso() {
268
+ return new Date().toISOString();
269
+ }
270
+ function stableStringify(input) {
271
+ const normalize = (value) => {
272
+ if (Array.isArray(value))
273
+ return value.map((item) => normalize(item));
274
+ if (value && typeof value === 'object') {
275
+ const obj = value;
276
+ return Object.keys(obj)
277
+ .sort()
278
+ .reduce((acc, key) => {
279
+ acc[key] = normalize(obj[key]);
280
+ return acc;
281
+ }, {});
282
+ }
283
+ return value;
284
+ };
285
+ return JSON.stringify(normalize(input), null, 2);
286
+ }
287
+ function ensureDir(dir) {
288
+ if (!fs.existsSync(dir))
289
+ fs.mkdirSync(dir, { recursive: true });
290
+ }
291
+ function getDataDir() {
292
+ const fromEnv = process.env.Q_AI_NOTE_DATA_DIR || process.env.PERSONAL_AI_NOTEBOOK_DATA_DIR;
293
+ if (fromEnv && fromEnv.trim())
294
+ return path.resolve(fromEnv);
295
+ return DEFAULT_DATA_DIR;
296
+ }
297
+ function readData(filePath) {
298
+ if (!fs.existsSync(filePath))
299
+ return { projects: [] };
300
+ try {
301
+ const text = fs.readFileSync(filePath, 'utf-8');
302
+ const parsed = JSON.parse(text);
303
+ if (!parsed || !Array.isArray(parsed.projects))
304
+ return { projects: [] };
305
+ return parsed;
306
+ }
307
+ catch {
308
+ return { projects: [] };
309
+ }
310
+ }
311
+ function writeData(filePath, data) {
312
+ const tmpFile = `${filePath}.tmp`;
313
+ fs.writeFileSync(tmpFile, stableStringify(data), 'utf-8');
314
+ fs.renameSync(tmpFile, filePath);
315
+ }
316
+ function normalizeTask(task) {
317
+ return {
318
+ ...task,
319
+ related: Array.isArray(task.related) ? task.related.map((value) => String(value || '')) : [],
320
+ artifacts: Array.isArray(task.artifacts) ? task.artifacts.map((value) => String(value || '')) : [],
321
+ };
322
+ }
323
+ function normalizeProject(project) {
324
+ const protocol = project.protocol && typeof project.protocol === 'object'
325
+ ? project.protocol
326
+ : buildDefaultProtocol();
327
+ return {
328
+ ...project,
329
+ events: Array.isArray(project.events) ? project.events : [],
330
+ intents: Array.isArray(project.intents) ? project.intents : [],
331
+ tasks: Array.isArray(project.tasks) ? project.tasks.map(normalizeTask) : [],
332
+ artifacts: Array.isArray(project.artifacts) ? project.artifacts : [],
333
+ evaluations: Array.isArray(project.evaluations) ? project.evaluations : [],
334
+ protocol: {
335
+ id: String(protocol.id || 'engineering_protocol_v1'),
336
+ version: Number.isFinite(Number(protocol.version)) ? Number(protocol.version) : 1,
337
+ roles: Array.isArray(protocol.roles) ? protocol.roles.map((row) => ({
338
+ role_id: row.role_id,
339
+ intents: Array.isArray(row.intents) ? row.intents.map((value) => String(value || '')) : [],
340
+ })) : buildDefaultProtocol().roles,
341
+ intents: Array.isArray(protocol.intents) ? protocol.intents.map((value) => String(value || '')) : [...DEFAULT_INTENTS],
342
+ intent_rules: Array.isArray(protocol.intent_rules) ? protocol.intent_rules.map((row) => ({
343
+ intent: String(row.intent || ''),
344
+ requires: Array.isArray(row.requires) ? row.requires.map((value) => String(value || '')) : [],
345
+ })) : buildDefaultProtocol().intent_rules,
346
+ evaluation_templates: Array.isArray(protocol.evaluation_templates) ? protocol.evaluation_templates.map((row) => ({
347
+ intent: String(row.intent || ''),
348
+ acceptance_criteria: Array.isArray(row.acceptance_criteria) ? row.acceptance_criteria.map((value) => String(value || '')) : [],
349
+ review_required: Boolean(row.review_required),
350
+ })) : buildDefaultProtocol().evaluation_templates,
351
+ quality_gates: Array.isArray(protocol.quality_gates) ? protocol.quality_gates.map((row) => ({
352
+ gate_id: String(row.gate_id || ''),
353
+ stage: String(row.stage || ''),
354
+ required_evidence: Array.isArray(row.required_evidence) ? row.required_evidence.map((value) => String(value || '')) : [],
355
+ pass_when: String(row.pass_when || ''),
356
+ })) : buildDefaultProtocol().quality_gates || [],
357
+ loop_actions: Array.isArray(protocol.loop_actions) ? protocol.loop_actions.map((row) => ({
358
+ id: String(row.id || ''),
359
+ taxonomy: String(row.taxonomy || ''),
360
+ trigger_event_types: Array.isArray(row.trigger_event_types) ? row.trigger_event_types.map((value) => String(value || '')) : [],
361
+ required_links: Array.isArray(row.required_links) ? row.required_links.map((value) => String(value || '')) : [],
362
+ closure_evidence: Array.isArray(row.closure_evidence) ? row.closure_evidence.map((value) => String(value || '')) : [],
363
+ })) : buildDefaultProtocol().loop_actions || [],
364
+ updated_at: String(protocol.updated_at || nowIso()),
365
+ },
366
+ };
367
+ }
368
+ function inferActorRole(actorRole, actor) {
369
+ const role = String(actorRole || '').trim();
370
+ if (role === 'product_manager' || role === 'architect' || role === 'developer' || role === 'reviewer') {
371
+ return role;
372
+ }
373
+ const actorText = String(actor || '').toLowerCase();
374
+ if (actorText.includes('review'))
375
+ return 'reviewer';
376
+ if (actorText.includes('arch'))
377
+ return 'architect';
378
+ if (actorText.includes('pm') || actorText.includes('product'))
379
+ return 'product_manager';
380
+ return 'developer';
381
+ }
382
+ function detectIntentFromEvent(input) {
383
+ const payloadIntent = String(input.payload?.intent_taxonomy || '').trim();
384
+ if (payloadIntent)
385
+ return payloadIntent;
386
+ const eventType = String(input.event_type || '').trim();
387
+ if (eventType === 'message_posted')
388
+ return 'clarify_design';
389
+ if (eventType === 'requirement_defined')
390
+ return 'create_requirement';
391
+ if (eventType === 'design_defined')
392
+ return 'design_system';
393
+ if (eventType === 'implementation_reported')
394
+ return 'implement_feature';
395
+ if (eventType === 'review_requested')
396
+ return 'review_code';
397
+ if (eventType === 'issue_reported' || eventType === 'task_blocked')
398
+ return 'report_issue';
399
+ if (eventType === 'escalation_requested')
400
+ return 'escalate_issue';
401
+ return null;
402
+ }
403
+ export class AserRuntimeStore {
404
+ loaded = false;
405
+ filePath = '';
406
+ data = { projects: [] };
407
+ dataDirOverride;
408
+ constructor(dataDirOverride) {
409
+ this.dataDirOverride = dataDirOverride && dataDirOverride.trim()
410
+ ? path.resolve(dataDirOverride)
411
+ : undefined;
412
+ }
413
+ ensureLoaded() {
414
+ if (this.loaded)
415
+ return;
416
+ const dataDir = this.dataDirOverride || getDataDir();
417
+ ensureDir(dataDir);
418
+ this.filePath = path.join(dataDir, FILE_NAME);
419
+ this.data = readData(this.filePath);
420
+ this.data.projects = this.data.projects.map(normalizeProject);
421
+ this.loaded = true;
422
+ }
423
+ persist() {
424
+ this.data.projects = this.data.projects
425
+ .map(normalizeProject)
426
+ .sort((a, b) => String(a.created_at || '').localeCompare(String(b.created_at || '')));
427
+ writeData(this.filePath, this.data);
428
+ }
429
+ listProjects() {
430
+ this.ensureLoaded();
431
+ return this.data.projects.map((project) => normalizeProject(project));
432
+ }
433
+ getProject(projectId) {
434
+ this.ensureLoaded();
435
+ const row = this.data.projects.find((project) => String(project.id) === String(projectId));
436
+ return row ? normalizeProject(row) : null;
437
+ }
438
+ createProject(name, description = '') {
439
+ this.ensureLoaded();
440
+ const timestamp = nowIso();
441
+ const project = {
442
+ id: uuidv4(),
443
+ name: String(name || '').trim(),
444
+ description: String(description || ''),
445
+ created_at: timestamp,
446
+ updated_at: timestamp,
447
+ events: [],
448
+ intents: [],
449
+ tasks: [],
450
+ artifacts: [],
451
+ evaluations: [],
452
+ protocol: buildDefaultProtocol(),
453
+ };
454
+ this.data.projects.push(project);
455
+ this.persist();
456
+ return normalizeProject(project);
457
+ }
458
+ deleteProject(projectId) {
459
+ this.ensureLoaded();
460
+ const before = this.data.projects.length;
461
+ this.data.projects = this.data.projects.filter((project) => String(project.id) !== String(projectId));
462
+ if (this.data.projects.length === before)
463
+ return false;
464
+ this.persist();
465
+ return true;
466
+ }
467
+ getProtocol(projectId) {
468
+ this.ensureLoaded();
469
+ const project = this.data.projects.find((row) => row.id === projectId);
470
+ if (!project)
471
+ return null;
472
+ return normalizeProject(project).protocol;
473
+ }
474
+ updateProtocol(projectId, protocol) {
475
+ this.ensureLoaded();
476
+ const project = this.data.projects.find((row) => row.id === projectId);
477
+ if (!project)
478
+ return null;
479
+ const current = normalizeProject(project).protocol;
480
+ const next = {
481
+ ...current,
482
+ id: String(protocol.id || current.id || 'engineering_protocol_v1'),
483
+ version: Number.isFinite(Number(protocol.version)) ? Number(protocol.version) : current.version + 1,
484
+ roles: Array.isArray(protocol.roles) ? protocol.roles.map((row) => ({
485
+ role_id: row.role_id,
486
+ intents: Array.isArray(row.intents) ? row.intents.map((value) => String(value || '')) : [],
487
+ })) : current.roles,
488
+ intents: Array.isArray(protocol.intents) ? protocol.intents.map((value) => String(value || '')) : current.intents,
489
+ intent_rules: Array.isArray(protocol.intent_rules) ? protocol.intent_rules.map((row) => ({
490
+ intent: String(row.intent || ''),
491
+ requires: Array.isArray(row.requires) ? row.requires.map((value) => String(value || '')) : [],
492
+ })) : current.intent_rules,
493
+ evaluation_templates: Array.isArray(protocol.evaluation_templates) ? protocol.evaluation_templates.map((row) => ({
494
+ intent: String(row.intent || ''),
495
+ acceptance_criteria: Array.isArray(row.acceptance_criteria) ? row.acceptance_criteria.map((value) => String(value || '')) : [],
496
+ review_required: Boolean(row.review_required),
497
+ })) : current.evaluation_templates,
498
+ quality_gates: Array.isArray(protocol.quality_gates) ? protocol.quality_gates.map((row) => ({
499
+ gate_id: String(row.gate_id || ''),
500
+ stage: String(row.stage || ''),
501
+ required_evidence: Array.isArray(row.required_evidence) ? row.required_evidence.map((value) => String(value || '')) : [],
502
+ pass_when: String(row.pass_when || ''),
503
+ })) : (current.quality_gates || []),
504
+ loop_actions: Array.isArray(protocol.loop_actions) ? protocol.loop_actions.map((row) => ({
505
+ id: String(row.id || ''),
506
+ taxonomy: String(row.taxonomy || ''),
507
+ trigger_event_types: Array.isArray(row.trigger_event_types) ? row.trigger_event_types.map((value) => String(value || '')) : [],
508
+ required_links: Array.isArray(row.required_links) ? row.required_links.map((value) => String(value || '')) : [],
509
+ closure_evidence: Array.isArray(row.closure_evidence) ? row.closure_evidence.map((value) => String(value || '')) : [],
510
+ })) : (current.loop_actions || []),
511
+ updated_at: nowIso(),
512
+ };
513
+ project.protocol = next;
514
+ project.updated_at = next.updated_at;
515
+ this.persist();
516
+ return next;
517
+ }
518
+ appendEvent(projectId, input) {
519
+ this.ensureLoaded();
520
+ const project = this.data.projects.find((row) => row.id === projectId);
521
+ if (!project)
522
+ return null;
523
+ const event = {
524
+ id: uuidv4(),
525
+ actor: String(input.actor || '').trim(),
526
+ actor_kind: input.actor_kind,
527
+ timestamp: nowIso(),
528
+ type: String(input.type || '').trim(),
529
+ payload: input.payload && typeof input.payload === 'object' ? input.payload : {},
530
+ };
531
+ project.events.push(event);
532
+ project.updated_at = nowIso();
533
+ this.persist();
534
+ return event;
535
+ }
536
+ submitEvent(projectId, input) {
537
+ this.ensureLoaded();
538
+ const project = this.data.projects.find((row) => row.id === projectId);
539
+ if (!project)
540
+ return null;
541
+ const normalizedProject = normalizeProject(project);
542
+ project.protocol = normalizedProject.protocol;
543
+ const event = this.appendEvent(projectId, input);
544
+ if (!event)
545
+ return null;
546
+ const violations = [];
547
+ const detectedIntent = detectIntentFromEvent({
548
+ event_type: input.type,
549
+ payload: input.payload,
550
+ });
551
+ if (!detectedIntent) {
552
+ return { event, intent: null, task: null, violations };
553
+ }
554
+ if (!project.protocol.intents.includes(detectedIntent)) {
555
+ violations.push(`intent_not_in_protocol:${detectedIntent}`);
556
+ return { event, intent: null, task: null, violations };
557
+ }
558
+ const role = inferActorRole(input.actor_role, input.actor);
559
+ const roleDef = project.protocol.roles.find((row) => row.role_id === role);
560
+ if (!roleDef || !roleDef.intents.includes(detectedIntent)) {
561
+ violations.push(`role_intent_forbidden:${role}:${detectedIntent}`);
562
+ return { event, intent: null, task: null, violations };
563
+ }
564
+ const rule = project.protocol.intent_rules.find((row) => row.intent === detectedIntent);
565
+ if (rule && rule.requires.length) {
566
+ const fulfilled = rule.requires.every((requiredIntent) => {
567
+ return project.tasks.some((task) => String(task.intent_id || '').includes(requiredIntent)
568
+ || (task.status === 'accepted' && project.intents.some((intent) => intent.id === task.intent_id && intent.taxonomy === requiredIntent)));
569
+ });
570
+ if (!fulfilled) {
571
+ violations.push(`intent_dependency_not_met:${detectedIntent}`);
572
+ return { event, intent: null, task: null, violations };
573
+ }
574
+ }
575
+ const intent = this.createIntent(projectId, {
576
+ taxonomy: detectedIntent,
577
+ title: String(input.payload?.intent_title || `${detectedIntent} from ${input.type}`),
578
+ source_event_id: event.id,
579
+ });
580
+ if (!intent)
581
+ return { event, intent: null, task: null, violations };
582
+ const evalTemplate = project.protocol.evaluation_templates.find((row) => row.intent === detectedIntent);
583
+ const task = this.createTask(projectId, {
584
+ intent_id: intent.id,
585
+ title: String(input.payload?.task_title || intent.title || detectedIntent),
586
+ owner_role: role,
587
+ preferred_agent: String(input.payload?.preferred_agent || ''),
588
+ parent: input.payload?.parent ? String(input.payload.parent) : null,
589
+ related: Array.isArray(input.payload?.related)
590
+ ? input.payload.related.map((value) => String(value || '')).filter((value) => Boolean(value))
591
+ : [],
592
+ acceptance_criteria: evalTemplate?.acceptance_criteria?.join('; ') || String(input.payload?.acceptance_criteria || ''),
593
+ evaluation_policy: evalTemplate
594
+ ? JSON.stringify({ review_required: Boolean(evalTemplate.review_required) })
595
+ : String(input.payload?.evaluation_policy || ''),
596
+ });
597
+ return { event, intent, task, violations };
598
+ }
599
+ createIntent(projectId, input) {
600
+ this.ensureLoaded();
601
+ const project = this.data.projects.find((row) => row.id === projectId);
602
+ if (!project)
603
+ return null;
604
+ const intent = {
605
+ id: uuidv4(),
606
+ taxonomy: String(input.taxonomy || '').trim(),
607
+ title: String(input.title || '').trim(),
608
+ source_event_id: input.source_event_id ? String(input.source_event_id) : null,
609
+ created_at: nowIso(),
610
+ };
611
+ project.intents.push(intent);
612
+ project.updated_at = nowIso();
613
+ this.persist();
614
+ return intent;
615
+ }
616
+ createTask(projectId, input) {
617
+ this.ensureLoaded();
618
+ const project = this.data.projects.find((row) => row.id === projectId);
619
+ if (!project)
620
+ return null;
621
+ const timestamp = nowIso();
622
+ const task = {
623
+ id: uuidv4(),
624
+ intent_id: String(input.intent_id || '').trim(),
625
+ title: String(input.title || '').trim(),
626
+ status: 'created',
627
+ owner_role: String(input.owner_role || '').trim(),
628
+ preferred_agent: String(input.preferred_agent || '').trim(),
629
+ parent: input.parent ? String(input.parent) : null,
630
+ related: Array.isArray(input.related) ? input.related.map((value) => String(value || '')) : [],
631
+ acceptance_criteria: String(input.acceptance_criteria || ''),
632
+ evaluation_policy: String(input.evaluation_policy || ''),
633
+ artifacts: [],
634
+ created_at: timestamp,
635
+ updated_at: timestamp,
636
+ };
637
+ project.tasks.push(task);
638
+ project.updated_at = nowIso();
639
+ this.persist();
640
+ return task;
641
+ }
642
+ pullTask(projectId, role) {
643
+ this.ensureLoaded();
644
+ const project = this.data.projects.find((row) => row.id === projectId);
645
+ if (!project)
646
+ return null;
647
+ const nextTask = [...project.tasks]
648
+ .filter((task) => task.owner_role === role && (task.status === 'created' || task.status === 'ready'))
649
+ .sort((a, b) => String(a.created_at || '').localeCompare(String(b.created_at || '')))[0];
650
+ if (!nextTask)
651
+ return null;
652
+ nextTask.status = 'in_progress';
653
+ nextTask.updated_at = nowIso();
654
+ project.updated_at = nextTask.updated_at;
655
+ this.persist();
656
+ const intent = project.intents.find((row) => row.id === nextTask.intent_id) || null;
657
+ const relatedEvents = project.events
658
+ .filter((row) => row.id === intent?.source_event_id)
659
+ .slice(0, 20);
660
+ const artifacts = project.artifacts.filter((row) => row.task_id === nextTask.id);
661
+ return {
662
+ task: normalizeTask(nextTask),
663
+ context: {
664
+ project_id: projectId,
665
+ intent,
666
+ related_events: relatedEvents,
667
+ artifacts,
668
+ },
669
+ };
670
+ }
671
+ submitTaskResult(projectId, taskId, input) {
672
+ this.ensureLoaded();
673
+ const project = this.data.projects.find((row) => row.id === projectId);
674
+ if (!project)
675
+ return null;
676
+ const task = project.tasks.find((row) => row.id === taskId);
677
+ if (!task)
678
+ return null;
679
+ if (Array.isArray(input.artifacts)) {
680
+ input.artifacts.forEach((artifact) => {
681
+ if (!artifact || !artifact.type || !artifact.uri)
682
+ return;
683
+ this.addArtifact(projectId, taskId, artifact);
684
+ });
685
+ }
686
+ if (input.mark_blocked) {
687
+ task.status = 'blocked';
688
+ }
689
+ else {
690
+ const evalPolicy = String(task.evaluation_policy || '');
691
+ const reviewRequired = evalPolicy.includes('review_required') && evalPolicy.includes('true');
692
+ task.status = reviewRequired ? 'evaluating' : 'completed';
693
+ }
694
+ task.updated_at = nowIso();
695
+ project.updated_at = task.updated_at;
696
+ this.appendEvent(projectId, {
697
+ actor: input.actor || input.role || 'worker',
698
+ actor_kind: 'agent',
699
+ type: input.mark_blocked ? 'task_blocked' : 'task_completed',
700
+ payload: {
701
+ task_id: taskId,
702
+ blocked_reason: input.blocked_reason || '',
703
+ },
704
+ });
705
+ this.persist();
706
+ return normalizeTask(task);
707
+ }
708
+ updateTaskStatus(projectId, taskId, status) {
709
+ this.ensureLoaded();
710
+ const project = this.data.projects.find((row) => row.id === projectId);
711
+ if (!project)
712
+ return null;
713
+ const task = project.tasks.find((row) => row.id === taskId);
714
+ if (!task)
715
+ return null;
716
+ task.status = status;
717
+ task.updated_at = nowIso();
718
+ project.updated_at = task.updated_at;
719
+ this.persist();
720
+ return normalizeTask(task);
721
+ }
722
+ addArtifact(projectId, taskId, input) {
723
+ this.ensureLoaded();
724
+ const project = this.data.projects.find((row) => row.id === projectId);
725
+ if (!project)
726
+ return null;
727
+ const task = project.tasks.find((row) => row.id === taskId);
728
+ if (!task)
729
+ return null;
730
+ const artifact = {
731
+ id: uuidv4(),
732
+ task_id: taskId,
733
+ type: String(input.type || '').trim(),
734
+ uri: String(input.uri || '').trim(),
735
+ summary: String(input.summary || ''),
736
+ created_at: nowIso(),
737
+ };
738
+ project.artifacts.push(artifact);
739
+ task.artifacts.push(artifact.id);
740
+ task.updated_at = nowIso();
741
+ project.updated_at = task.updated_at;
742
+ this.persist();
743
+ return artifact;
744
+ }
745
+ addEvaluation(projectId, taskId, input) {
746
+ this.ensureLoaded();
747
+ const project = this.data.projects.find((row) => row.id === projectId);
748
+ if (!project)
749
+ return null;
750
+ const task = project.tasks.find((row) => row.id === taskId);
751
+ if (!task)
752
+ return null;
753
+ let finalDecision = input.decision;
754
+ const taskTaxonomy = findIntentTaxonomy(project, task);
755
+ const gate = (project.protocol.quality_gates || []).find((row) => String(row.stage || '') === taskTaxonomy);
756
+ const expectedEvidence = Array.isArray(gate?.required_evidence)
757
+ ? gate.required_evidence.map((row) => String(row || '').trim()).filter(Boolean)
758
+ : [];
759
+ const providedEvidence = getTaskArtifactTypes(project, task.id);
760
+ const missingEvidence = expectedEvidence.filter((type) => !providedEvidence.includes(type));
761
+ let mergedComments = String(input.comments || '');
762
+ if (input.decision === 'accept' && missingEvidence.length > 0) {
763
+ finalDecision = 'revise';
764
+ const gateId = String(gate?.gate_id || 'gate');
765
+ const detail = `gate_blocked:${gateId}:missing_evidence:${missingEvidence.join(',')}`;
766
+ mergedComments = mergedComments ? `${mergedComments} | ${detail}` : detail;
767
+ this.appendEvent(projectId, {
768
+ actor: String(input.evaluator || 'reviewer'),
769
+ actor_kind: 'human',
770
+ type: 'quality_gate_blocked',
771
+ payload: {
772
+ task_id: task.id,
773
+ gate_id: gateId,
774
+ expected_evidence: expectedEvidence,
775
+ provided_evidence: providedEvidence,
776
+ missing_evidence: missingEvidence,
777
+ },
778
+ });
779
+ }
780
+ const evaluation = {
781
+ id: uuidv4(),
782
+ task_id: taskId,
783
+ evaluator: String(input.evaluator || '').trim(),
784
+ decision: finalDecision,
785
+ score: Number.isFinite(Number(input.score)) ? Number(input.score) : 0,
786
+ comments: mergedComments,
787
+ created_at: nowIso(),
788
+ };
789
+ project.evaluations.push(evaluation);
790
+ task.status = finalDecision === 'accept'
791
+ ? 'accepted'
792
+ : finalDecision === 'revise'
793
+ ? 'failed'
794
+ : 'failed';
795
+ task.updated_at = nowIso();
796
+ project.updated_at = task.updated_at;
797
+ this.persist();
798
+ return evaluation;
799
+ }
800
+ getProjectState(projectId) {
801
+ this.ensureLoaded();
802
+ const project = this.data.projects.find((row) => row.id === projectId);
803
+ if (!project)
804
+ return null;
805
+ const total = project.tasks.length;
806
+ const accepted = project.tasks.filter((task) => task.status === 'accepted').length;
807
+ const blocked = project.tasks.filter((task) => task.status === 'blocked').length;
808
+ const reviewPending = project.tasks.filter((task) => task.status === 'evaluating').length;
809
+ const failed = project.tasks.filter((task) => task.status === 'failed').length;
810
+ const gateBlockedEvents = project.events.filter((event) => String(event.type || '') === 'quality_gate_blocked').length;
811
+ const latestEvent = [...project.events]
812
+ .sort((a, b) => String(b.timestamp || '').localeCompare(String(a.timestamp || '')))[0];
813
+ return {
814
+ feature_completion: total <= 0 ? 0 : Number(((accepted / total) * 100).toFixed(2)),
815
+ accepted_tasks: accepted,
816
+ total_tasks: total,
817
+ blocked_tasks: blocked,
818
+ review_pending_tasks: reviewPending,
819
+ failed_tasks: failed,
820
+ quality_gate_blocked_events: gateBlockedEvents,
821
+ last_event_at: latestEvent?.timestamp || null,
822
+ };
823
+ }
824
+ }
825
+ export const aserRuntimeStore = new AserRuntimeStore();
826
+ //# sourceMappingURL=aserRuntimeStore.js.map